@5minds/node-red-dashboard-2-processcube-chat 0.1.1-develop-e94793-mcvyrm7g → 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,30 +1,23 @@
1
1
  <template>
2
- <div ref="deepChatContainer" class="ui-deepchat-container" :style="containerStyle">
3
- <deep-chat width="100%"
4
- ref="deepChat"
2
+ <div class="deep-chat-container">
3
+ <deep-chat
5
4
  :style="deepChatStyle"
6
- :intro-message="config.introMessage"
7
- :text-input="textInputConfig"
8
- :speech-to-text="speechToTextConfig"
9
- :camera="cameraConfig"
10
- :microphone="microphoneConfig"
11
- :mixed-files="attachmentsConfig"
12
- :avatars="config.avatars"
13
- :names="config.names"
14
- :timestamps="config.timestamps"
15
- :stream="config.stream"
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>
27
- import 'deep-chat'
20
+ import 'deep-chat';
28
21
 
29
22
  export default {
30
23
  name: 'UIDeepChat',
@@ -32,311 +25,350 @@ export default {
32
25
  inject: ['$socket'],
33
26
  data() {
34
27
  return {
35
- config: {
36
- introMessage: 'Hello! How can I help you today?',
37
- placeholder: 'Type a message...',
38
- apiUrl: '',
39
- apiKey: '',
40
- model: 'gpt-3.5-turbo',
41
- textInput: true,
42
- speechToText: false,
43
- camera: false,
44
- microphone: false,
45
- attachments: false,
46
- avatars: true,
47
- names: true,
48
- timestamps: false,
49
- stream: false
50
- },
51
- messages: []
52
- }
28
+ conversation: [],
29
+ };
53
30
  },
54
-
31
+
55
32
  computed: {
56
- containerStyle() {
33
+ deepChatStyle() {
57
34
  return {
58
35
  width: '100%',
59
- height: '100%',
60
- minHeight: '400px'
61
- }
36
+ maxWidth: '600px',
37
+ height: '80vh',
38
+ borderRadius: '8px',
39
+ };
62
40
  },
63
41
 
64
- deepChatStyle() {
65
- return {
66
- width: '300px',
67
- height: '300px',
68
- flex: '1',
69
- border: 'none',
70
- borderRadius: '8px'
71
- }
72
- },
73
-
74
- speechToTextConfig() {
75
- return this.config.speechToText ? {
76
- button: true,
77
- displayInterimResults: true
78
- } : false
79
- },
80
-
81
- cameraConfig() {
82
- return this.config.camera ? {
83
- button: true
84
- } : false
85
- },
86
-
87
- microphoneConfig() {
88
- return this.config.microphone ? {
89
- button: true,
90
- audio: true
91
- } : false
92
- },
93
-
94
- attachmentsConfig() {
95
- return this.config.attachments ? {
96
- button: true,
97
- acceptedFormats: ".jpeg,.jpg,.png,.gif,.pdf,.txt,.doc,.docx"
98
- } : false
99
- },
100
-
101
42
  textInputConfig() {
102
- if (!this.config.textInput) return false
103
-
104
43
  return {
105
44
  placeholder: {
106
- text: this.config.placeholder
107
- }
108
- }
45
+ text: this.props.placeholder || 'Type a message...',
46
+ },
47
+ };
109
48
  },
