@5minds/node-red-dashboard-2-processcube-chat 0.1.1-add-functionality-043ee8-mdy8o5ki → 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,369 +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 base64Data = e.target.result.split(',')[1]; // Remove data URL prefix
151
-
152
- resolve({
153
- name: file.name,
154
- type: file.type,
155
- size: file.size,
156
- base64Data: base64Data,
157
- });
158
- } catch (error) {
159
- reject(error);
160
- }
161
- };
162
- reader.onerror = () => reject(new Error('Failed to read audio'));
163
- reader.readAsDataURL(file);
164
- });
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)
165
199
  },
166
-
167
- processDocumentFile(file) {
168
- return new Promise((resolve, reject) => {
169
- const reader = new FileReader();
170
- reader.onload = (e) => {
171
- try {
172
- const base64Data = e.target.result.split(',')[1]; // Remove data URL prefix
173
-
174
- resolve({
175
- name: file.name,
176
- type: file.type,
177
- size: file.size,
178
- fileData: base64Data, // Use fileData for documents
179
- });
180
- } catch (error) {
181
- 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]
182
207
  }
183
- };
184
- reader.onerror = () => reject(new Error('Failed to read document'));
185
- reader.readAsDataURL(file);
186
- });
187
- },
188
-
189
- formatForChatGPT(conversation, textOnly = false) {
190
- const payload = {
191
- messages: conversation.map((msg) => this.formatMessage(msg, textOnly)),
192
- model: this.props.model,
193
- };
194
-
195
- // Add ChatGPT API extensions
196
- if (this.props.tools) {
197
- payload.tools = this.props.tools;
198
- }
199
-
200
- if (this.props.tool_choice) {
201
- payload.tool_choice = this.props.tool_choice;
202
- }
203
-
204
- if (this.props.temperature !== undefined) {
205
- payload.temperature = this.props.temperature;
206
- }
207
-
208
- if (this.props.max_tokens) {
209
- payload.max_tokens = this.props.max_tokens;
210
- }
211
-
212
- if (this.props.top_p !== undefined) {
213
- payload.top_p = this.props.top_p;
214
- }
215
-
216
- if (this.props.frequency_penalty !== undefined) {
217
- payload.frequency_penalty = this.props.frequency_penalty;
208
+ })
218
209
  }
219
-
220
- if (this.props.presence_penalty !== undefined) {
221
- payload.presence_penalty = this.props.presence_penalty;
222
- }
223
-
224
- if (this.props.response_format) {
225
- payload.response_format = this.props.response_format;
226
- }
227
-
228
- return payload;
229
210
  },
230
-
231
- formatMessage(msg, textOnly = false) {
232
- const message = {
233
- role: msg.role === 'ai' ? 'assistant' : 'user',
234
- };
235
-
236
- // Handle tool calls (for assistant messages)
237
- if (msg.tool_calls) {
238
- message.tool_calls = msg.tool_calls;
239
- message.content = msg.content || null;
240
- return message;
241
- }
242
-
243
- // Handle tool responses (for tool messages)
244
- if (msg.role === 'tool') {
245
- message.role = 'tool';
246
- message.content = msg.content;
247
- message.tool_call_id = msg.tool_call_id;
248
- return message;
249
- }
250
-
251
- // Handle messages with files
252
- if (msg.files && msg.files.length > 0) {
253
- message.content = this.buildContentArray(msg, textOnly);
254
- 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)
255
235
  }
256
-
257
- // Handle text-only messages
258
- message.content = msg.text || msg.content || '';
259
- return message;
260
236
  },
