@5minds/node-red-dashboard-2-processcube-chat 0.1.1-add-functionality-94845a-me8hect9 → 0.1.1-develop-e94793-mcvyrm7g

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,23 +1,30 @@
1
1
  <template>
2
- <div class="deep-chat-container">
3
- <deep-chat
2
+ <div ref="deepChatContainer" class="ui-deepchat-container" :style="containerStyle">
3
+ <deep-chat width="100%"
4
+ ref="deepChat"
4
5
  :style="deepChatStyle"
5
- :textInput="textInputConfig"
6
- :introMessage="introMessageConfig"
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"
7
16
  :connect="connectConfig"
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"
17
+ @onNewMessage="handleNewMessage"
18
+ @onClearMessages="handleClearMessages"
19
+ @onComponentRender="handleComponentRender"
20
+ @onResponse="handleResponse"
21
+ @onError="handleError"
15
22
  ></deep-chat>
16
23
  </div>
17
24
  </template>
18
25
 
19
26
  <script>
20
- import 'deep-chat';
27
+ import 'deep-chat'
21
28
 
22
29
  export default {
23
30
  name: 'UIDeepChat',
@@ -25,350 +32,311 @@ export default {
25
32
  inject: ['$socket'],
26
33
  data() {
27
34
  return {
28
- conversation: [],
29
- };
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
+ }
30
53
  },
31
-
54
+
32
55
  computed: {
33
- deepChatStyle() {
56
+ containerStyle() {
34
57
  return {
35
58
  width: '100%',
36
- maxWidth: '600px',
37
- height: '80vh',
38
- borderRadius: '8px',
39
- };
59
+ height: '100%',
60
+ minHeight: '400px'
61
+ }
40
62
  },
41
63
 
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
+
42
101
  textInputConfig() {
102
+ if (!this.config.textInput) return false
103
+
43
104
  return {
44
105
  placeholder: {
45
- text: this.props.placeholder || 'Type a message...',
46
- },
47
- };
106
+ text: this.config.placeholder
107
+ }
108
+ }
48
109
  },
49
-
50
- introMessageConfig() {
51
- return {
52
- text: this.props.introMessage || 'Hello! How can I help you today?',
53
- };
110
+
111
+ speechToTextConfig() {
112
+ return this.config.speechToText ? {
113
+ button: true,
114
+ displayInterimResults: true
115
+ } : false
54
116
  },
55
-
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
136
+ },
137
+
56
138
  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
57
157
  return {
58
- handler: this.handleConnection,
59
- };
60
- },
158
+ handler: this.handleCustomConnection,
159
+ stream: this.config.stream
160
+ }
161
+ }
61
162
  },
163
+
164
+ mounted() {
165
+ this.setupSocketListeners()
166
+ this.applyConfiguration()
167
+ },
168
+
62
169
  beforeUnmount() {
63
- this.$socket.off('msg-input:' + this.id);
170
+ this.removeSocketListeners()
64
171
  },
65
-
172
+
66
173
  methods: {
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
- }
85
- }
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;
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)
94
195
  }
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.');
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]
219
+ }
220
+ })
105
221
  }
106
222
  },
107
-
108
- async processFiles(files) {
223
+
224
+ clearMessages() {
109
225
  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
- );
226
+ if (this.$refs.deepChat) {
227
+ this.$refs.deepChat.clearMessages()
228
+ this.messages = []
229
+ }
124
230
  } catch (error) {
125
- console.error('Error processing files:', error);
126
- return [];
231
+ console.error('Error clearing Deep Chat messages:', error)
127
232
  }
128
233
  },
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
- });
143
- },
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;
234
+
235
+ updateConfiguration() {
236
+ if (this.deepChatInstance) {
237
+ // Bestehende Instanz aktualisieren
238
+ this.applyConfigToElement(this.deepChatInstance)
227
239
  }
228
-
229
- if (this.props.response_format) {
230
- payload.response_format = this.props.response_format;
231
- }
232
-
233
- return payload;
234
240
  },
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;
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
+ })
265
255
  },
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;
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
+ })
297
264
  },
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';
265
+
266
+ handleClearMessages() {
267
+ // Event an Node-RED weiterleiten
268
+ this.$socket.emit('deepchat-onClearMessages', this.id, {
269
+ timestamp: new Date().toISOString()
270
+ })
305
271
  },
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
- });
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
+ })
313
279
  },
280
+
281
+ handleResponse(event) {
282
+ console.log('Deep Chat Response:', event)
283
+ },
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>
314
297
 
315
- handleNodeREDResponse(msg, signals) {
316
- try {
317
- // Store the complete ChatGPT response in conversation
318
- const fullResponse = msg.payload;
319
-
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
- };
298
+ <style scoped>
299
+ .ui-deepchat-container {
300
+ position: relative;
301
+ border-radius: 8px;
302
+ overflow: hidden;
303
+ background: var(--v-theme-surface);
304
+ }
327
305
 
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
- }
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
+ }
333
314
 
334
- this.conversation.push(aiMessage);
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
+ }
335
324
 
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
- },
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
+ }
355
330
 
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>
331
+ /* Message bubbles */
332
+ .ui-deepchat-container :deep(.message-bubble) {
333
+ border-radius: 12px !important;
334
+ }
367
335
 
368
- <style scoped>
369
- .deep-chat-container {
370
- display: flex;
371
- justify-content: center;
372
- width: 100%;
336
+ /* Responsive design */
337
+ @media (max-width: 768px) {
338
+ .ui-deepchat-container {
339
+ border-radius: 0;
340
+ }
373
341
  }
374
- </style>
342
+ </style>