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

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,374 +1,349 @@
1
1
  <template>
2
- <div class="deep-chat-container">
2
+ <div ref="deepChatContainer" class="ui-deepchat-container" :style="containerStyle">
3
3
  <deep-chat
4
- :style="deepChatStyle"
5
- :textInput="textInputConfig"
6
- :introMessage="introMessageConfig"
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"
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';
21
-
22
27
  export default {
23
28
  name: 'UIDeepChat',
24
29
  props: ['id', 'props', 'state'],
25
- inject: ['$socket'],
30
+
26
31
  data() {
27
32
  return {
28
- conversation: [],
29
- };
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
+ }
30
52
  },
31
-
53
+
32
54
  computed: {
33
- deepChatStyle() {
55
+ containerStyle() {
34
56
  return {
35
57
  width: '100%',
36
- maxWidth: '600px',
37
- height: '80vh',
38
- borderRadius: '8px',
39
- };
58
+ height: '100%',
59
+ minHeight: '400px'
60
+ }
40
61
  },
41
-
42
- textInputConfig() {
43
- return {
44
- placeholder: {
45
- text: this.props.placeholder || 'Type a message...',
46
- },
47
- };
62
+
63
+ speechToTextConfig() {
64
+ return this.config.speechToText ? {
65
+ button: true,
66
+ displayInterimResults: true
67
+ } : false
48
68
  },
49
-
50
- introMessageConfig() {
51
- return {
52
- text: this.props.introMessage || 'Hello! How can I help you today?',
53
- };
69
+
70
+ cameraConfig() {
71
+ return this.config.camera ? {
72
+ button: true
73
+ } : false
54
74
  },
55
-
75
+
76
+ microphoneConfig() {
77
+ return this.config.microphone ? {
78
+ button: true,
79
+ audio: true
80
+ } : false
81
+ },
82
+
83
+ attachmentsConfig() {
84
+ return this.config.attachments ? {
85
+ button: true,
86
+ acceptedFormats: ".jpeg,.jpg,.png,.gif,.pdf,.txt,.doc,.docx"
87
+ } : false
88
+ },
89
+
56
90
  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
57
109
  return {
58
- handler: this.handleConnection,
59
- };
60
- },
110
+ handler: this.handleCustomConnection,
111
+ stream: this.config.stream
112
+ }
113
+ }
114
+ },
115
+
116
+ mounted() {
117
+ this.loadDeepChat()
118
+ this.setupSocketListeners()
119
+ this.applyConfiguration()
61
120
  },
121
+
62
122
  beforeUnmount() {
63
- this.$socket.off('msg-input:' + this.id);
123
+ this.removeSocketListeners()
64
124
  },
65
-
125
+
66
126
  methods: {
67
- async handleConnection(body, signals) {
127
+ async loadDeepChat() {
68
128
  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);
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
+ })
91
138
  }
92
- } else if (body.messages) {
93
- newMessages = body.messages;
139
+ document.head.appendChild(script)
140
+ } else {
141
+ this.isDeepChatLoaded = true
142
+ this.$nextTick(() => {
143
+ this.initializeDeepChat()
144
+ })
94
145
  }
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
146
  } catch (error) {
103
- console.error('Error in handleConnection:', error);
104
- this.sendErrorResponse(signals, 'Sorry, there was an error processing your message.');
147
+ console.error('Error loading Deep Chat:', error)
105
148
  }
106
149
  },
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 [];
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
+ }
127
160
  }
128
161
  },
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
- });
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
+ })
143
192
  },
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
- });
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)
170
199
  },
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);
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]
187
207
  }
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;
208
+ })
223
209
  }
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;
234
210
  },
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;
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
227
+ }
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)
260
235
  }
261
-
262
- // Handle text-only messages
263
- message.content = msg.text || msg.content || '';
264
- return message;
265
236
  },
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
- });
237
+
238
+ clearMessages() {
239
+ try {
240
+ if (this.$refs.deepChat) {
241
+ this.$refs.deepChat.clearMessages()
242
+ this.messages = []
293
243
  }
294
- });
295
-
296
- return content;
244
+ } catch (error) {
245
+ console.error('Error clearing Deep Chat messages:', error)
246
+ }
297
247
  },
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';
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
+ })
305
262
  },
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
- });
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
+ })
313
271
  },
272
+
273
+ handleClearMessages() {
274
+ // Event an Node-RED weiterleiten
275
+ this.$socket.emit('deepchat-onClearMessages', this.id, {
276
+ timestamp: new Date().toISOString()
277
+ })
278
+ },
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
+ })
286
+ },
287
+
288
+ handleResponse(event) {
289
+ console.log('Deep Chat Response:', event)
290
+ },
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>
314
304
 
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
- };
305
+ <style scoped>
306
+ .ui-deepchat-container {
307
+ position: relative;
308
+ border-radius: 8px;
309
+ overflow: hidden;
310
+ background: var(--v-theme-surface);
311
+ }
327
312
 
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
- }
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
+ }
333
321
 
334
- this.conversation.push(aiMessage);
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
+ }
335
331
 
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
- },
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
+ }
355
337
 
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>
338
+ /* Message bubbles */
339
+ .ui-deepchat-container :deep(.message-bubble) {
340
+ border-radius: 12px !important;
341
+ }
367
342
 
368
- <style scoped>
369
- .deep-chat-container {
370
- display: flex;
371
- justify-content: center;
372
- width: 100%;
343
+ /* Responsive design */
344
+ @media (max-width: 768px) {
345
+ .ui-deepchat-container {
346
+ border-radius: 0;
347
+ }
373
348
  }
374
- </style>
349
+ </style>