110
-
111
- speechToTextConfig() {
112
- return this.config.speechToText ? {
113
- button: true,
114
- displayInterimResults: true
115
- } : false
116
- },
117
-
118
- cameraConfig() {
119
- return this.config.camera ? {
120
- button: true
121
- } : false
122
- },
123
-
124
- microphoneConfig() {
125
- return this.config.microphone ? {
126
- button: true,
127
- audio: true
128
- } : false
129
- },
130
-
131
- attachmentsConfig() {
132
- return this.config.attachments ? {
133
- button: true,
134
- acceptedFormats: ".jpeg,.jpg,.png,.gif,.pdf,.txt,.doc,.docx"
135
- } : false
49
+
50
+ introMessageConfig() {
51
+ return {
52
+ text: this.props.introMessage || 'Hello! How can I help you today?',
53
+ };
136
54
  },
137
-
55
+
138
56
  connectConfig() {
139
- if (this.config.apiUrl && this.config.apiKey) {
140
- return {
141
- url: this.config.apiUrl,
142
- method: 'POST',
143
- headers: {
144
- 'Authorization': `Bearer ${this.config.apiKey}`,
145
- 'Content-Type': 'application/json'
146
- },
147
- body: {
148
- model: this.config.model,
149
- max_tokens: 2000,
150
- temperature: 0.7
151
- },
152
- stream: this.config.stream
153
- }
154
- }
155
-
156
- // Custom Handler für Node-RED Integration
157
57
  return {
158
- handler: this.handleCustomConnection,
159
- stream: this.config.stream
160
- }
161
- }
162
- },
163
-
164
- mounted() {
165
- this.setupSocketListeners()
166
- this.applyConfiguration()
58
+ handler: this.handleConnection,
59
+ };
60
+ },
167
61
  },
168
-
169
62
  beforeUnmount() {
170
- this.removeSocketListeners()
63
+ this.$socket.off('msg-input:' + this.id);
171
64
  },
172
-
65
+
173
66
  methods: {
174
- setupSocketListeners() {
175
- // Neue Nachricht von Node-RED empfangen
176
- this.$socket.on('deepchat-newMessage:' + this.id, (data) => {
177
- this.addMessage(data.message, data.role || 'ai', data.html, data.files)
178
- })
179
-
180
- // Konfiguration aktualisieren
181
- this.$socket.on('deepchat-updateConfig:' + this.id, (newConfig) => {
182
- this.config = { ...this.config, ...newConfig }
183
- this.updateConfiguration()
184
- })
185
-
186
- // Chat löschen
187
- this.$socket.on('deepchat-clearMessages:' + this.id, () => {
188
- this.clearMessages()
189
- })
190
-
191
- // Standard Dashboard Message Input
192
- this.$socket.on('msg-input:' + this.id, (msg) => {
193
- if (msg.payload) {
194
- this.addMessage(msg.payload, msg.role || 'ai', msg.html, msg.files)
195
- }
196
- if (msg.config) {
197
- this.config = { ...this.config, ...msg.config }
198
- this.updateConfiguration()
199
- }
200
- if (msg.clear) {
201
- this.clearMessages()
202
- }
203
- })
204
- },
205
-
206
- removeSocketListeners() {
207
- this.$socket.off('deepchat-newMessage:' + this.id)
208
- this.$socket.off('deepchat-updateConfig:' + this.id)
209
- this.$socket.off('deepchat-clearMessages:' + this.id)
210
- this.$socket.off('msg-input:' + this.id)
211
- },
212
-
213
- applyConfiguration() {
214
- // Konfiguration aus props übernehmen
215
- if (this.props) {
216
- Object.keys(this.props).forEach(key => {
217
- if (this.config.hasOwnProperty(key)) {
218
- this.config[key] = this.props[key]
67
+ async handleConnection(body, signals) {
68
+ try {
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
+ }
219
85
  }
220
- })
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;
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);
102
+ } catch (error) {
103
+ console.error('Error in handleConnection:', error);
104
+ this.sendErrorResponse(signals, 'Sorry, there was an error processing your message.');
221
105
  }
222
106
  },
223
-
224
- clearMessages() {
107
+
108
+ async processFiles(files) {
225
109
  try {
226
- if (this.$refs.deepChat) {
227
- this.$refs.deepChat.clearMessages()
228
- this.messages = []
229
- }
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
+ );
230
124
  } catch (error) {
231
- console.error('Error clearing Deep Chat messages:', error)
125
+ console.error('Error processing files:', error);
126
+ return [];
232
127
  }
233
128
  },
234
-
235
- updateConfiguration() {
236
- if (this.deepChatInstance) {
237
- // Bestehende Instanz aktualisieren
238
- this.applyConfigToElement(this.deepChatInstance)
239
- }
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
+ });
240
143
  },
