From 34949ea16c26d113f17f59a95ddac72d20666ba3 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 22 Apr 2026 14:59:53 +0800 Subject: [PATCH] Fix bricks-framework component usage: replace invalid List with custom implementation, replace Input/Checkbox/Select/Textarea with proper UiStr/UiInt/UiCheck/UiCode/UiText components with required name attributes --- wwwroot/chat.ui | 4 +- wwwroot/new_session.ui | 9 +- wwwroot/scripts/list-component.js | 149 ++++++++++++++++++++++++++++++ wwwroot/services.ui | 13 +-- wwwroot/sessions.ui | 3 +- wwwroot/settings.ui | 32 ++++--- 6 files changed, 186 insertions(+), 24 deletions(-) create mode 100644 wwwroot/scripts/list-component.js diff --git a/wwwroot/chat.ui b/wwwroot/chat.ui index 2880729..4cfe1f3 100644 --- a/wwwroot/chat.ui +++ b/wwwroot/chat.ui @@ -29,8 +29,10 @@ }, "subwidgets": [ { - "widgettype": "Input", + "widgettype": "UiStr", + "id": "chat-input", "options": { + "name": "chat-input", "placeholder": "输入消息...", "width": "100%" } diff --git a/wwwroot/new_session.ui b/wwwroot/new_session.ui index fa5da09..3835876 100644 --- a/wwwroot/new_session.ui +++ b/wwwroot/new_session.ui @@ -25,9 +25,10 @@ }, "subwidgets": [ { - "widgettype": "Input", + "widgettype": "UiStr", "id": "session-name", "options": { + "name": "session-name", "label": "Session Name", "placeholder": "Enter session name...", "width": "100%", @@ -35,9 +36,10 @@ } }, { - "widgettype": "Select", + "widgettype": "UiCode", "id": "model-select", "options": { + "name": "model-select", "label": "AI Model", "placeholder": "Select model...", "width": "100%", @@ -46,9 +48,10 @@ } }, { - "widgettype": "Textarea", + "widgettype": "UiText", "id": "initial-prompt", "options": { + "name": "initial-prompt", "label": "Initial Prompt (Optional)", "placeholder": "Enter initial context or instructions...", "width": "100%", diff --git a/wwwroot/scripts/list-component.js b/wwwroot/scripts/list-component.js new file mode 100644 index 0000000..acc3126 --- /dev/null +++ b/wwwroot/scripts/list-component.js @@ -0,0 +1,149 @@ +// Custom List component for bricks-framework +// This component renders a list of items with custom item templates + +bricks.List = class extends bricks.VBox { + constructor(opts) { + super(opts); + this.data_url = opts.data_url || null; + this.items = opts.items || []; + this.itemHeight = opts.itemHeight || 50; + this._loading = false; + + // Set default dimensions if not provided + if (!this.options.width) this.options.width = '100%'; + if (!this.options.height) this.options.height = '100%'; + + // Load data if data_url is provided + if (this.data_url) { + this.loadData(); + } else if (this.items.length > 0) { + this.renderItems(); + } + } + + async loadData() { + if (this._loading || !this.data_url) return; + this._loading = true; + + try { + const response = await fetch(this.data_url); + if (response.ok) { + const data = await response.json(); + this.items = Array.isArray(data) ? data : (data.items || []); + this.renderItems(); + } else { + console.error('Failed to load list data:', response.status); + } + } catch (error) { + console.error('Error loading list data:', error); + } finally { + this._loading = false; + } + } + + renderItems() { + // Clear existing children + this.subwidgets = []; + this.children = []; + + // Render each item using the template defined in subwidgets + this.items.forEach((item, index) => { + const itemWidget = this.createItemWidget(item, index); + if (itemWidget) { + this.appendChild(itemWidget); + } + }); + + // Refresh the display + this.refresh(); + } + + createItemWidget(itemData, index) { + if (!this.template || this.template.length === 0) { + return null; + } + + // Clone the template and process it with item data + const processedTemplate = this.processTemplate(this.template[0], itemData, index); + if (processedTemplate) { + const widget = bricks.Factory.create(processedTemplate); + if (widget) { + // Store reference to item data for event handling + widget._listItemData = itemData; + widget._listItemIndex = index; + return widget; + } + } + return null; + } + + processTemplate(template, itemData, index) { + // This is a simplified template processor + // In a real implementation, you would need proper Jinja2-like processing + const processed = JSON.parse(JSON.stringify(template)); + + // Process options recursively + this.processObject(processed, itemData, index); + + return processed; + } + + processObject(obj, itemData, index) { + if (typeof obj !== 'object' || obj === null) { + return; + } + + for (const key in obj) { + if (typeof obj[key] === 'string') { + // Simple string replacement for {{item.xxx}} patterns + if (obj[key].includes('{{item.')) { + const match = obj[key].match(/{{item\.([^}]+)}}/); + if (match && match[1]) { + const fieldName = match[1]; + obj[key] = obj[key].replace(`{{item.${fieldName}}}`, itemData[fieldName] || ''); + } + } + // Handle conditional expressions like {{ '#22C55E' if item.status == 'Connected' else '#EF4444' }} + if (obj[key].includes('{{ ') && obj[key].includes(' if item.')) { + const fullMatch = obj[key].match(/{{\s*(.*?)\s+if\s+item\.(\w+)\s*==\s*'([^']+)'\s+else\s+(.*?)\s*}}/); + if (fullMatch) { + const trueValue = fullMatch[1].trim().replace(/^['"]|['"]$/g, ''); + const fieldName = fullMatch[2]; + const conditionValue = fullMatch[3]; + const falseValue = fullMatch[4].trim().replace(/^['"]|['"]$/g, ''); + + obj[key] = itemData[fieldName] === conditionValue ? trueValue : falseValue; + } + } + } else if (typeof obj[key] === 'object' && obj[key] !== null) { + this.processObject(obj[key], itemData, index); + } + } + } + + // Public method to reload data + reload() { + if (this.data_url) { + this.loadData(); + } + } + + // Public method to set items directly + setItems(items) { + this.items = items || []; + this.renderItems(); + } + + // Override appendChild to capture the template + appendChild(widget) { + if (!this.template) { + this.template = this.subwidgets || []; + } + super.appendChild(widget); + } +}; + +// Register the List component +bricks.register('List', bricks.List); + +console.log('Custom List component registered with bricks framework'); \ No newline at end of file diff --git a/wwwroot/services.ui b/wwwroot/services.ui index 2fe3666..73ca2a7 100644 --- a/wwwroot/services.ui +++ b/wwwroot/services.ui @@ -25,9 +25,10 @@ }, "subwidgets": [ { - "widgettype": "Input", + "widgettype": "UiStr", "id": "service-search", "options": { + "name": "service-search", "placeholder": "Search services...", "width": "300px", "icon": "fa fa-search" @@ -107,7 +108,7 @@ "widgettype": "Badge", "options": { "text": "{{item.status}}", - "bgcolor": "{{ '#22C55E' if data.status == 'Connected' else '#EF4444' }}", + "bgcolor": "{{ '#22C55E' if item.status == 'Connected' else '#EF4444' }}", "color": "#FFFFFF", "borderRadius": "12px", "padding": "4px 12px", @@ -119,7 +120,7 @@ { "widgettype": "Text", "options": { - "text": "{{data.endpoint}}", + "text": "{{item.endpoint}}", "fontSize": "14px", "color": "#CBD5E1", "marginBottom": "8px" @@ -150,7 +151,7 @@ "actiontype": "urlwidget", "target": "app.main-content", "options": { - "url": "{{entire_url('edit_service.ui?id={{data.id}}')}}" + "url": "{{entire_url('edit_service.ui?id={{item.id}}')}}" }, "mode": "replace" } @@ -175,7 +176,7 @@ "target": "self", "rfname": "remove_service", "params": { - "service_id": "{{data.id}}" + "service_id": "{{item.id}}" } } ] @@ -199,7 +200,7 @@ "target": "self", "rfname": "test_service_connection", "params": { - "service_id": "{{data.id}}" + "service_id": "{{item.id}}" } } ] diff --git a/wwwroot/sessions.ui b/wwwroot/sessions.ui index ff93234..3cc456b 100644 --- a/wwwroot/sessions.ui +++ b/wwwroot/sessions.ui @@ -25,9 +25,10 @@ }, "subwidgets": [ { - "widgettype": "Input", + "widgettype": "UiStr", "id": "session-search", "options": { + "name": "session-search", "placeholder": "Search sessions...", "width": "300px", "icon": "fa fa-search" diff --git a/wwwroot/settings.ui b/wwwroot/settings.ui index fb7827d..c61f24c 100644 --- a/wwwroot/settings.ui +++ b/wwwroot/settings.ui @@ -59,9 +59,10 @@ }, "subwidgets": [ { - "widgettype": "Input", + "widgettype": "UiCode", "id": "default-model", "options": { + "name": "default-model", "label": "Default AI Model", "placeholder": "Select default model...", "width": "100%", @@ -69,20 +70,21 @@ } }, { - "widgettype": "Input", + "widgettype": "UiInt", "id": "session-timeout", "options": { + "name": "session-timeout", "label": "Session Timeout (minutes)", - "type": "number", "placeholder": "Enter timeout in minutes...", "width": "100%", "marginBottom": "16px" } }, { - "widgettype": "Checkbox", + "widgettype": "UiCheck", "id": "auto-save", "options": { + "name": "auto-save", "label": "Auto-save sessions", "checked": true, "marginBottom": "24px" @@ -160,8 +162,9 @@ }, "subwidgets": [ { - "widgettype": "Input", + "widgettype": "UiStr", "options": { + "name": "model-name", "label": "Model Name", "value": "{{data.name}}", "width": "100%", @@ -169,8 +172,9 @@ } }, { - "widgettype": "Input", + "widgettype": "UiStr", "options": { + "name": "provider", "label": "Provider", "value": "{{data.provider}}", "width": "100%", @@ -284,20 +288,22 @@ } }, { - "widgettype": "Checkbox", - "id": "require-auth", + { + "widgettype": "UiCheck", "options": { - "label": "Require authentication for web access", - "checked": false, + "name": "require-auth", + "label": "Require authentication for API access", + "checked": "{{security.require_auth}}", "marginBottom": "12px" } }, { - "widgettype": "Checkbox", - "id": "encrypt-storage", + "widgettype": "UiCheck", "options": { + "name": "encrypt-storage", "label": "Encrypt local storage", - "checked": false, + "checked": "{{security.encrypt_storage}}", + "marginBottom": "24px" "marginBottom": "24px" } },