@c8y/ngx-components 1023.79.1 → 1023.80.2
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.
- package/ai/agent-chat/index.d.ts +22 -11
- package/ai/agent-chat/index.d.ts.map +1 -1
- package/ai/ai-chat/index.d.ts +30 -9
- package/ai/ai-chat/index.d.ts.map +1 -1
- package/ai/index.d.ts +64 -49
- package/ai/index.d.ts.map +1 -1
- package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs +235 -127
- package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs +104 -44
- package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-ai.mjs +92 -61
- package/fesm2022/c8y-ngx-components-ai.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-global-context.mjs +23 -3
- package/fesm2022/c8y-ngx-components-global-context.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs +31 -29
- package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs +47 -8
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs.map +1 -1
- package/global-context/index.d.ts +17 -2
- package/global-context/index.d.ts.map +1 -1
- package/locales/de.po +3 -0
- package/locales/es.po +3 -0
- package/locales/fr.po +3 -0
- package/locales/ja_JP.po +3 -0
- package/locales/ko.po +3 -0
- package/locales/locales.pot +3 -0
- package/locales/nl.po +3 -0
- package/locales/pl.po +3 -0
- package/locales/pt_BR.po +3 -0
- package/locales/zh_CN.po +3 -0
- package/locales/zh_TW.po +3 -0
- package/package.json +1 -1
- package/widgets/implementations/datapoints-list/index.d.ts +5 -0
- package/widgets/implementations/datapoints-list/index.d.ts.map +1 -1
|
@@ -111,17 +111,20 @@ class AIHtmlWidgetConfigFactory {
|
|
|
111
111
|
htmlWidgetConfigService.widgetConfigService.updateConfig({ config: newConfig });
|
|
112
112
|
}
|
|
113
113
|
preprocessAgentMessage(message, changedPart, htmlWidgetConfigService) {
|
|
114
|
-
//
|
|
115
|
-
if (!
|
|
114
|
+
// Only apply this pre-processing to text parts, since that's what the agent will use to send the code
|
|
115
|
+
if (!changedPart || changedPart.type !== 'text') {
|
|
116
116
|
return message;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
}
|
|
118
|
+
// Rewrite HTML content generated by the agent as text content as if it had come from a tool call
|
|
119
|
+
// Find last tool named this.codeToolName in message.content, or undefined
|
|
120
|
+
const codeTool = message.content.reduce((last, part) => 'toolName' in part && part.toolName === this.codeToolName
|
|
121
|
+
? part
|
|
122
|
+
: last, undefined);
|
|
120
123
|
if (codeTool && codeTool.type !== 'tool-result') {
|
|
121
124
|
// A code update tool call is in progress - accumulate text into input
|
|
122
|
-
const input = `${codeTool.input?.code || ''}${
|
|
125
|
+
const input = `${codeTool.input?.code || ''}${changedPart.text}`;
|
|
123
126
|
codeTool.input.code = input;
|
|
124
|
-
|
|
127
|
+
changedPart.text = '';
|
|
125
128
|
const closeIdx = input.indexOf(`</${this.codeTag}>`);
|
|
126
129
|
if (closeIdx !== -1) {
|
|
127
130
|
// Found closing tag - convert to result and start new step
|
|
@@ -129,40 +132,39 @@ class AIHtmlWidgetConfigFactory {
|
|
|
129
132
|
const afterClose = input.substring(closeIdx + `</${this.codeTag}>`.length);
|
|
130
133
|
codeTool.input.code = beforeClose;
|
|
131
134
|
codeTool.type = 'tool-result';
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
// Always create a new (fake) step after this (just like a real AI), since final step can't contain tool calls
|
|
136
|
+
message.content.push({ type: 'step-start' });
|
|
137
|
+
if (afterClose.length > 0) {
|
|
138
|
+
message.content.push({ type: 'text', text: afterClose });
|
|
139
|
+
}
|
|
137
140
|
// Since this is a fake tool call not a real one, the tool result callback won't trigger so do this manually
|
|
138
141
|
this.applyCurrentCode(codeTool.input.code, htmlWidgetConfigService);
|
|
139
142
|
}
|
|
140
143
|
else {
|
|
141
|
-
// Replace with a new instance so that change
|
|
142
|
-
|
|
144
|
+
// Replace with a new instance so that change detection works
|
|
145
|
+
message.content[message.content.findIndex(part => part === codeTool)] = { ...codeTool };
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
|
-
else
|
|
148
|
+
else {
|
|
146
149
|
// No in-progress code tag, so check for one
|
|
147
|
-
const openIdx =
|
|
150
|
+
const openIdx = changedPart.text.indexOf(`<${this.codeTag}>`);
|
|
148
151
|
if (openIdx !== -1) {
|
|
149
|
-
const afterOpen =
|
|
150
|
-
|
|
152
|
+
const afterOpen = changedPart.text.substring(openIdx + `<${this.codeTag}>`.length);
|
|
153
|
+
changedPart.text = changedPart.text.substring(0, openIdx);
|
|
151
154
|
// Start a new "step" to keep it separate and simple
|
|
152
|
-
message.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
toolName: this.codeToolName,
|
|
159
|
-
toolCallId: this.codeToolName + message.steps.length, // add step number just to ensure uniqueness
|
|
160
|
-
input: { code: afterOpen }
|
|
161
|
-
}
|
|
162
|
-
]
|
|
155
|
+
message.content.push({ type: 'step-start' });
|
|
156
|
+
message.content.push({
|
|
157
|
+
type: 'tool-input-streaming',
|
|
158
|
+
toolName: this.codeToolName,
|
|
159
|
+
toolCallId: this.codeToolName + message.content.length, // add part number just to ensure uniqueness
|
|
160
|
+
input: { code: afterOpen }
|
|
163
161
|
});
|
|
164
162
|
}
|
|
165
163
|
}
|
|
164
|
+
if (changedPart.text === '') {
|
|
165
|
+
// Remove any empty changedPart from content
|
|
166
|
+
message.content.splice(message.content.findIndex(part => part === changedPart), 1);
|
|
167
|
+
}
|
|
166
168
|
return message;
|
|
167
169
|
}
|
|
168
170
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AIHtmlWidgetConfigFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs","sources":["../../widgets/definitions/html-widget-ai-config/html-widget-agent.definitions.ts","../../widgets/definitions/html-widget-ai-config/ai-html-widget-config.factory.ts","../../widgets/definitions/html-widget-ai-config/index.ts","../../widgets/definitions/html-widget-ai-config/c8y-ngx-components-widgets-definitions-html-widget-ai-config.ts"],"sourcesContent":["import type { ClientAgentDefinition } from '@c8y/ngx-components/ai';\nimport { HTML_AGENT } from '@c8y/ngx-components/ai/agents/html';\nimport { gettext } from '@c8y/ngx-components/gettext';\n\ndeclare const __MODE__: 'development' | 'production' | undefined;\n\n// Treat as \"production\" when run from Jest\nconst mode = (typeof __MODE__ !== 'undefined' ? __MODE__ : 'production') as\n | 'development'\n | 'production';\n\nexport const HTML_WIDGET_AGENT_DEFINITIONS: ClientAgentDefinition = {\n snapshot: mode === 'development',\n label: gettext('HTML Widget Code assistant'),\n definition: HTML_AGENT\n};\n","import { inject, Injectable, Injector } from '@angular/core';\nimport { ExtensionFactory, PreviewService } from '@c8y/ngx-components';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n AIMessage,\n AIService,\n AIStreamResponse,\n WidgetAiChatSectionComponentConfig,\n ToolCallPart\n} from '@c8y/ngx-components/ai';\nimport { WidgetAiChatSectionComponent } from '@c8y/ngx-components/ai/agent-chat';\nimport type { WidgetConfigSectionDefinition } from '@c8y/ngx-components/context-dashboard';\nimport { WidgetConfigService } from '@c8y/ngx-components/context-dashboard';\nimport { defaultWidgetIds } from '@c8y/ngx-components/widgets/definitions';\nimport { combineLatest, first, from, map, Observable } from 'rxjs';\nimport { HTML_WIDGET_AGENT_DEFINITIONS } from './html-widget-agent.definitions';\nimport type {\n HtmlWidgetConfig,\n HtmlWidgetConfigService\n} from '@c8y/ngx-components/widgets/implementations/html-widget';\n\ntype CodeChangeToolCall = ToolCallPart<{ code: string }>;\n\n@Injectable({\n providedIn: 'root'\n})\nexport class AIHtmlWidgetConfigFactory implements ExtensionFactory<WidgetConfigSectionDefinition> {\n private readonly betaPreviewService = inject(PreviewService);\n private readonly aiService = inject(AIService);\n /** The root injector (nb: components cannot be injected from here). */\n private readonly injector = inject(Injector);\n\n private readonly codeTag = 'c8y-code-extract';\n private readonly codeToolName = 'c8y-html-widget-code';\n private readonly queryToolName = 'cumulocity-api-request';\n private readonly widgetConfigService = inject(WidgetConfigService);\n\n private readonly aiWidgetConfigDefinition: WidgetConfigSectionDefinition<WidgetAiChatSectionComponent> =\n {\n widgetId: defaultWidgetIds.HTML,\n label: gettext('AI Code Assistant'),\n loadComponent: () =>\n import('@c8y/ngx-components/ai/agent-chat').then(m => m.WidgetAiChatSectionComponent),\n initialState: {\n // configuration to pass to WidgetAiChatSectionComponent\n agent: HTML_WIDGET_AGENT_DEFINITIONS,\n chatConfig: {\n title: gettext(\n 'I’m your AI Code Assistant, here to help you build powerful widgets for your dashboard.'\n ),\n welcomeText: gettext(\n 'Describe the widget you want or select one of the options below to get started.'\n )\n },\n\n loadComponentConfig: this.loadWidgetAiChatComponentConfig.bind(this),\n\n variables: this.widgetConfigService.currentConfig$.pipe(\n map((htmlWidgetConfig: HtmlWidgetConfig) => ({\n currentHtmlWidgetCode: htmlWidgetConfig?.config?.code || '',\n c8yContext: htmlWidgetConfig?.device || {}\n }))\n ),\n\n suggestions: [\n {\n label: gettext('Measurement widget'),\n prompt: gettext('Create a widget that shows the current measurement of this device.')\n },\n {\n label: gettext('Device status widget'),\n prompt: gettext('Create a widget that shows the status of my devices.')\n },\n {\n label: gettext('Critical alarm widget'),\n prompt: gettext('Create a widget that shows all critical alarms.')\n }\n ]\n },\n priority: 100,\n injector: this.injector\n };\n\n get(): Observable<WidgetConfigSectionDefinition[]> {\n return combineLatest([\n from(this.aiService.getAgentHealth()),\n this.betaPreviewService.getState$('ui.html-widget.v2').pipe(first())\n ]).pipe(\n map(([aiHealthCheck, state]) => {\n if (state && aiHealthCheck.isProviderConfigured) {\n return [this.aiWidgetConfigDefinition];\n }\n return [];\n })\n );\n }\n\n private async loadWidgetAiChatComponentConfig(\n componentInjector: Injector\n ): Promise<WidgetAiChatSectionComponentConfig> {\n const { HtmlWidgetConfigService, HtmlAiChatToolDetailsComponent } = await import(\n '@c8y/ngx-components/widgets/implementations/html-widget'\n );\n const htmlWidgetConfigService = componentInjector.get(HtmlWidgetConfigService);\n\n return {\n preprocessAgentMessage: (message, changed) =>\n this.preprocessAgentMessage(message, changed, htmlWidgetConfigService),\n\n assistantMessageDisplayConfig: {\n toolDetailsComponent: toolCallPart => {\n if (toolCallPart.toolName === this.codeToolName) {\n return HtmlAiChatToolDetailsComponent;\n }\n return undefined;\n },\n\n toolCallConfig: {\n [this.queryToolName]: {\n executingLabel: gettext('Analyzing query…'),\n completedLabel: gettext('Query analyzed')\n },\n [this.codeToolName]: {\n executingLabel: gettext('Creating widget…'),\n completedLabel: gettext('Widget created')\n }\n },\n // To match current behaviour and to help with development, for now we show the JSON for all tool calls\n showDefaultToolDetails: 'all'\n }\n };\n }\n\n private applyCurrentCode(code: string, htmlWidgetConfigService: HtmlWidgetConfigService) {\n const newConfig = {\n code: code,\n css: '',\n devMode: true,\n legacy: false,\n options: { advancedSecurity: false, cssEncapsulation: false }\n };\n htmlWidgetConfigService.configChanged$.next(newConfig);\n htmlWidgetConfigService.widgetConfigService.updateConfig({ config: newConfig });\n }\n\n protected preprocessAgentMessage(\n message: AIMessage,\n changedPart: AIStreamResponse['changedPart'],\n htmlWidgetConfigService: HtmlWidgetConfigService\n ): AIMessage {\n // Rewrite HTML content generated by the agent in as text content as if it had come from a tool call\n\n if (!message.steps?.length) return message;\n\n const step = message.steps[message.steps.length - 1];\n const codeToolIndex = step.toolCalls?.findIndex(tc => tc.toolName === this.codeToolName);\n const codeTool = step.toolCalls?.[codeToolIndex] as CodeChangeToolCall | undefined;\n\n if (codeTool && codeTool.type !== 'tool-result') {\n // A code update tool call is in progress - accumulate text into input\n const input = `${codeTool.input?.code || ''}${step.text}`;\n codeTool.input.code = input;\n step.text = '';\n\n const closeIdx = input.indexOf(`</${this.codeTag}>`);\n if (closeIdx !== -1) {\n // Found closing tag - convert to result and start new step\n const beforeClose = input.substring(0, closeIdx);\n const afterClose = input.substring(closeIdx + `</${this.codeTag}>`.length);\n\n codeTool.input.code = beforeClose;\n codeTool.type = 'tool-result';\n // Move it from calls to results\n step.toolCalls = [];\n step.toolResults = [{ ...codeTool }];\n\n // Always create a new (fake) step after this (just like a real AI), to keep any future text and code update calls separate from this one\n message.steps.push({ type: 'text', text: afterClose });\n\n // Since this is a fake tool call not a real one, the tool result callback won't trigger so do this manually\n this.applyCurrentCode(codeTool.input.code, htmlWidgetConfigService);\n } else {\n // Replace with a new instance so that change detections works\n step.toolCalls[codeToolIndex] = { ...codeTool };\n }\n } else if (step.text) {\n // No in-progress code tag, so check for one\n const openIdx = step.text.indexOf(`<${this.codeTag}>`);\n if (openIdx !== -1) {\n const afterOpen = step.text.substring(openIdx + `<${this.codeTag}>`.length);\n step.text = step.text.substring(0, openIdx);\n\n // Start a new \"step\" to keep it separate and simple\n message.steps.push({\n type: 'text',\n text: '',\n toolCalls: [\n {\n type: 'tool-input-streaming',\n toolName: this.codeToolName,\n toolCallId: this.codeToolName + message.steps.length, // add step number just to ensure uniqueness\n input: { code: afterOpen }\n } satisfies CodeChangeToolCall\n ]\n });\n }\n }\n\n return message;\n }\n}\n","import { hookWidgetConfig } from '@c8y/ngx-components/context-dashboard';\nimport { AIHtmlWidgetConfigFactory } from './ai-html-widget-config.factory';\n\nexport const htmlWidgetAIChatProviders = [hookWidgetConfig(AIHtmlWidgetConfigFactory)];\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs","sources":["../../widgets/definitions/html-widget-ai-config/html-widget-agent.definitions.ts","../../widgets/definitions/html-widget-ai-config/ai-html-widget-config.factory.ts","../../widgets/definitions/html-widget-ai-config/index.ts","../../widgets/definitions/html-widget-ai-config/c8y-ngx-components-widgets-definitions-html-widget-ai-config.ts"],"sourcesContent":["import type { ClientAgentDefinition } from '@c8y/ngx-components/ai';\nimport { HTML_AGENT } from '@c8y/ngx-components/ai/agents/html';\nimport { gettext } from '@c8y/ngx-components/gettext';\n\ndeclare const __MODE__: 'development' | 'production' | undefined;\n\n// Treat as \"production\" when run from Jest\nconst mode = (typeof __MODE__ !== 'undefined' ? __MODE__ : 'production') as\n | 'development'\n | 'production';\n\nexport const HTML_WIDGET_AGENT_DEFINITIONS: ClientAgentDefinition = {\n snapshot: mode === 'development',\n label: gettext('HTML Widget Code assistant'),\n definition: HTML_AGENT\n};\n","import { inject, Injectable, Injector } from '@angular/core';\nimport { ExtensionFactory, PreviewService } from '@c8y/ngx-components';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n AIService,\n AIStreamResponse,\n WidgetAiChatSectionComponentConfig,\n ToolCallPart,\n AIAssistantMessage\n} from '@c8y/ngx-components/ai';\nimport { WidgetAiChatSectionComponent } from '@c8y/ngx-components/ai/agent-chat';\nimport type { WidgetConfigSectionDefinition } from '@c8y/ngx-components/context-dashboard';\nimport { WidgetConfigService } from '@c8y/ngx-components/context-dashboard';\nimport { defaultWidgetIds } from '@c8y/ngx-components/widgets/definitions';\nimport { combineLatest, first, from, map, Observable } from 'rxjs';\nimport { HTML_WIDGET_AGENT_DEFINITIONS } from './html-widget-agent.definitions';\nimport type {\n HtmlWidgetConfig,\n HtmlWidgetConfigService\n} from '@c8y/ngx-components/widgets/implementations/html-widget';\n\ntype CodeChangeToolCall = ToolCallPart<{ code: string }>;\n\n@Injectable({\n providedIn: 'root'\n})\nexport class AIHtmlWidgetConfigFactory implements ExtensionFactory<WidgetConfigSectionDefinition> {\n private readonly betaPreviewService = inject(PreviewService);\n private readonly aiService = inject(AIService);\n /** The root injector (nb: components cannot be injected from here). */\n private readonly injector = inject(Injector);\n\n private readonly codeTag = 'c8y-code-extract';\n private readonly codeToolName = 'c8y-html-widget-code';\n private readonly queryToolName = 'cumulocity-api-request';\n private readonly widgetConfigService = inject(WidgetConfigService);\n\n private readonly aiWidgetConfigDefinition: WidgetConfigSectionDefinition<WidgetAiChatSectionComponent> =\n {\n widgetId: defaultWidgetIds.HTML,\n label: gettext('AI Code Assistant'),\n loadComponent: () =>\n import('@c8y/ngx-components/ai/agent-chat').then(m => m.WidgetAiChatSectionComponent),\n initialState: {\n // configuration to pass to WidgetAiChatSectionComponent\n agent: HTML_WIDGET_AGENT_DEFINITIONS,\n chatConfig: {\n title: gettext(\n 'I’m your AI Code Assistant, here to help you build powerful widgets for your dashboard.'\n ),\n welcomeText: gettext(\n 'Describe the widget you want or select one of the options below to get started.'\n )\n },\n\n loadComponentConfig: this.loadWidgetAiChatComponentConfig.bind(this),\n\n variables: this.widgetConfigService.currentConfig$.pipe(\n map((htmlWidgetConfig: HtmlWidgetConfig) => ({\n currentHtmlWidgetCode: htmlWidgetConfig?.config?.code || '',\n c8yContext: htmlWidgetConfig?.device || {}\n }))\n ),\n\n suggestions: [\n {\n label: gettext('Measurement widget'),\n prompt: gettext('Create a widget that shows the current measurement of this device.')\n },\n {\n label: gettext('Device status widget'),\n prompt: gettext('Create a widget that shows the status of my devices.')\n },\n {\n label: gettext('Critical alarm widget'),\n prompt: gettext('Create a widget that shows all critical alarms.')\n }\n ]\n },\n priority: 100,\n injector: this.injector\n };\n\n get(): Observable<WidgetConfigSectionDefinition[]> {\n return combineLatest([\n from(this.aiService.getAgentHealth()),\n this.betaPreviewService.getState$('ui.html-widget.v2').pipe(first())\n ]).pipe(\n map(([aiHealthCheck, state]) => {\n if (state && aiHealthCheck.isProviderConfigured) {\n return [this.aiWidgetConfigDefinition];\n }\n return [];\n })\n );\n }\n\n private async loadWidgetAiChatComponentConfig(\n componentInjector: Injector\n ): Promise<WidgetAiChatSectionComponentConfig> {\n const { HtmlWidgetConfigService, HtmlAiChatToolDetailsComponent } = await import(\n '@c8y/ngx-components/widgets/implementations/html-widget'\n );\n const htmlWidgetConfigService = componentInjector.get(HtmlWidgetConfigService);\n\n return {\n preprocessAgentMessage: (message, changed) =>\n this.preprocessAgentMessage(message, changed, htmlWidgetConfigService),\n\n assistantMessageDisplayConfig: {\n toolDetailsComponent: toolCallPart => {\n if (toolCallPart.toolName === this.codeToolName) {\n return HtmlAiChatToolDetailsComponent;\n }\n return undefined;\n },\n\n toolCallConfig: {\n [this.queryToolName]: {\n executingLabel: gettext('Analyzing query…'),\n completedLabel: gettext('Query analyzed')\n },\n [this.codeToolName]: {\n executingLabel: gettext('Creating widget…'),\n completedLabel: gettext('Widget created')\n }\n },\n // To match current behaviour and to help with development, for now we show the JSON for all tool calls\n showDefaultToolDetails: 'all'\n }\n };\n }\n\n private applyCurrentCode(code: string, htmlWidgetConfigService: HtmlWidgetConfigService) {\n const newConfig = {\n code: code,\n css: '',\n devMode: true,\n legacy: false,\n options: { advancedSecurity: false, cssEncapsulation: false }\n };\n htmlWidgetConfigService.configChanged$.next(newConfig);\n htmlWidgetConfigService.widgetConfigService.updateConfig({ config: newConfig });\n }\n\n protected preprocessAgentMessage(\n message: AIAssistantMessage,\n changedPart: AIStreamResponse['changedPart'],\n htmlWidgetConfigService: HtmlWidgetConfigService\n ): AIAssistantMessage {\n // Only apply this pre-processing to text parts, since that's what the agent will use to send the code\n if (!changedPart || changedPart.type !== 'text') {\n return message;\n }\n\n // Rewrite HTML content generated by the agent as text content as if it had come from a tool call\n\n // Find last tool named this.codeToolName in message.content, or undefined\n const codeTool = message.content.reduce<CodeChangeToolCall | undefined>(\n (last, part) =>\n 'toolName' in part && part.toolName === this.codeToolName\n ? (part as CodeChangeToolCall)\n : last,\n undefined\n );\n\n if (codeTool && codeTool.type !== 'tool-result') {\n // A code update tool call is in progress - accumulate text into input\n const input = `${codeTool.input?.code || ''}${changedPart.text}`;\n codeTool.input.code = input;\n changedPart.text = '';\n\n const closeIdx = input.indexOf(`</${this.codeTag}>`);\n if (closeIdx !== -1) {\n // Found closing tag - convert to result and start new step\n const beforeClose = input.substring(0, closeIdx);\n const afterClose = input.substring(closeIdx + `</${this.codeTag}>`.length);\n\n codeTool.input.code = beforeClose;\n codeTool.type = 'tool-result';\n\n // Always create a new (fake) step after this (just like a real AI), since final step can't contain tool calls\n message.content.push({ type: 'step-start' });\n if (afterClose.length > 0) {\n message.content.push({ type: 'text', text: afterClose });\n }\n\n // Since this is a fake tool call not a real one, the tool result callback won't trigger so do this manually\n this.applyCurrentCode(codeTool.input.code, htmlWidgetConfigService);\n } else {\n // Replace with a new instance so that change detection works\n message.content[message.content.findIndex(part => part === codeTool)] = { ...codeTool };\n }\n } else {\n // No in-progress code tag, so check for one\n const openIdx = changedPart.text.indexOf(`<${this.codeTag}>`);\n if (openIdx !== -1) {\n const afterOpen = changedPart.text.substring(openIdx + `<${this.codeTag}>`.length);\n changedPart.text = changedPart.text.substring(0, openIdx);\n\n // Start a new \"step\" to keep it separate and simple\n message.content.push({ type: 'step-start' });\n message.content.push({\n type: 'tool-input-streaming',\n toolName: this.codeToolName,\n toolCallId: this.codeToolName + message.content.length, // add part number just to ensure uniqueness\n input: { code: afterOpen }\n } satisfies CodeChangeToolCall);\n }\n }\n\n if (changedPart.text === '') {\n // Remove any empty changedPart from content\n message.content.splice(\n message.content.findIndex(part => part === changedPart),\n 1\n );\n }\n\n return message;\n }\n}\n","import { hookWidgetConfig } from '@c8y/ngx-components/context-dashboard';\nimport { AIHtmlWidgetConfigFactory } from './ai-html-widget-config.factory';\n\nexport const htmlWidgetAIChatProviders = [hookWidgetConfig(AIHtmlWidgetConfigFactory)];\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -8,8 +8,8 @@ import * as i1 from '@c8y/ngx-components';
|
|
|
8
8
|
import { AlertService, DashboardChildComponent, GroupService, DynamicComponentAlertAggregator, DynamicComponentAlert, C8yTranslateDirective, DeviceStatusComponent, DynamicComponentModule, EmptyStateComponent, ForOfDirective, GuideDocsComponent, GuideHrefDirective, IconDirective, ListGroupModule, LoadingComponent, VirtualScrollListenerDirective, WidgetActionWrapperComponent, ApplyRangeClassPipe, C8yTranslatePipe, DatePipe, FormGroupComponent } from '@c8y/ngx-components';
|
|
9
9
|
import { DatapointsExportSelectorComponent } from '@c8y/ngx-components/datapoints-export-selector';
|
|
10
10
|
import { gettext } from '@c8y/ngx-components/gettext';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { REFRESH_OPTION, TIME_INTERVAL, GLOBAL_CONTEXT_DISPLAY_MODE, WidgetConfigMigrationService, CONTEXT_FEATURE, PRESET_NAME, GlobalContextConnectorComponent, LocalControlsComponent } from '@c8y/ngx-components/global-context';
|
|
12
|
+
import { isEqual, merge } from 'lodash-es';
|
|
13
13
|
import { pairwise, filter, debounceTime, take, distinctUntilChanged } from 'rxjs';
|
|
14
14
|
import * as i1$1 from '@angular/cdk/drag-drop';
|
|
15
15
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
@@ -30,6 +30,23 @@ const DEFAULT_DATAPOINTS_LIST_COLUMNS = [
|
|
|
30
30
|
{ id: 'diffPercentage', label: gettext('Diff %'), visible: true, order: 4 },
|
|
31
31
|
{ id: 'asset', label: gettext('Asset'), visible: true, order: 5 }
|
|
32
32
|
];
|
|
33
|
+
/**
|
|
34
|
+
* Defaults for legacy AngularJS datapoints list widgets that arrive without any
|
|
35
|
+
* time-context fields. Mirrors the legacy "show all data" behavior. Factory so
|
|
36
|
+
* `dateTo` is read at call time.
|
|
37
|
+
*/
|
|
38
|
+
function getLegacyDatapointsListDefaults() {
|
|
39
|
+
return {
|
|
40
|
+
displayMode: GLOBAL_CONTEXT_DISPLAY_MODE.CONFIG,
|
|
41
|
+
dateTimeContext: {
|
|
42
|
+
dateFrom: '1970-01-01T00:00:00.000Z',
|
|
43
|
+
dateTo: new Date().toISOString(),
|
|
44
|
+
interval: TIME_INTERVAL.CUSTOM
|
|
45
|
+
},
|
|
46
|
+
isAutoRefreshEnabled: true,
|
|
47
|
+
refreshOption: REFRESH_OPTION.LIVE
|
|
48
|
+
};
|
|
49
|
+
}
|
|
33
50
|
|
|
34
51
|
class DatapointsListService {
|
|
35
52
|
/**
|
|
@@ -290,11 +307,7 @@ class DatapointsListViewComponent {
|
|
|
290
307
|
this.GLOBAL_CONTEXT_DISPLAY_MODE = GLOBAL_CONTEXT_DISPLAY_MODE;
|
|
291
308
|
this.missingAllPermissionsAlert = new DynamicComponentAlertAggregator();
|
|
292
309
|
this.targetManagedObjects = new Map();
|
|
293
|
-
this.configSignal = linkedSignal(() => {
|
|
294
|
-
const raw = this.config();
|
|
295
|
-
const migrated = this.widgetConfigMigrationService.migrateWidgetConfig(raw);
|
|
296
|
-
return merge({}, raw, migrated);
|
|
297
|
-
}, ...(ngDevMode ? [{ debugName: "configSignal" }] : []));
|
|
310
|
+
this.configSignal = linkedSignal(() => this.config(), ...(ngDevMode ? [{ debugName: "configSignal" }] : []));
|
|
298
311
|
this.contextConfig = signal({}, ...(ngDevMode ? [{ debugName: "contextConfig" }] : []));
|
|
299
312
|
this.dataPoints = signal([], ...(ngDevMode ? [{ debugName: "dataPoints" }] : []));
|
|
300
313
|
this.displayMode = signal(GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD, ...(ngDevMode ? [{ debugName: "displayMode" }] : []));
|
|
@@ -378,11 +391,14 @@ class DatapointsListViewComponent {
|
|
|
378
391
|
});
|
|
379
392
|
}
|
|
380
393
|
ngOnInit() {
|
|
394
|
+
this.applyConfigMigration();
|
|
381
395
|
const config = this.configSignal();
|
|
382
396
|
const displayMode = config.displayMode || GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD;
|
|
383
397
|
this.displayMode.set(displayMode);
|
|
384
398
|
this.contextConfig.set(this.extractContextState(config));
|
|
385
|
-
|
|
399
|
+
// First fetch is triggered by the global-context connector's initial configChange,
|
|
400
|
+
// except in preview mode where the connector is not rendered (see template).
|
|
401
|
+
if (this.isInPreviewMode()) {
|
|
386
402
|
this.loadDatapoints();
|
|
387
403
|
}
|
|
388
404
|
}
|
|
@@ -439,6 +455,17 @@ class DatapointsListViewComponent {
|
|
|
439
455
|
onListScrolledToTop() {
|
|
440
456
|
this.setAutoRefreshPaused(false);
|
|
441
457
|
}
|
|
458
|
+
/** Runs once on init; subsequent input changes are already in the new format. */
|
|
459
|
+
applyConfigMigration() {
|
|
460
|
+
const raw = this.config();
|
|
461
|
+
const options = this.widgetConfigMigrationService.hasNoTimeContextFields(raw)
|
|
462
|
+
? { legacyTimeContextDefaults: getLegacyDatapointsListDefaults() }
|
|
463
|
+
: undefined;
|
|
464
|
+
const migrated = this.widgetConfigMigrationService.migrateWidgetConfig(raw, options);
|
|
465
|
+
if (migrated !== raw) {
|
|
466
|
+
this.configSignal.set(merge({}, raw, migrated));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
442
469
|
extractContextState(config) {
|
|
443
470
|
return {
|
|
444
471
|
dateTimeContext: config.dateTimeContext,
|
|
@@ -572,6 +599,7 @@ class DatapointsListConfigComponent {
|
|
|
572
599
|
this.destroyRef = inject(DestroyRef);
|
|
573
600
|
this.form = inject(NgForm);
|
|
574
601
|
this.formBuilder = inject(FormBuilder);
|
|
602
|
+
this.widgetConfigMigrationService = inject(WidgetConfigMigrationService);
|
|
575
603
|
this.widgetConfigService = inject(WidgetConfigService);
|
|
576
604
|
this.controls = PRESET_NAME.DATA_POINTS_LIST;
|
|
577
605
|
this.minDecimalPlaces = MIN_DECIMAL_PLACES;
|
|
@@ -585,6 +613,7 @@ class DatapointsListConfigComponent {
|
|
|
585
613
|
this.previewConfig$ = this.widgetConfigService.currentConfig$.pipe(filter(config => !!config?.dateTimeContext), debounceTime(300));
|
|
586
614
|
}
|
|
587
615
|
ngOnInit() {
|
|
616
|
+
this.applyLegacyEpochDefaults();
|
|
588
617
|
this.widgetConfigService.currentConfig$
|
|
589
618
|
.pipe(filter(config => !!config?.dateTimeContext), take(1), takeUntilDestroyed(this.destroyRef))
|
|
590
619
|
.subscribe(() => this.initForm());
|
|
@@ -674,6 +703,16 @@ class DatapointsListConfigComponent {
|
|
|
674
703
|
return visibleColumns.length >= 1 ? null : { atLeastOneColumnMustBeVisible: true };
|
|
675
704
|
};
|
|
676
705
|
}
|
|
706
|
+
/** Stamp epoch defaults so the editor opens matching the AngularJS predecessor. */
|
|
707
|
+
applyLegacyEpochDefaults() {
|
|
708
|
+
const currentConfig = this.widgetConfigService.currentConfig;
|
|
709
|
+
if (!currentConfig) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (this.widgetConfigMigrationService.hasNoTimeContextFields(currentConfig)) {
|
|
713
|
+
this.widgetConfigService.updateConfig(getLegacyDatapointsListDefaults());
|
|
714
|
+
}
|
|
715
|
+
}
|
|
677
716
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatapointsListConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
678
717
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DatapointsListConfigComponent, isStandalone: true, selector: "c8y-datapoints-list-view-config", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "previewTemplate", first: true, predicate: ["dataPointsListPreview"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (configForm(); as form) {\n <form\n class=\"no-card-context\"\n [formGroup]=\"form\"\n >\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Columns (drag to reorder)' | translate }}</legend>\n <c8y-list-group\n formArrayName=\"columns\"\n cdkDropList\n (cdkDropListDropped)=\"onColumnDrop($event)\"\n >\n @if (columnsFormArray().errors?.atLeastOneColumnMustBeVisible) {\n <div\n class=\"alert alert-warning m-t-8\"\n role=\"alert\"\n >\n {{ 'At least 1 column must be visible.' | translate }}\n </div>\n }\n\n @for (column of columnsFormArray().controls; track column.value.id; let i = $index) {\n <c8y-li\n class=\"c8y-list__item__collapse--container-small\"\n [formGroupName]=\"i\"\n cdkDrag\n >\n <c8y-li-drag-handle\n [title]=\"'Click and drag to reorder' | translate\"\n cdkDragHandle\n >\n <i c8yIcon=\"drag-reorder\"></i>\n </c8y-li-drag-handle>\n <c8y-li-checkbox\n class=\"a-s-center p-r-0\"\n [displayAsSwitch]=\"true\"\n formControlName=\"visible\"\n (click)=\"$event.stopPropagation()\"\n ></c8y-li-checkbox>\n <c8y-li-body>\n <div class=\"d-flex a-i-center\">\n <span class=\"text-truncate\">{{ column.value.label | translate }}</span>\n @switch (column.value.label) {\n @case ('Target') {\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help content' | translate\"\n [popover]=\"\n 'The Target column shows the value set on the target field of the data point'\n | translate\n \"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n }\n @case ('Diff') {\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help content' | translate\"\n [popover]=\"\n 'The Diff column shows the difference between the current value and the target value of the data point'\n | translate\n \"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n }\n }\n </div>\n </c8y-li-body>\n </c8y-li>\n }\n </c8y-list-group>\n </fieldset>\n\n <!-- decimal input -->\n <fieldset class=\"c8y-fieldset\">\n <legend>\n {{ 'Decimal places' | translate }}\n </legend>\n <c8y-form-group class=\"p-t-8\">\n <input\n class=\"form-control\"\n name=\"decimalPlaces\"\n type=\"number\"\n formControlName=\"decimalPlaces\"\n step=\"1\"\n [min]=\"minDecimalPlaces\"\n [max]=\"maxDecimalPlaces\"\n />\n </c8y-form-group>\n </fieldset>\n </form>\n}\n\n<ng-template #dataPointsListPreview>\n @let previewConfig = previewConfig$ | async;\n @if (previewConfig && previewConfig.displayMode !== 'dashboard') {\n <c8y-local-controls\n [controls]=\"controls\"\n [displayMode]=\"previewConfig.displayMode!\"\n [config]=\"previewConfig\"\n [disabled]=\"true\"\n ></c8y-local-controls>\n }\n\n @if (previewConfig) {\n <c8y-datapoints-list\n [config]=\"previewConfig\"\n [isInPreviewMode]=\"true\"\n data-cy=\"c8y-datapoints-list-widget-config--preview-datapoints-list\"\n ></c8y-datapoints-list>\n }\n</ng-template>\n", dependencies: [{ kind: "component", type: DatapointsListViewComponent, selector: "c8y-datapoints-list", inputs: ["config", "isInPreviewMode"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: DynamicComponentModule }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: ListGroupModule }, { kind: "component", type: i1.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i1.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i1.ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "component", type: i1.ListItemCheckboxComponent, selector: "c8y-list-item-checkbox, c8y-li-checkbox", inputs: ["selected", "indeterminate", "disabled", "displayAsSwitch"], outputs: ["onSelect"] }, { kind: "component", type: i1.ListItemDragHandleComponent, selector: "c8y-list-item-drag-handle, c8y-li-drag-handle" }, { kind: "component", type: LocalControlsComponent, selector: "c8y-local-controls", inputs: ["controls", "displayMode", "config", "isLoading", "disabled", "emitRefresh"], outputs: ["configChange", "refresh"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i3.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i3.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i4.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
679
718
|
}
|