@5minds/node-red-dashboard-2-processcube-chat 0.1.1-develop-bd0cb7-mcvkuzxr → 0.1.1-develop-cb2322-mf120t2z

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,349 +1,374 @@
1
1
  <template>
2
- <div ref="deepChatContainer" class="ui-deepchat-container" :style="containerStyle">
2
+ <div class="deep-chat-container">
3
3
  <deep-chat
4
- ref="deepChat"
5
- :intro-message="config.introMessage"
6
- :placeholder-text="config.placeholder"
7
- :text-input="config.textInput"
8
- :speech-to-text="speechToTextConfig"
9
- :camera="cameraConfig"
10
- :microphone="microphoneConfig"
11
- :mixedFiles="attachmentsConfig"
12
- :avatars="config.avatars"
13
- :names="config.names"
14
- :timestamps="config.timestamps"
15
- :stream="config.stream"
4
+ :style="deepChatStyle"
5
+ :textInput="textInputConfig"
6
+ :introMessage="introMessageConfig"
16
7
  :connect="connectConfig"
17
- @onNewMessage="handleNewMessage"
18
- @onClearMessages="handleClearMessages"
19
- @onComponentRender="handleComponentRender"
20
- @onResponse="handleResponse"
21
- @onError="handleError"
8
+ :speechToText="props.speechToText"
9
+ :camera="props.camera"
10
+ :mixedFiles="props.attachments"
11
+ :avatars="props.avatars"
12
+ :names="props.names"
13
+ :timestamps="props.timestamps"
14
+ :stream="props.stream"
22
15
  ></deep-chat>
23
16
  </div>
24
17
  </template>
25
18
 
26
19
  <script>
20
+ import 'deep-chat';
21
+
27
22
  export default {
28
23
  name: 'UIDeepChat',
29
24
  props: ['id', 'props', 'state'],
30
-
25
+ inject: ['$socket'],
31
26
  data() {
32
27
  return {
33
- config: {
34
- introMessage: 'Hello! How can I help you today?',
35
- placeholder: 'Type a message...',
36
- apiUrl: '',
37
- apiKey: '',
38
- model: 'gpt-3.5-turbo',
39
- textInput: true,
40
- speechToText: false,
41
- camera: false,
42
- microphone: false,
43
- attachments: false,
44
- avatars: true,
45
- names: true,
46
- timestamps: false,
47
- stream: false
48
- },
49
- isDeepChatLoaded: false,
50
- messages: []
51
- }
28
+ conversation: [],
29
+ };
52
30
  },
53
-
31
+
54
32
  computed: {
55
- containerStyle() {
33
+ deepChatStyle() {
56
34
  return {
57
35
  width: '100%',
58
- height: '100%',
59
- minHeight: '400px'
60
- }
61
- },
62
-
63
- speechToTextConfig() {
64
- return this.config.speechToText ? {
65
- button: true,
66
- displayInterimResults: true
67
- } : false
36
+ maxWidth: '600px',
37
+ height: '80vh',
38
+ borderRadius: '8px',
39
+ };
68
40
  },
69
-
70
- cameraConfig() {
71
- return this.config.camera ? {
72
- button: true
73
- } : false
74
- },
75
-
76
- microphoneConfig() {
77
- return this.config.microphone ? {
78
- button: true,
79
- audio: true
80
- } : false
41
+
42
+ textInputConfig() {
43
+ return {
44
+ placeholder: {
45
+ text: this.props.placeholder || 'Type a message...',
46
+ },
47
+ };
81
48
  },
82
-
83
- attachmentsConfig() {
84
- return this.config.attachments ? {
85
- button: true,
86
- acceptedFormats: ".jpeg,.jpg,.png,.gif,.pdf,.txt,.doc,.docx"
87
- } : false
49
+
50
+ introMessageConfig() {
51
+ return {
52
+ text: this.props.introMessage || 'Hello! How can I help you today?',
53
+ };
88
54
  },
89
-
55
+
90
56
  connectConfig() {
91
- if (this.config.apiUrl && this.config.apiKey) {
92
- return {
93
- url: this.config.apiUrl,
94
- method: 'POST',
95
- headers: {
96
- 'Authorization': `Bearer ${this.config.apiKey}`,
97
- 'Content-Type': 'application/json'
98
- },
99
- body: {
100
- model: this.config.model,
101
- max_tokens: 2000,
102
- temperature: 0.7
103
- },
104
- stream: this.config.stream
105
- }
106
- }
107
-
108
- // Custom Handler für Node-RED Integration
109
57
  return {
110
- handler: this.handleCustomConnection,
111
- stream: this.config.stream
112
- }
113
- }
114
- },
115
-
116
- mounted() {
117
- this.loadDeepChat()
118
- this.setupSocketListeners()
119
- this.applyConfiguration()
58
+ handler: this.handleConnection,
59
+ };
60
+ },
120
61
  },