261
-
262
- buildContentArray(msg, textOnly) {
263
- const content = [{ type: 'text', text: msg.text || '' }];
264
-
265
- msg.files.forEach((file) => {
266
- if (file.type && file.type.startsWith('image/') && file.src) {
267
- content.push({
268
- type: 'image_url',
269
- image_url: { url: file.src },
270
- });
271
- } else if (!textOnly && file.type && file.type.startsWith('audio/') && file.base64Data) {
272
- const format = this.getAudioFormat(file.type);
273
- content.push({
274
- type: 'input_audio',
275
- input_audio: {
276
- data: file.base64Data,
277
- format: format,
278
- },
279
- });
280
- } else if (!textOnly && file.fileData) {
281
- content.push({
282
- type: 'file',
283
- file: {
284
- filename: file.name,
285
- file_data: file.fileData,
286
- },
287
- });
237
+
238
+ clearMessages() {
239
+ try {
240
+ if (this.$refs.deepChat) {
241
+ this.$refs.deepChat.clearMessages()
242
+ this.messages = []
288
243
  }
289
- });
290
-
291
- return content;
244
+ } catch (error) {
245
+ console.error('Error clearing Deep Chat messages:', error)
246
+ }
292
247
  },
293
-
294
- getAudioFormat(mimeType) {
295
- if (mimeType.includes('mp3')) return 'mp3';
296
- if (mimeType.includes('wav')) return 'wav';
297
- if (mimeType.includes('webm')) return 'webm';
298
- if (mimeType.includes('m4a')) return 'm4a';
299
- 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
+ })
300
262
  },
301
-
302
- sendToNodeRED(payload, signals, fallbackMessage = null) {
303
- this.$socket.emit('widget-action', this.id, { payload });
304
-
305
- this.$socket.once('msg-input:' + this.id, (msg) => {
306
- this.handleNodeREDResponse(msg, signals);
307
- });
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
+ })
308
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>
309
304
 
310
- handleNodeREDResponse(msg, signals) {
311
- try {
312
- // Store the complete ChatGPT response in conversation
313
- const fullResponse = msg.payload;
314
-
315
- // Create AI message with complete response data
316
- const aiMessage = {
317
- role: 'ai',
318
- content: fullResponse.message?.content || fullResponse.content,
319
- text: fullResponse.message?.content || fullResponse.content,
320
- fullResponse: fullResponse,
321
- };
305
+ <style scoped>
306
+ .ui-deepchat-container {
307
+ position: relative;
308
+ border-radius: 8px;
309
+ overflow: hidden;
310
+ background: var(--v-theme-surface);
311
+ }
322
312
 
323
- // Handle tool calls if present
324
- if (fullResponse.message?.tool_calls) {
325
- aiMessage.tool_calls = fullResponse.message.tool_calls;
326
- aiMessage.content = fullResponse.message.content || null;
327
- }
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
+ }
328
321
 
329
- 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
+ }
330
331
 
331
- // For the chat display, show appropriate response
332
- if (fullResponse.message?.tool_calls) {
333
- // If ChatGPT wants to call tools, show a message
334
- signals.onResponse({
335
- text: 'Function call requested...',
336
- role: 'ai',
337
- });
338
- } else if (fullResponse.message?.content || fullResponse.content) {
339
- // Normal text response
340
- signals.onResponse({
341
- text: fullResponse.message?.content || fullResponse.content,
342
- role: 'ai',
343
- });
344
- }
345
- } catch (error) {
346
- console.error('Error handling response:', error);
347
- this.sendErrorResponse(signals, 'Error processing response');
348
- }
349
- },
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
+ }
350
337
 
351
- sendErrorResponse(signals, message) {
352
- if (signals && signals.onResponse) {
353
- signals.onResponse({
354
- text: message,
355
- role: 'ai',
356
- });
357
- }
358
- },
359
- },
360
- };
361
- </script>
338
+ /* Message bubbles */
339
+ .ui-deepchat-container :deep(.message-bubble) {
340
+ border-radius: 12px !important;
341
+ }
362
342
 
363
- <style scoped>
364
- .deep-chat-container {
365
- display: flex;
366
- justify-content: center;
367
- width: 100%;
343
+ /* Responsive design */
344
+ @media (max-width: 768px) {
345
+ .ui-deepchat-container {
346
+ border-radius: 0;
347
+ }
368
348
  }
369
- </style>
349
+ </style>