241
-
242
- handleCustomConnection(body, signals) {
243
- // Node-RED Integration: Nachricht an Node-RED senden
244
- this.$socket.emit('chat-message', this.id, {
245
- message: body.messages || body,
246
- timestamp: new Date().toISOString(),
247
- files: body.files || []
248
- })
249
-
250
- // Temporary loading message
251
- signals.onOpen({
252
- text: 'Processing...',
253
- role: 'ai'
254
- })
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);
165
+ }
166
+ };
167
+ reader.onerror = () => reject(new Error('Failed to read audio'));
168
+ reader.readAsDataURL(file);
169
+ });
170
+ },
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);
187
+ }
188
+ };
189
+ reader.onerror = () => reject(new Error('Failed to read document'));
190
+ reader.readAsDataURL(file);
191
+ });
192
+ },
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;
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;
255
234
  },
256
-
257
- handleNewMessage(event) {
258
- // Event an Node-RED weiterleiten
259
- this.$socket.emit('deepchat-onNewMessage', this.id, {
260
- message: event.message,
261
- role: event.role || 'user',
262
- timestamp: new Date().toISOString()
263
- })
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;
264
265
  },
265
-
266
- handleClearMessages() {
267
- // Event an Node-RED weiterleiten
268
- this.$socket.emit('deepchat-onClearMessages', this.id, {
269
- timestamp: new Date().toISOString()
270
- })
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;
271
297
  },
272
-
273
- handleComponentRender(event) {
274
- // Event an Node-RED weiterleiten
275
- this.$socket.emit('deepchat-onComponentRender', this.id, {
276
- event: event,
277
- timestamp: new Date().toISOString()
278
- })
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';
279
305
  },
280
-
281
- handleResponse(event) {
282
- 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
+ });
283
313
  },
284
-
285
- handleError(error) {
286
- console.error('Deep Chat Error:', error)
287
-
288
- // Fehler an Node-RED melden
289
- this.$socket.emit('chat-message', this.id, {
290
- error: error.message || error,
291
- timestamp: new Date().toISOString()
292
- })
293
- }
294
- }
295
- }
296
- </script>
297
314
 
298
- <style scoped>
299
- .ui-deepchat-container {
300
- position: relative;
301
- border-radius: 8px;
302
- overflow: hidden;
303
- background: var(--v-theme-surface);
304
- }
315
+ handleNodeREDResponse(msg, signals) {
316
+ try {
317
+ // Store the complete ChatGPT response in conversation
318
+ const fullResponse = msg.payload;
305
319
 
306
- /* Deep Chat Styling Integration */
307
- .ui-deepchat-container :deep(deep-chat) {
308
- width: 100% !important;
309
- height: 100% !important;
310
- border: none !important;
311
- border-radius: 8px;
312
- font-family: var(--v-font-family) !important;
313
- }
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
+ };
314
327
 
315
- /* Dark theme support */
316
- .ui-deepchat-container :deep(deep-chat) {
317
- --chat-background-color: var(--v-theme-surface);
318
- --user-message-color: var(--v-theme-primary);
319
- --ai-message-color: var(--v-theme-secondary);
320
- --border-color: var(--v-theme-outline);
321
- --text-color: var(--v-theme-on-surface);
322
- --placeholder-color: var(--v-theme-on-surface-variant);
323
- }
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
+ }
324
333
 
325
- /* Input area styling */
326
- .ui-deepchat-container :deep(.text-input-container) {
327
- border-top: 1px solid var(--v-theme-outline) !important;
328
- background: var(--v-theme-surface) !important;
329
- }
334
+ this.conversation.push(aiMessage);
330
335
 
331
- /* Message bubbles */
332
- .ui-deepchat-container :deep(.message-bubble) {
333
- border-radius: 12px !important;
334
- }
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
+ },
335
355
 
336
- /* Responsive design */
337
- @media (max-width: 768px) {
338
- .ui-deepchat-container {
339
- border-radius: 0;
340
- }
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%;
341
373
  }
342
- </style>
374
+ </style>