121
-
122
62
  beforeUnmount() {
123
- this.removeSocketListeners()
63
+ this.$socket.off('msg-input:' + this.id);
124
64
  },
125
-
65
+
126
66
  methods: {
127
- async loadDeepChat() {
67
+ async handleConnection(body, signals) {
128
68
  try {
129
- // Deep Chat von CDN laden
130
- if (!window.DeepChat) {
131
- const script = document.createElement('script')
132
- script.src = 'https://unpkg.com/deep-chat@latest/dist/deepChat.bundle.js'
133
- script.onload = () => {
134
- this.isDeepChatLoaded = true
135
- this.$nextTick(() => {
136
- this.initializeDeepChat()
137
- })
69
+ // Extract messages and files from FormData
70
+ let newMessages = [];
71
+ let files = [];
72
+
73
+ if (body instanceof FormData) {
74
+ for (let [key, value] of body.entries()) {
75
+ if (key.startsWith('message')) {
76
+ try {
77
+ const messageContent = JSON.parse(value);
78
+ newMessages.push(messageContent);
79
+ } catch (e) {
80
+ console.error('Error parsing message:', e);
81
+ }
82
+ } else if (key === 'files') {
83
+ files.push(value);
84
+ }
138
85
  }
139
- document.head.appendChild(script)
140
- } else {
141
- this.isDeepChatLoaded = true
142
- this.$nextTick(() => {
143
- this.initializeDeepChat()
144
- })
86
+
87
+ // Process files if present
88
+ if (files.length > 0 && newMessages.length > 0) {
89
+ const lastMessage = newMessages[newMessages.length - 1];
90
+ lastMessage.files = await this.processFiles(files);
91
+ }
92
+ } else if (body.messages) {
93
+ newMessages = body.messages;
145
94
  }
95
+
96
+ // Add to conversation history
97
+ this.conversation.push(...newMessages);
98
+
99
+ // Send to Node-RED
100
+ const payload = this.formatForChatGPT(this.conversation);
101
+ this.sendToNodeRED(payload, signals);
146
102
  } catch (error) {
147
- console.error('Error loading Deep Chat:', error)
103
+ console.error('Error in handleConnection:', error);
104
+ this.sendErrorResponse(signals, 'Sorry, there was an error processing your message.');
148
105
  }
149
106
  },
150
-
151
- initializeDeepChat() {
152
- if (this.$refs.deepChat) {
153
- // Deep Chat ist bereit
154
- console.log('Deep Chat initialized for widget:', this.id)
155
-
156
- // Intro Message senden falls konfiguriert
157
- if (this.config.introMessage) {
158
- this.addMessage(this.config.introMessage, 'ai')
159
- }
107
+
108
+ async processFiles(files) {
109
+ try {
110
+ return await Promise.all(
111
+ files.map(async (file) => {
112
+ if (!(file instanceof File)) return file;
113
+
114
+ if (file.type.startsWith('image/')) {
115
+ return await this.processImageFile(file);
116
+ } else if (file.type.startsWith('audio/')) {
117
+ return await this.processAudioFile(file);
118
+ } else {
119
+ // Handle other file types (PDFs, documents, etc.)
120
+ return await this.processDocumentFile(file);
121
+ }
122
+ })
123
+ );
124
+ } catch (error) {
125
+ console.error('Error processing files:', error);
126
+ return [];
160
127
  }
161
128
  },
162
-
163
- setupSocketListeners() {
164
- // Neue Nachricht von Node-RED empfangen
165
- this.$socket.on('deepchat-newMessage:' + this.id, (data) => {
166
- this.addMessage(data.message, data.role || 'ai', data.html, data.files)
167
- })
168
-
169
- // Konfiguration aktualisieren
170
- this.$socket.on('deepchat-updateConfig:' + this.id, (newConfig) => {
171
- this.config = { ...this.config, ...newConfig }
172
- this.applyConfiguration()
173
- })
174
-
175
- // Chat löschen
176
- this.$socket.on('deepchat-clearMessages:' + this.id, () => {
177
- this.clearMessages()
178
- })
179
-
180
- // Standard Dashboard Message Input
181
- this.$socket.on('msg-input:' + this.id, (msg) => {
182
- if (msg.payload) {
183
- this.addMessage(msg.payload, msg.role || 'ai', msg.html, msg.files)
184
- }
185
- if (msg.config) {
186
- this.config = { ...this.config, ...msg.config }
187
- }
188
- if (msg.clear) {
189
- this.clearMessages()
190
- }
191
- })
192
- },
193
-
194
- removeSocketListeners() {
195
- this.$socket.off('deepchat-newMessage:' + this.id)
196
- this.$socket.off('deepchat-updateConfig:' + this.id)
197
- this.$socket.off('deepchat-clearMessages:' + this.id)
198
- this.$socket.off('msg-input:' + this.id)
129
+
130
+ processImageFile(file) {
131
+ return new Promise((resolve, reject) => {
132
+ const reader = new FileReader();
133
+ reader.onload = (e) =>
134
+ resolve({
135
+ name: file.name,
136
+ type: file.type,
137
+ size: file.size,
138
+ src: e.target.result,
139
+ });
140
+ reader.onerror = () => reject(new Error('Failed to read image'));
141
+ reader.readAsDataURL(file);
142
+ });
199
143
  },
200
-
201
- applyConfiguration() {
202
- // Konfiguration aus props übernehmen
203
- if (this.props) {
204
- Object.keys(this.props).forEach(key => {
205
- if (this.config.hasOwnProperty(key)) {
206
- this.config[key] = this.props[key]
144
+
145
+ processAudioFile(file) {
146
+ return new Promise((resolve, reject) => {
147
+ const reader = new FileReader();
148
+ reader.onload = (e) => {
149
+ try {
150
+ const result = e.target.result;
151
+ const commaIndex = result.indexOf(',');
152
+ if (commaIndex === -1) {
153
+ throw new Error('Invalid data URL format: missing comma separator');
154
+ }
155
+ const base64Data = result.split(',')[1]; // Remove data URL prefix
156
+
157
+ resolve({
158
+ name: file.name,
159
+ type: file.type,
160
+ size: file.size,
161
+ base64Data: base64Data,
162
+ });
163
+ } catch (error) {
164
+ reject(error);
207
165
  }
208
- })
209
- }
166
+ };
167
+ reader.onerror = () => reject(new Error('Failed to read audio'));
168
+ reader.readAsDataURL(file);
169
+ });
210
170
  },
211
-
212
- addMessage(content, role = 'ai', isHtml = false, files = []) {
213
- try {
214
- if (this.$refs.deepChat) {
215
- const message = {
216
- text: content,
217
- role: role
218
- }
219
-
220
- if (isHtml) {
221
- message.html = content
222
- delete message.text
223
- }
224
-
225
- if (files && files.length > 0) {
226
- message.files = files
171
+
172
+ processDocumentFile(file) {
173
+ return new Promise((resolve, reject) => {
174
+ const reader = new FileReader();
175
+ reader.onload = (e) => {
176
+ try {
177
+ const base64Data = e.target.result.split(',')[1]; // Remove data URL prefix
178
+
179
+ resolve({
180
+ name: file.name,
181
+ type: file.type,
182
+ size: file.size,
183
+ fileData: base64Data, // Use fileData for documents
184
+ });
185
+ } catch (error) {
186
+ reject(error);
227
187
  }
228
-
229
- // Nachricht zu Deep Chat hinzufügen
230
- this.$refs.deepChat.addMessage(message)
231
- this.messages.push(message)
232
- }
233
- } catch (error) {
234
- console.error('Error adding message to Deep Chat:', error)
235
- }
188
+ };
189
+ reader.onerror = () => reject(new Error('Failed to read document'));
190
+ reader.readAsDataURL(file);
191
+ });
236
192
  },
237
-
238
- clearMessages() {
239
- try {
240
- if (this.$refs.deepChat) {
241
- this.$refs.deepChat.clearMessages()
242
- this.messages = []
243
- }
244
- } catch (error) {
245
- console.error('Error clearing Deep Chat messages:', error)
193
+
194
+ formatForChatGPT(conversation, textOnly = false) {
195
+ const payload = {
196
+ messages: conversation.map((msg) => this.formatMessage(msg, textOnly)),
197
+ model: this.props.model,
198
+ };
199
+
200
+ // Add ChatGPT API extensions
201
+ if (this.props.tools) {
202
+ payload.tools = this.props.tools;
246
203
  }
204
+
205
+ if (this.props.tool_choice) {
206
+ payload.tool_choice = this.props.tool_choice;
207
+ }
208
+
209
+ if (this.props.temperature !== undefined) {
210
+ payload.temperature = this.props.temperature;
211
+ }
212
+
213
+ if (this.props.max_tokens) {
214
+ payload.max_tokens = this.props.max_tokens;
215
+ }
216
+
217
+ if (this.props.top_p !== undefined) {
218
+ payload.top_p = this.props.top_p;
219
+ }
220
+
221
+ if (this.props.frequency_penalty !== undefined) {
222
+ payload.frequency_penalty = this.props.frequency_penalty;
223
+ }
224
+
225
+ if (this.props.presence_penalty !== undefined) {
226
+ payload.presence_penalty = this.props.presence_penalty;
227
+ }
228
+
229
+ if (this.props.response_format) {
230
+ payload.response_format = this.props.response_format;
231
+ }
232
+
233
+ return payload;
247
234
  },
248
-
249
- handleCustomConnection(body, signals) {
250
- // Node-RED Integration: Nachricht an Node-RED senden
251
- this.$socket.emit('chat-message', this.id, {
252
- message: body.messages || body,
253
- timestamp: new Date().toISOString(),
254
- files: body.files || []
255
- })
256
-
257
- // Temporary loading message
258
- signals.onOpen({
259
- text: 'Processing...',
260
- role: 'ai'
261
- })
262
- },
263
-
264
- handleNewMessage(event) {
265
- // Event an Node-RED weiterleiten
266
- this.$socket.emit('deepchat-onNewMessage', this.id, {
267
- message: event.message,
268
- role: event.role || 'user',
269
- timestamp: new Date().toISOString()
270
- })
235
+
236
+ formatMessage(msg, textOnly = false) {
237
+ const message = {
238
+ role: msg.role === 'ai' ? 'assistant' : 'user',
239
+ };
240
+
241
+ // Handle tool calls (for assistant messages)
242
+ if (msg.tool_calls) {
243
+ message.tool_calls = msg.tool_calls;
244
+ message.content = msg.content || null;
245
+ return message;
246
+ }
247
+
248
+ // Handle tool responses (for tool messages)
249
+ if (msg.role === 'tool') {
250
+ message.role = 'tool';
251
+ message.content = msg.content;
252
+ message.tool_call_id = msg.tool_call_id;
253
+ return message;
254
+ }
255
+
256
+ // Handle messages with files
257
+ if (msg.files && msg.files.length > 0) {
258
+ message.content = this.buildContentArray(msg, textOnly);
259
+ return message;
260
+ }
261
+
262
+ // Handle text-only messages
263
+ message.content = msg.text || msg.content || '';
264
+ return message;
271
265
  },
272
-
273
- handleClearMessages() {
274
- // Event an Node-RED weiterleiten
275
- this.$socket.emit('deepchat-onClearMessages', this.id, {
276
- timestamp: new Date().toISOString()
277
- })
266
+
267
+ buildContentArray(msg, textOnly) {
268
+ const content = [{ type: 'text', text: msg.text || '' }];
269
+
270
+ msg.files.forEach((file) => {
271
+ if (file.type && file.type.startsWith('image/') && file.src) {
272
+ content.push({
273
+ type: 'image_url',
274
+ image_url: { url: file.src },
275
+ });
276
+ } else if (!textOnly && file.type && file.type.startsWith('audio/') && file.base64Data) {
277
+ const format = this.getAudioFormat(file.type);
278
+ content.push({
279
+ type: 'input_audio',
280
+ input_audio: {
281
+ data: file.base64Data,
282
+ format: format,
283
+ },
284
+ });
285
+ } else if (!textOnly && file.fileData) {
286
+ content.push({
287
+ type: 'file',
288
+ file: {
289
+ filename: file.name,
290
+ file_data: file.fileData,
291
+ },
292
+ });
293
+ }
294
+ });
295
+
296
+ return content;
278
297
  },
279
-
280
- handleComponentRender(event) {
281
- // Event an Node-RED weiterleiten
282
- this.$socket.emit('deepchat-onComponentRender', this.id, {
283
- event: event,
284
- timestamp: new Date().toISOString()
285
- })
298
+
299
+ getAudioFormat(mimeType) {
300
+ if (mimeType.includes('mp3')) return 'mp3';
301
+ if (mimeType.includes('wav')) return 'wav';
302
+ if (mimeType.includes('webm')) return 'webm';
303
+ if (mimeType.includes('m4a')) return 'm4a';
304
+ return 'wav';
286
305
  },
287
-
288
- handleResponse(event) {
289
- console.log('Deep Chat Response:', event)
306
+
307
+ sendToNodeRED(payload, signals, fallbackMessage = null) {
308
+ this.$socket.emit('widget-action', this.id, { payload });
309
+
310
+ this.$socket.once('msg-input:' + this.id, (msg) => {
311
+ this.handleNodeREDResponse(msg, signals);
312
+ });
290
313
  },
291
-
292
- handleError(error) {
293
- console.error('Deep Chat Error:', error)
294
-
295
- // Fehler an Node-RED melden
296
- this.$socket.emit('chat-message', this.id, {
297
- error: error.message || error,
298
- timestamp: new Date().toISOString()
299
- })
300
- }
301
- }
302
- }
303
- </script>
304
314
 
305
- <style scoped>
306
- .ui-deepchat-container {
307
- position: relative;
308
- border-radius: 8px;
309
- overflow: hidden;
310
- background: var(--v-theme-surface);
311
- }
315
+ handleNodeREDResponse(msg, signals) {
316
+ try {
317
+ // Store the complete ChatGPT response in conversation
318
+ const fullResponse = msg.payload;
312
319
 
313
- /* Deep Chat Styling Integration */
314
- .ui-deepchat-container :deep(deep-chat) {
315
- width: 100% !important;
316
- height: 100% !important;
317
- border: none !important;
318
- border-radius: 8px;
319
- font-family: var(--v-font-family) !important;
320
- }
320
+ // Create AI message with complete response data
321
+ const aiMessage = {
322
+ role: 'ai',
323
+ content: fullResponse.message?.content || fullResponse.content,
324
+ text: fullResponse.message?.content || fullResponse.content,
325
+ fullResponse: fullResponse,
326
+ };
321
327
 
322
- /* Dark theme support */
323
- .ui-deepchat-container :deep(deep-chat) {
324
- --chat-background-color: var(--v-theme-surface);
325
- --user-message-color: var(--v-theme-primary);
326
- --ai-message-color: var(--v-theme-secondary);
327
- --border-color: var(--v-theme-outline);
328
- --text-color: var(--v-theme-on-surface);
329
- --placeholder-color: var(--v-theme-on-surface-variant);
330
- }
328
+ // Handle tool calls if present
329
+ if (fullResponse.message?.tool_calls) {
330
+ aiMessage.tool_calls = fullResponse.message.tool_calls;
331
+ aiMessage.content = fullResponse.message.content || null;
332
+ }
331
333
 
332
- /* Input area styling */
333
- .ui-deepchat-container :deep(.text-input-container) {
334
- border-top: 1px solid var(--v-theme-outline) !important;
335
- background: var(--v-theme-surface) !important;
336
- }
334
+ this.conversation.push(aiMessage);
337
335
 
338
- /* Message bubbles */
339
- .ui-deepchat-container :deep(.message-bubble) {
340
- border-radius: 12px !important;
341
- }
336
+ // For the chat display, show appropriate response
337
+ if (fullResponse.message?.tool_calls) {
338
+ // If ChatGPT wants to call tools, show a message
339
+ signals.onResponse({
340
+ text: 'Function call requested...',
341
+ role: 'ai',
342
+ });
343
+ } else if (fullResponse.message?.content || fullResponse.content) {
344
+ // Normal text response
345
+ signals.onResponse({
346
+ text: fullResponse.message?.content || fullResponse.content,
347
+ role: 'ai',
348
+ });
349
+ }
350
+ } catch (error) {
351
+ console.error('Error handling response:', error);
352
+ this.sendErrorResponse(signals, 'Error processing response');
353
+ }
354
+ },
342
355
 
343
- /* Responsive design */
344
- @media (max-width: 768px) {
345
- .ui-deepchat-container {
346
- border-radius: 0;
347
- }
356
+ sendErrorResponse(signals, message) {
357
+ if (signals && signals.onResponse) {
358
+ signals.onResponse({
359
+ text: message,
360
+ role: 'ai',
361
+ });
362
+ }
363
+ },
364
+ },
365
+ };
366
+ </script>
367
+
368
+ <style scoped>
369
+ .deep-chat-container {
370
+ display: flex;
371
+ justify-content: center;
372
+ width: 100%;
348
373
  }
349
- </style>
374
+ </style>