PageSourceSearch

https://zalmoxeland.ro/wp-content/plugins/ican-assistant/js/api/openai.js

js zalmoxeland.ro collected 2026-10-02 02:06:12 UTC 13,386 bytes, 345 lines download raw bytes

1// This file contains functions to interact with OpenAI's Assistants API v2
2
3// LOCAL DEVELOPMENT: For local testing with php -S localhost:8000
4// const LOCAL_PHP_PROXY = 'http://localhost:8000/proxy.php?endpoint=';
5
6// WORDPRESS: Use this path for WordPress
7const WP_PHP_PROXY = '/wp-content/plugins/ican-assistant/proxy.php?endpoint=';
8
9const CONFIG = {
10    OPENAI_API_KEY: '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', 
11    ASSISTANT_ID: 'asst_HcIO8BjlSawUUv0wYyBWOAMI', 
12    API_BASE_URL: 'https://api.openai.com/v1'
13};
14
15class OpenAIService {
16    constructor() {
17        this.threadId = localStorage.getItem('threadId') || null;
18        
19        // Use the WordPress PHP proxy
20        this.apiBaseUrl = WP_PHP_PROXY;
21    }
22
23    // Updated createThread method for Assistants API v2
24    async createThread() {
25        try {
26            console.log('Creating thread via PHP proxy...');
27            const response = await fetch(`${this.apiBaseUrl}/threads`, {
28                method: 'POST',
29                headers: {
30                    'Content-Type': 'application/json'
31                    // No Auth header needed - handled by the PHP proxy
32                },
33                body: JSON.stringify({})
34            });
35
36            if (!response.ok) {
37                const errorData = await response.json().catch(() => ({}));
38                console.error('Thread creation error details:', errorData);
39                if (errorData.error && errorData.error.message) {
40                    console.error('Error message:', errorData.error.message);
41                }
42                throw new Error(`Failed to create thread: ${response.status}`);
43            }
44
45            const data = await response.json();
46            this.threadId = data.id;
47            localStorage.setItem('threadId', this.threadId);
48            console.log('Thread created:', this.threadId);
49            return this.threadId;
50        } catch (error) {
51            console.error('Error creating thread:', error);
52            throw error;
53        }
54    }
55
56    async sendMessage(message) {
57        try {
58            // Ensure we have a thread
59            if (!this.threadId) {
60                await this.createThread();
61            }
62
63            // Check if we need to summarize and create a new thread
64            // (perform this before sending the new message)
65            await this.summarizeAndManageThread();
66
67            console.log('Sending message to thread:', this.threadId);
68
69            // Rest of your existing sendMessage method...
70            
71            // Add the message to the thread through PHP proxy
72            const messageResponse = await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/messages`, {
73                method: 'POST',
74                headers: {
75                    'Content-Type': 'application/json'
76                    // No Auth header needed
77                },
78                body: JSON.stringify({
79                    role: 'user',
80                    content: message
81                })
82            });
83
84            if (!messageResponse.ok) {
85                const errorData = await messageResponse.json().catch(() => ({}));
86                console.error('Message error details:', errorData);
87                throw new Error(`Failed to add message: ${messageResponse.status}`);
88            }
89
90            const messageData = await messageResponse.json();
91            console.log('Message added:', messageData.id);
92
93            // Run the assistant on the thread through PHP proxy
94            const runResponse = await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/runs`, {
95                method: 'POST',
96                headers: {
97                    'Content-Type': 'application/json'
98                    // No Auth header needed
99                },
100                body: JSON.stringify({
101                    assistant_id: CONFIG.ASSISTANT_ID
102                })
103            });
104
105            if (!runResponse.ok) {
106                const errorData = await runResponse.json().catch(() => ({}));
107                console.error('Run error details:', errorData);
108                throw new Error(`Failed to run assistant: ${runResponse.status}`);
109            }
110
111            const runData = await runResponse.json();
112            const runId = runData.id;
113            console.log('Run created:', runId);
114
115            // Poll for the run completion
116            return await this.pollRunStatus(runId);
117
118        } catch (error) {
119            console.error('Error sending message:', error);
120            throw error;
121        }
122    }
123
124    async pollRunStatus(runId) {
125        try {
126            let runStatus = 'queued';
127            console.log('Starting to poll run status for:', runId);
128            
129            while (runStatus !== 'completed' && runStatus !== 'failed' && runStatus !== 'expired') {
130                // Wait for 1 second before checking status
131                await new Promise(resolve => setTimeout(resolve, 1000));
132                
133                // Get status via PHP proxy
134                const response = await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/runs/${runId}`, {
135                    headers: {
136                        'Content-Type': 'application/json'
137                        // No Auth header needed
138                    }
139                });
140                
141                if (!response.ok) {
142                    const errorData = await response.json().catch(() => ({}));
143                    console.error('Poll status error details:', errorData);
144                    throw new Error(`Failed to get run status: ${response.status}`);
145                }
146                
147                const data = await response.json();
148                runStatus = data.status;
149                console.log('Run status:', runStatus);
150                
151                if (runStatus === 'requires_action') {
152                    console.log('Run requires action', data);
153                    // Handle function calling if needed
154                }
155            }
156            
157            if (runStatus === 'completed') {
158                return await this.getMessages();
159            } else {
160                throw new Error(`Run ended with status: ${runStatus}`);
161            }
162        } catch (error) {
163            console.error('Error polling run status:', error);
164            throw error;
165        }
166    }
167
168    // Update the getMessages method to preserve formatting
169
170    async getMessages() {
171        try {
172            console.log('Getting messages from thread:', this.threadId);
173            
174            // Get messages via PHP proxy
175            const response = await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/messages?limit=1`, {
176                headers: {
177                    'Content-Type': 'application/json'
178                    // No Auth header needed
179                }
180            });
181
182            if (!response.ok) {
183                const errorData = await response.json().catch(() => ({}));
184                console.error('Get messages error details:', errorData);
185                throw new Error(`Failed to get messages: ${response.status}`);
186            }
187
188            const data = await response.json();
189            console.log('Got messages response:', data);
190            
191            const messages = data.data;
192            
193            // Return the most recent assistant message
194            const assistantMessages = messages.filter(msg => msg.role === 'assistant');
195            if (assistantMessages.length > 0) {
196                const messageContent = assistantMessages[0].content[0].text.value;
197                console.log('Assistant response:', messageContent);
198                return messageContent;
199            } else {
200                return "I'm sorry, I couldn't generate a response.";
201            }
202        } catch (error) {
203            console.error('Error getting messages:', error);
204            throw error;
205        }
206    }
207
208    async summarizeAndManageThread() {
209        try {
210            if (!this.threadId) return;
211            
212            // Get all messages in the thread
213            const response = await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/messages?limit=30&order=asc`, {
214                headers: {
215                    'Content-Type': 'application/json'
216                }
217            });
218
219            if (!response.ok) {
220                throw new Error(`Failed to get messages for summarization: ${response.status}`);
221            }
222
223            const data = await response.json();
224            const messages = data.data;
225            
226            // If we have more than 30 messages, summarize and create a new thread
227            if (messages.length >= 30) {
228                console.log('Thread has reached 30+ messages. Summarizing conversation...');
229                
230                // Ask the assistant to summarize the conversation
231                const summaryMessage = await this.createSummaryRequest();
232                
233                // Create a new thread
234                const oldThreadId = this.threadId;
235                await this.createThread();
236                
237                // Add the summary as the first message in the new thread
238                await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/messages`, {
239                    method: 'POST',
240                    headers: {
241                        'Content-Type': 'application/json'
242                    },
243                    body: JSON.stringify({
244                        role: 'user',
245                        content: `Previous conversation summary: ${summaryMessage}`
246                    })
247                });
248                
249                console.log('Created new thread with conversation summary');
250                return true;
251            }
252            
253            return false;
254        } catch (error) {
255            console.error('Error managing thread:', error);
256            return false;
257        }
258    }
259
260    async createSummaryRequest() {
261        try {
262            console.log('Requesting conversation summary...');
263            
264            // Add a request for summarization
265            const messageResponse = await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/messages`, {
266                method: 'POST',
267                headers: {
268                    'Content-Type': 'application/json'
269                },
270                body: JSON.stringify({
271                    role: 'user',
272                    content: "Please summarize our conversation so far, capturing all important points, advice given, and context needed for continuing our discussion. This summary will be used as context for our continued conversation."
273                })
274            });
275
276            if (!messageResponse.ok) {
277                throw new Error(`Failed to add summary request: ${messageResponse.status}`);
278            }
279
280            // Run the assistant to generate the summary
281            const runResponse = await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/runs`, {
282                method: 'POST',
283                headers: {
284                    'Content-Type': 'application/json'
285                },
286                body: JSON.stringify({
287                    assistant_id: CONFIG.ASSISTANT_ID
288                })
289            });
290
291            if (!runResponse.ok) {
292                throw new Error(`Failed to run summary generation: ${runResponse.status}`);
293            }
294
295            const runData = await runResponse.json();
296            
297            // Wait for the summary to be generated
298            await this.pollRunStatus(runData.id);
299            
300            // Get the summary message
301            const summaryResponse = await fetch(`${this.apiBaseUrl}/threads/${this.threadId}/messages?limit=1`, {
302                headers: {
303                    'Content-Type': 'application/json'
304                }
305            });
306
307            if (!summaryResponse.ok) {
308                throw new Error(`Failed to get summary: ${summaryResponse.status}`);
309            }
310
311            const summaryData = await summaryResponse.json();
312            const assistantMessages = summaryData.data.filter(msg => msg.role === 'assistant');
313            
314            if (assistantMessages.length > 0) {
315                const summaryText = assistantMessages[0].content[0].text.value;
316                console.log('Summary generated:', summaryText.substring(0, 100) + '...');
317                return summaryText;
318            } else {
319                return "Summary of previous conversation.";
320            }
321        } catch (error) {
322            console.error('Error creating summary:', error);
323            return "Error summarizing previous conversation.";
324        }
325    }
326
327    async resetConversation() {
328        try {
329            // Clear the thread ID from memory and localStorage
330            this.threadId = null;
331            localStorage.removeItem('threadId');
332            
333            // Create a new thread right away so it's ready for the next message
334            await this.createThread();
335            
336            console.log('Conversation reset, new thread created:', this.threadId);
337            return true;
338        } catch (error) {
339            console.error('Error resetting conversation:', error);
340            return false;
341        }
342    }
343}
344
345const openaiService = new OpenAIService();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.