Skip to content

Conversation

@hexqi
Copy link
Collaborator

@hexqi hexqi commented Sep 28, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

TinyEngine AI功能增强

部署环境https://hexqi.github.io/tiny-engine/?type=app&id=1&tenant=1

Agent模式:

  • 优化Agent模式智能搭建页面(支持上传图片、自然预研生成页面UI)效果,优化流式生成UI效果
  • 支持搭建模式物料跟随系统内部的物料变化
  • 支持自动修复有问题的Schema
  • 支持事件与状态绑定等协议特性
  • 生成页面支持引用资源管理图片
  • 上传图片后聊天支持渲染及预览用户图片
  • 支持继续对话二次修改,使用增量返回修改速度更快
  • 支持tailwind,减少schema大小,提升效果

重构:

  • 插件代码全部重构,根据对话生命周期,抽取AgentMode与ChatMode,方便扩展
  • composables重构,RobotChat组件解耦抽离业务逻辑
  • 重写AIProvider,支持fetch与axios

新增历史列表功能:

  • 历史列表支持显示Icon,区分Agent与Chat模式
  • 历史会话按照日期分组
  • 支持本地缓存历史记录

新增模型设置功能:

  • 支持通过UI添加自定义模型服务,支持批量添加模型
  • 支持配置模型视觉、工具调用、快速模型能力
  • 支持添加快速模型用于代码补全等对速度有要求场景
  • 模型baseUrl支持非/chat/completions结尾类型
  • 设置面板由popover改为半屏显示提升体验,适配全屏样式
  • 支持加密API Key,解决安全风险问题
  • 未输入API Key时添加校验
  • 模型选择菜单显示能力标签
  • 支持qwen 深度思考配置模式

UI/体验优化:

  • 刷新UI,适配边栏与全屏模式
  • AI插件移入顶部工具栏,使用更便捷
  • 打开插件时自动关闭右侧设置面板
  • 更新Agent与Chat模式的图标
  • 点击首页的Prompt自动切换模式
  • 添加深度思考开关
  • 支持记忆用户选择配置:模式、深度思考等

bugfix:

  • 解决无法中断问题
  • 解决切换模式混乱问题
  • 解决一直显示loading图标问题
  • 修复接口报错一直loading问题
  • 不返回JSON问题
  • 返回json,但不是json patch问题
  • methods自动修复
  • 自定义模型无法使用图片问题
  • 不能自动滚动问题

二次开发:

  • 支持自定义chatMode与agentMode逻辑
  • 添加注册表参数,支持配置apikey加密、使用向量数据库、使用图片资源、模型接口等

文档:

  • 更新AI插件使用文档

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Redesigned AI assistant with Agent and Chat modes, streaming replies, tool integrations, file uploads, and live page/schema updates.
    • Rich chat UI: grouped history panel, prompts, attachments, fullscreen/settings toggles, and new renderers (image/loading/reasoning).
    • Advanced model & service settings: multi-provider model catalog, service management, API key handling, and MCP tool support.
  • Bug Fixes

    • Silenced canceled-request errors, safer icon handling, and improved right-panel/layout positioning.
  • Documentation

    • Updated comprehensive AI plugin usage guide and tutorials.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 28, 2025

Walkthrough

Refactors the robot plugin into a composable-driven architecture: adds mode-based lifecycle hooks (Agent/Chat), streaming LLM/provider integration, tool-call orchestration, JSON-Patch prompt/repair flows, new UI components and renderers, extensive types/utilities, config migration/persistence, and removes legacy monolithic modules.

Changes

Cohort / File(s) Change Summary
Layout & Canvas
packages/layout/src/DesignSettings.vue, packages/layout/src/Main.vue, packages/canvas/container/src/components/CanvasResize.vue, packages/layout/src/composable/useLayout.ts
CSS/positioning tweaks (removed top offset, added relative positioning), watcher added for layoutState.toolbars.render, and toolbars.render added to layout state.
Plugin Package / Config
packages/plugins/robot/package.json, tsconfig.app.json, packages/register/src/constants.ts, packages/plugins/robot/meta.js
Dependency/version updates (including @vueuse/core), TS lib/jsx options, added META_SERVICE Robot entry, and new plugin meta options (customCompatibleAIModels, enableResourceContext, enableRagContext, encryptServiceApiKey, modeImplementation).
Core Config & Composables
packages/plugins/robot/src/composables/core/useConfig.ts, .../useConversation.ts, .../pageUpdater.ts, .../useMessageStream.ts, packages/plugins/robot/src/composables/useChat.ts
New config manager with migration/persistence/encryption, conversation adapter, throttled streaming page updater, message stream factory, and a high-level streaming chat composable (streaming lifecycle, aborts, conversation management).
Mode System
packages/plugins/robot/src/composables/modes/*, packages/plugins/robot/src/types/mode.types.ts
New mode registry and implementations for Agent and Chat exposing ModeHooks lifecycle (request composition, streaming, tool-calls, JSON repair, schema updates).
LLM Provider & Client
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts, packages/plugins/robot/src/services/aiClient.ts, packages/plugins/robot/src/services/api.ts
New OpenAI-compatible provider (fetch/axios + streaming), AI client factory, and consolidated apiService (chat, agentChat, aiSearch, resource upload, encrypt).
Tool Calls & MCP Integration
packages/plugins/robot/src/composables/features/useToolCalls.ts, packages/plugins/robot/src/composables/features/useMcp.ts, packages/plugins/robot/src/types/mcp.types.ts
Tool-call orchestration (sequential calls, aborts, hooks), MCP composable refactor (llmTools cache, changed listTools return type), and MCP-related types.
Schema / Prompt / Repair Utilities
packages/plugins/robot/src/constants/prompts/*, packages/plugins/robot/src/utils/schema.utils.ts, packages/plugins/robot/src/utils/chat.utils.ts, packages/plugins/robot/src/utils/meta.utils.ts
Dynamic prompt generation (agent/chat/json-fix), component catalog/examples, JSON Patch validation/auto-fix/repair, SSE stream helpers, and robot service options helper.
Types & Re-exports
packages/plugins/robot/src/types/*.ts, packages/plugins/robot/src/constants/model-config.ts, packages/plugins/robot/src/types/index.ts
New and expanded type definitions (modes, settings, MCP/chat types), default model catalog, and added re-exports.
UI — Chat & Renderers
packages/plugins/robot/src/components/chat/RobotChat.vue, .../FooterButton.vue, .../renderers/*.vue, .../renderers/index.ts
New RobotChat UI (file upload, prompts, bubbles), FooterButton, Loading/Img/Agent renderers, and centralized renderer exports.
UI — Header & Footer Extensions
packages/plugins/robot/src/components/header-extension/**/*, packages/plugins/robot/src/components/footer-extension/*, .../index.ts
New RobotSetting, ServiceEditDialog, History component; MCP footer integration uses FooterButton; RobotTypeSelect prop renamed; added re-export index files.
Main Component & Service Entry
packages/plugins/robot/src/Main.vue, packages/plugins/robot/src/metas/index.ts, packages/plugins/robot/index.ts
Main.vue rewritten to script-setup with component-driven flow and file upload/API-key handling; RobotService now uses useConfig(); index adds type re-exports and imports adjusted.
Removed Legacy Modules / Components
packages/plugins/robot/src/js/useRobot.ts, .../prompts.ts, .../utils.ts, .../mcp/utils.ts, .../BuildLoadingRenderer.vue
Deleted legacy monolithic modules and deprecated BuildLoadingRenderer component.
Misc Utilities & Fixes
packages/common/js/completion.js, packages/plugins/resource/src/ResourceList.vue, designer-demo/src/composable/http/index.js, designer-demo/public/mock/bundle.json, packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue, packages/plugins/materials/src/composable/useMaterial.ts
Import consolidation and meta API usage in completion.js; batch resource payload adds description default; silence canceled HTTP errors; demo snippet name updates; safer optional chaining for icons; getComponentDetail adds null guard.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as RobotChat
    participant ChatC as useChat
    participant Mode as useMode (Agent/Chat)
    participant Client as AIClient
    participant Provider as LLM
    participant MCP as MCP Server
    participant Canvas

    User->>UI: sendMessage(text, attachment?)
    UI->>ChatC: sendUserMessage(...)
    ChatC->>ChatC: insert loading assistant message
    ChatC->>Mode: onBeforeRequest(requestParams)
    Mode->>Mode: build prompt & request params
    ChatC->>Client: chatStream(request)
    Client->>Provider: open stream request
    Provider-->>Client: SSE chunks
    Client->>ChatC: onStreamData(chunk)
    ChatC->>Mode: onStreamData(chunk)
    Mode->>Canvas: updatePageSchema(stream patches) (Agent mode)
    ChatC->>ChatC: detect tool_calls in stream
    ChatC->>MCP: callTool(...) (when needed)
    MCP-->>ChatC: tool result
    ChatC->>Mode: onPostCallTool(...)
    Provider-->>Client: [DONE]
    Client->>ChatC: onFinishRequest()
    ChatC->>Mode: onMessageProcessed(...)
    Mode->>Mode: validate/repair JSON (Agent)
    Mode->>Canvas: apply final schema (Agent)
    ChatC->>UI: update messages
    UI->>User: render response
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

  • Focus areas:
    • packages/plugins/robot/src/composables/useChat.ts — streaming lifecycle, abort controllers, concurrency/race conditions
    • packages/plugins/robot/src/composables/core/useConfig.ts — migration, persistence, encryption, merge logic
    • packages/plugins/robot/src/composables/modes/useAgentMode.ts & useChatMode.ts — streaming JSON-Patch flows, repair logic, tool-call interplay
    • packages/plugins/robot/src/composables/core/pageUpdater.ts & utils/schema.utils.ts — patch parsing, validation, auto-fix and canvas history integration
    • packages/plugins/robot/src/composables/features/useToolCalls.ts & useMcp.ts — tool orchestration, abort propagation, MCP tool conversions
    • packages/plugins/robot/src/services/OpenAICompatibleProvider.ts & aiClient.ts — streaming adapters (fetch/axios) and error mapping
    • packages/plugins/robot/src/Main.vue — API surface and behavior differences; verify consumer compatibility

Poem

🐰 I hopped into code with a twitch of my nose,
New modes and streams where the patch-logic grows.
Agent thinks, Chat replies, tools click and run,
Schemas mend, renderers gleam under the sun.
Hooray — a refactor, a carrot-shaped fun! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: ai plugin refactor' accurately captures the main objective of this large-scale refactor of the AI plugin codebase, emphasizing the architectural restructuring and feature enhancements.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hexqi hexqi marked this pull request as draft September 28, 2025 03:22
@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from 0d7f889 to 57ac995 Compare September 28, 2025 03:24
@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from 57ac995 to 5eb53e9 Compare September 29, 2025 07:06
@chilingling
Copy link
Member

image

模型配置需要支持配置额外传参,比如配置一个 qwen-plus-thinking 模型,可以配置额外带一个 { enable_thinking: true } 的参数

@chilingling
Copy link
Member

感觉可以增强一下代码结构,增强插件的可拓展性与可维护性,比如:

  1. 增强 useRobot.ts 中的 AIModelOptions。提供默认配置,允许在AI 插件配置层级进行新增与隐藏。

好处:将大模型提供商+模型的静态配置集中放置,容易阅读+可维护;也方便后续集中提供配置进行新增或者隐藏

增强示例结构:

export default {
   name: 'DeepSeek',
  apiBase: 'https://api.deepseek.com/v1',
  models: [
    {
        id: 'deepseek-chat',
        name: 'deepseek-chat',
        contextWindow: 65536, // 上下文大小
        maxTokens: 8192, 
        defaultMaxTokens: 8000,
        inputPrice: 0.0006, // 输入 token 价格
        outputPrice: 0.002, // 输出 token 价格
        isDefault: true,
        description: `60 tokens/second, Enhanced capabilities,API compatibility intact`, // 描述
        capabilities: { // 模型能力
            tools: {
              enabled: true,
            },
        },
    },
  ]
}
  1. 增强 modelProvider 能力。(clients/index.ts、clients/OpenAICompatibleProvider.ts)
    当前我们提供了基础的 openai compatible 的 modelProvider。
    建议:
  • 集成处理 tool_call 的能力(当前处理 tool_call 放置在了 useChat.ts 中)
  • 将 agent模式/chat 模式的处理,抽离出来。

好处:不同的大模型提供商、甚至不同的大模型的 tool_call 格式、以及传参可能都有细微的差别,我们将通用的处理模式全部内聚到一个 provider 里面,后续如果有定制化的需求,直接开放配置出来,让二开用户传入自己的 provider 即可处理 tool_call 格式、传参的相关差异。

总结:增强扩展性+高内聚

  1. 增加模式处理器(agent Mode、chat Mode、plan Mode 等等)
    不同的模式,可能 system prompt、可以调用的工具、可以调用的大模型不同。原本的一些处理我们在 useChat 和 modelProvider 中都有散落处理,可以考虑抽离一个 chatMode 之类的处理器,内聚处理不同模式的差异,然后再传递给 modelProvider。做到下层无感知。

@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically.


It feels like the code structure can be enhanced to enhance the scalability and maintainability of the plug-in, such as:

  1. Enhance AIModelOptions in useRobot.ts. Provides default configuration, allowing adding and hiding at the AI ​​plug-in configuration level.

Benefits: Centralize the static configuration of large model providers and models, making it easy to read and maintain; it also facilitates subsequent centralized provision of configurations for adding or hiding

Enhanced example structure:

export default {
   name: 'DeepSeek',
  apiBase: 'https://api.deepseek.com/v1',
  models: [
    {
        id: 'deepseek-chat',
        name: 'deepseek-chat',
        contextWindow: 65536, //Context size
        maxTokens: 8192,
        defaultMaxTokens: 8000,
        inputPrice: 0.0006, //Input token price
        outputPrice: 0.002, // Output token price
        isDefault: true,
        description: `60 tokens/second, Enhanced capabilities,API compatibility intact`, // description
        capabilities: { // Model capabilities
            tools: {
              enabled: true,
            },
        },
    },
  ]
}
  1. Enhance modelProvider capabilities. (clients/index.ts, clients/OpenAICompatibleProvider.ts)
    Currently we provide a basic openai compatible modelProvider.
    suggestion:
  • Integrated ability to handle tool_call (currently processing tool_call is placed in useChat.ts)
  • Extract the processing of agent mode/chat mode.

Benefits: Different large model providers, or even different large models, may have subtle differences in the tool_call format and parameter passing. We have integrated all common processing modes into one provider. If there is a need for customization in the future, the configuration can be directly opened, allowing secondary users to pass in their own providers to handle the differences in tool_call format and parameter passing.

Summary: Enhanced scalability + high cohesion

  1. Add mode processor (agent Mode, chat Mode, plan Mode, etc.)
    Different modes may have different system prompts, tools that can be called, and large models that can be called. We have scattered some of the original processing in useChat and modelProvider. We can consider extracting a processor such as chatMode to cohesively handle the differences in different modes, and then pass it to modelProvider. Make sure the lower level is insensitive.

@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from cd3d8c0 to 8005437 Compare October 20, 2025 11:51
@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from f8a401a to 0d7ca22 Compare October 28, 2025 10:09
@hexqi hexqi changed the title [WIP] feat: ai plugin refactor feat: ai plugin refactor Nov 3, 2025
@github-actions github-actions bot added documentation Improvements or additions to documentation enhancement New feature or request labels Nov 3, 2025
@hexqi hexqi marked this pull request as ready for review November 3, 2025 01:09
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/plugins/robot/src/components/McpServer.vue (1)

34-81: Make activeCount reactive to picker state.

activeCount is initialized with ref(1) and never updated, even though the picker exposes v-model:activeCount. The template now binds that model directly to activeCount, so you must remove the hard-coded default and rely on the picker to drive the value (or initialize it from real data) to avoid incorrect badge states.

packages/plugins/robot/src/components/RobotSettingPopover.vue (1)

204-220: Restore state.modelOptions removal side-effect.

state.modelOptions was removed in favor of computed modelOptions, but changeBaseUrl still assigns to state.modelOptions, leaving state.existFormData.model reading from undefined[0] on the next line. Update the assignment to use modelOptions.value (or remove the stale line) so selecting a provider always seeds the model dropdown correctly.

🧹 Nitpick comments (26)
packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue (1)

79-79: Consider using optional chaining for consistency.

While name is guaranteed to exist in SvgICons (since it's derived from Object.keys(SvgICons)), applying optional chaining here would maintain consistency with lines 57 and 61.

Apply this diff:

-        component: name && SvgICons[name]()
+        component: name && SvgICons[name]?.()
designer-demo/src/composable/http/index.js (1)

19-19: Good UX improvement; consider a more robust cancellation check.

Silencing canceled requests reduces noise and is appropriate. However, the string comparison message === 'canceled' is fragile—Axios may use different messages across versions or locales (e.g., 'cancelled'), and legitimate errors containing "canceled" could be suppressed.

Consider refactoring showError to accept the full error object and use Axios's built-in cancellation check:

-const showError = (url, message) => {
-  if (message === 'canceled') return // 取消请求场景不报错
+import axios from 'axios'
+
+const showError = (url, error) => {
+  if (axios.isCancel(error)) return // 取消请求场景不报错
+  const message = error?.message || error
   globalNotify({
     type: 'error',
     title: '接口报错',

Then update the call sites:

 const preResponse = (res) => {
   if (res.data?.error) {
-    showError(res.config?.url, res?.data?.error?.message)
+    showError(res.config?.url, res?.data?.error)
     return Promise.reject(res.data.error)
   }
 const errorResponse = (error) => {
   ...
-  showError(error.config?.url, error?.message)
+  showError(error.config?.url, error)
   return response?.data.error ? Promise.reject(response.data.error) : Promise.reject(error.message)
packages/plugins/robot/package.json (1)

37-37: Standardize version specifiers for consistency.

The dependencies use inconsistent version specifiers: fast-json-patch uses ~3.1.1 (patch-level), jsonrepair uses exact 3.13.0, and markdown-it uses ^14.1.0 (minor-level). For maintainability, adopt a consistent pinning strategy across similar utility dependencies.

-    "fast-json-patch": "~3.1.1",
+    "fast-json-patch": "^3.1.1",
-    "jsonrepair": "3.13.0",
+    "jsonrepair": "^3.13.0",

Also applies to: 39-40

packages/plugins/robot/src/types/types.ts (1)

3-8: Clarify the distinction between url and baseUrl.

RequestOptions now contains both url?: string (line 4) and baseUrl?: string (line 7). Additionally, LLMRequestBody (line 45) also has baseUrl?: string. This creates potential confusion about their roles and precedence. Consider:

  • If baseUrl is the API endpoint root and url is a path, document this clearly.
  • If they serve the same purpose, remove the duplication.
packages/plugins/robot/src/components/FooterButton.vue (1)

17-32: Potential reactivity issue: destructured props are not reactive.

In Vue 3 <script setup>, destructuring props (line 17) breaks reactivity. While the template binding at line 3 will work correctly, if handleVisibleToggle or any other composition logic directly accesses active, it won't be reactive.

Consider using defineProps() without destructuring:

-const { active, tooltipContent } = defineProps({
+const props = defineProps({
   active: {
     type: Boolean,
     default: false
   },
   tooltipContent: {
     type: String,
     default: ''
   }
 })

 const emit = defineEmits(['update:active'])

 const handleVisibleToggle = () => {
-  emit('update:active', !active)
+  emit('update:active', !props.active)
 }
packages/plugins/robot/index.ts (1)

25-25: Consider guarding debug initialization for production.

initDebugWindow() is called unconditionally at module load. Consider wrapping it in a development-only guard to avoid debug overhead in production builds.

-initDebugWindow()
+if (import.meta.env.DEV) {
+  initDebugWindow()
+}
packages/plugins/robot/src/composables/agent.ts (6)

19-24: Add type safety to fixIconComponent.

Line 20 accesses data?.componentName without a type guard, and line 21 assigns to data.props.name assuming the shape is correct. If data doesn't match the expected structure, this could cause runtime errors.

Consider adding a type guard:

-const fixIconComponent = (data: unknown) => {
-  if (data?.componentName === 'Icon' && data.props?.name && !SvgICons[data.props.name as keyof typeof SvgICons]) {
+const fixIconComponent = (data: any) => {
+  if (
+    isPlainObject(data) &&
+    data.componentName === 'Icon' &&
+    data.props?.name &&
+    typeof data.props.name === 'string' &&
+    !SvgICons[data.props.name as keyof typeof SvgICons]
+  ) {
     data.props.name = 'IconWarning'
     logger.log('autofix icon to warning:', data)
   }
 }

29-34: Add type safety to fixComponentName.

Line 30 assigns data.componentName = 'div' without proper type checking. TypeScript will allow this on object type, but it's not type-safe.

-const fixComponentName = (data: object) => {
-  if (isPlainObject(data) && !data.componentName) {
+const fixComponentName = (data: any) => {
+  if (isPlainObject(data) && !data.componentName) {
     data.componentName = 'div'
     logger.log('autofix component to div:', data)
   }
 }

42-42: Add explicit type to child parameter.

Line 42 uses any type for the child parameter. Consider defining a proper Schema type.

-    data.children.forEach((child: any) => schemaAutoFix(child))
+    data.children.forEach((child: unknown) => schemaAutoFix(child))

46-53: Add explicit types to arrow function parameters.

Lines 48-49 define an arrow function with implicit any types for patch, index, and arr.

 const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => {
   // 流式渲染过程中,画布只渲染children字段,避免不完整的methods/states/css等字段导致解析报错
-  const childrenFilter = (patch, index, arr) =>
+  const childrenFilter = (patch: any, index: number, arr: any[]) =>
     isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
   const validJsonPatches = jsonPatches.filter(childrenFilter).filter(useRobot().isValidFastJsonPatch)

   return validJsonPatches
 }

86-95: Clarify boolean parameters in applyPatch.

Line 88 calls jsonpatch.applyPatch(acc, [patch], false, false) with two boolean parameters. Without context, it's unclear what these flags control. Consider adding comments or using named options if the library supports them.

     try {
-      return jsonpatch.applyPatch(acc, [patch], false, false).newDocument
+      // Parameters: validateOperation=false, mutateDocument=false
+      return jsonpatch.applyPatch(acc, [patch], false, false).newDocument
     } catch (error) {

111-127: Silent error swallowing in search function.

The search function (lines 111-127) silently catches and ignores all errors at line 123-125, which makes debugging difficult. Consider logging the error or propagating it to the caller.

   } catch (error) {
-    // error
+    logger.error('Search API request failed:', error)
   }
   return result
 }
packages/plugins/robot/src/prompts/agent-prompt-en.md (1)

46-76: Fix Markdown list indentation for critical constraints.

The nested bullet lists under “Constraint Rules” violate Markdown indentation (MD007) and render incorrectly, which muddles non-negotiable output rules for the agent. Normalize list indentation so every sub-list is indented by two spaces per level; this keeps the rendered instructions clear and prevents lint failures.

packages/plugins/robot/src/components/RobotChat.vue (2)

326-333: Improve conversation title generation.

The automatic title generation uses a simple 20-character substring without considering word boundaries or multi-byte characters. For structured content (when messageContent is an object), JSON.stringify(messageContent).substring(0, 20) may produce an incomplete or invalid fragment like {"type":"image_url",.

Consider using a more robust approach:

     const currentTitle = conversationState.conversations.find(
       (conversation) => conversation.id === conversationState.currentId
     )?.title
     const DEFAULT_TITLE = '新会话'
     if (currentTitle === DEFAULT_TITLE && conversationState.currentId) {
-      const contentStr = typeof messageContent === 'string' ? messageContent : JSON.stringify(messageContent)
-      updateTitle(conversationState.currentId, contentStr.substring(0, 20))
+      let titleStr = typeof messageContent === 'string' ? messageContent : '图文消息'
+      // Trim to 20 chars at word boundary
+      if (titleStr.length > 20) {
+        titleStr = titleStr.substring(0, 20).trim() + '...'
+      }
+      updateTitle(conversationState.currentId, titleStr)
     }

91-363: Consider extracting business logic from the component.

The component handles UI rendering, file uploads, message formatting, history management, and error handling all in one place. While functional, this makes the component harder to test and maintain.

Consider extracting the following into separate composables:

  • File attachment handling (lines 163-218)
  • Message formatting and sending logic (lines 273-343)
  • History interaction handlers (lines 255-268)

This would align with the composable-driven architecture introduced elsewhere in this PR and improve testability.

packages/plugins/robot/src/utils/common-utils.ts (1)

32-48: Centralize model configuration defaults.

The function hardcodes 'deepseek-chat' as the default model (Line 35) and uses a fixed endpoint. Per the PR comments suggesting centralized model/provider configuration, these defaults should come from a shared configuration source rather than being scattered across utility functions.

Consider importing defaults from a centralized config:

Based on PR comments.

+import { DEFAULT_MODEL, DEFAULT_CHAT_ENDPOINT } from '../config/model-config'
+
 export const fetchLLM = async (messages: LLMMessage[], tools: RequestTool[], options: RequestOptions = {}) => {
   const bodyObj: LLMRequestBody = {
     baseUrl: options.baseUrl,
-    model: options?.model || 'deepseek-chat',
+    model: options?.model || DEFAULT_MODEL,
     stream: false,
     messages: toRaw(messages)
   }
   if (tools.length > 0) {
     bodyObj.tools = toRaw(tools)
   }
-  return getMetaApi(META_SERVICE.Http).post(options?.url || '/app-center/api/chat/completions', bodyObj, {
+  return getMetaApi(META_SERVICE.Http).post(options?.url || DEFAULT_CHAT_ENDPOINT, bodyObj, {
packages/plugins/robot/src/composables/useChat.ts (3)

204-223: Add circular reference protection to recursive merge.

The mergeStringFields function recursively merges objects without protection against circular references. While tool_calls data from LLM responses typically won't have circular structures, defensive coding suggests adding a WeakSet to track visited objects.

 /**
  * 合并字符串字段。如果值是对象,则递归合并字符串字段
  * @param target 目标对象
  * @param source 源对象
+ * @param visited WeakSet to track visited objects and prevent circular references
  * @returns 合并后的对象
  */
-const mergeStringFields = (target: Record<string, any>, source: Record<string, any>) => {
+const mergeStringFields = (
+  target: Record<string, any>,
+  source: Record<string, any>,
+  visited: WeakSet<object> = new WeakSet()
+) => {
+  if (visited.has(source)) return target
+  visited.add(source)
+
   for (const [key, value] of Object.entries(source)) {
     const targetValue = target[key]
 
     if (targetValue) {
       if (typeof targetValue === 'string' && typeof value === 'string') {
         // 都是字符串,直接拼接
         target[key] = targetValue + value
       } else if (targetValue && typeof targetValue === 'object' && value && typeof value === 'object') {
         // 都是对象,递归合并
-        target[key] = mergeStringFields(targetValue, value)
+        target[key] = mergeStringFields(targetValue, value, visited)
       }
     } else {

252-347: Ensure AbortController cleanup and consider splitting function.

The afterToolCallAbortController created at Line 262 is not explicitly cleaned up in all paths. While JavaScript GC will eventually collect it, explicitly setting it to null after completion improves clarity and prevents potential memory leaks if references are held elsewhere.

Additionally, this 95-line function handles both tool execution (lines 267-304) and subsequent streaming (lines 308-346). Consider splitting into:

  • executeToolCalls(tool_calls, messages, contextMessages)
  • streamToolCallResponse(toolMessages, currentMessage, messages)

For the cleanup issue:

       onDone: async () => {
         removeLoading(messages, 'latest')
         const toolCalls = messages.at(-1)!.tool_calls
         if (toolCalls?.length) {
           await handleToolCall(toolCalls, messages, toolMessages)
         } else {
+          afterToolCallAbortController = null
           getMessageManager().messageState.status = STATUS.FINISHED
         }
       }

349-361: Clarify conversation creation logic in mode switching.

The logic for when to create a new conversation versus updating the existing one is unclear. Line 353 checks if usedConversationId === newConversationId, which would only be true if createConversation returned the existing conversation ID (implying the conversation was empty and reused).

Consider adding a comment explaining this behavior, or refactoring to be more explicit:

 const changeChatMode = (chatMode: string) => {
-  // 空会话更新metadata
   const usedConversationId = conversationState.currentId
   const newConversationId = createConversation('新会话', { chatMode })
+  // If the conversation was reused (empty), update its metadata
+  // Otherwise, the new conversation already has the correct metadata
   if (usedConversationId === newConversationId) {
     rest.updateMetadata(newConversationId, { chatMode })
     rest.saveConversations()
   }
packages/plugins/robot/src/Main.vue (1)

198-202: Add a comment explaining the teleport timing logic.

The code combines three timing mechanisms (defer attribute, v-if="showTeleport", and a 1000ms delay) without explanation. While the target element .tiny-engine-right-robot exists in the layout component, the hardcoded delay appears redundant with Vue's defer support and warrants clarification.

Add a comment explaining whether:

  • The delay is needed due to specific component initialization order
  • Both defer and the timeout are intentional or if one can be removed
  • The 1000ms value is based on measured timing requirements or conservative estimation

Consider whether nextTick combined with a DOM observer or event-based approach would be more deterministic than a hardcoded delay.

packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (4)

51-81: Constructor looks good with minor documentation need.

The validation logic correctly ensures axiosClient is provided when httpClientType is 'axios'. However, the beforeRequest hook lacks documentation about what transformations are safe and whether async operations are fully supported in all code paths.

Consider adding JSDoc examples for the beforeRequest hook:

   /**
    * @param config AI模型配置
-   * @param options 额外选项
+   * @param options 额外选项
+   * @param options.beforeRequest 请求前处理钩子,可用于添加自定义参数或修改请求体。支持同步和异步函数。
+   * @example
+   * new OpenAICompatibleProvider(config, {
+   *   beforeRequest: (request) => ({ ...request, temperature: 0.7 })
+   * })
    */

169-249: Consider consolidating fetch logic to reduce duplication.

Both createFetchAdapter (lines 178-183) and sendFetchRequest (lines 236-241) perform nearly identical fetch calls with the same headers, body serialization, error checks, and response handling. This duplication increases maintenance burden.

Refactor to a shared helper:

+  private async executeFetch(
+    url: string, 
+    requestData: ChatRequestData, 
+    headers: Record<string, string>, 
+    signal?: AbortSignal
+  ): Promise<Response> {
+    const response = await fetch(url, {
+      method: 'POST',
+      headers,
+      body: JSON.stringify(requestData),
+      signal
+    })
+    
+    if (!response.ok) {
+      const errorText = await response.text()
+      throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`)
+    }
+    
+    return response
+  }

   private sendFetchRequest(...): Promise<Response> {
-    const response = await fetch(this.baseURL, {
-      method: 'POST',
-      headers,
-      body: JSON.stringify(requestData),
-      signal
-    })
-    ...
+    return this.executeFetch(this.baseURL, requestData, headers, signal)
   }

Then simplify createFetchAdapter to call executeFetch.


255-276: Custom adapter forces fetch even when using axios.

The method always injects createFetchAdapter (line 271), which means axios never uses its native request adapters (Node.js http/https or browser XMLHttpRequest). This undermines the flexibility of the httpClientType option.

If the goal is to support axios-specific features (interceptors, custom adapters, etc.), consider making the fetch adapter optional:

   private async sendAxiosRequest(...): Promise<unknown> {
     ...
     const requestOptions: AxiosRequestConfig = {
       method: 'POST',
       url: this.baseURL,
       headers,
       data: requestData,
-      signal,
-      adapter: this.createFetchAdapter(requestData, isStream)
+      signal
     }
+    
+    // Only inject fetch adapter if axios doesn't have native browser support
+    if (typeof window === 'undefined' || isStream) {
+      requestOptions.adapter = this.createFetchAdapter(requestData, isStream)
+    }
     
     const axiosClient = typeof this.axiosClient === 'function' ? this.axiosClient() : this.axiosClient

Alternatively, clarify in documentation that axios mode still uses fetch under the hood.


309-336: Streaming implementation works but has complex response extraction for axios.

Lines 320-322 perform nested response extraction that is hard to follow:

const fetchResponse = (
  (response as { data: { response: Response } }).data || (response as { response: Response })
).response

This suggests uncertainty about the axios response structure when using a custom adapter.

Simplify with explicit checks:

         const response = await this.sendAxiosRequest(requestData, headers, true, signal)
-        const fetchResponse = (
-          (response as { data: { response: Response } }).data || (response as { response: Response })
-        ).response
+        // Extract Response from axios wrapper
+        const axiosData = (response as any).data
+        const fetchResponse = (axiosData?.response || (response as any).response) as Response
+        if (!fetchResponse || typeof fetchResponse.body === 'undefined') {
+          throw new Error('Invalid streaming response structure from axios')
+        }
         await handleSSEStream(fetchResponse, handler, signal)

The abort handling (lines 331-333) is good—silent return prevents noisy errors from user cancellations.

packages/plugins/robot/src/composables/useRobot.ts (2)

134-138: Consider adding error handling for localStorage quota limits.

localStorage.setItem can throw QuotaExceededError if storage is full. While rare, it can cause unexpected errors.

Add graceful degradation:

 const saveRobotSettingState = (state: object) => {
   const currentState = loadRobotSettingState() || {}
   const newState = { ...currentState, ...state }
-  localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(newState))
+  try {
+    localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(newState))
+  } catch (error) {
+    console.error('Failed to save robot settings:', error)
+  }
 }

157-198: Consider moving JSON Patch validation to a separate utility.

These validation functions are generic and unrelated to robot settings or model configuration. Placing them in useRobot.ts reduces discoverability and reusability.

Extract to a dedicated file:

// packages/plugins/robot/src/utils/json-patch-validator.ts
export const isValidOperation = (operation: unknown): boolean => {
  // ... current implementation
}

export const isValidFastJsonPatch = (patch: unknown): boolean => {
  // ... current implementation
}

Then import in useRobot.ts:

+import { isValidOperation, isValidFastJsonPatch } from '../utils/json-patch-validator'
-const isValidOperation = (operation: object) => { ... }
-const isValidFastJsonPatch = (patch) => { ... }

This improves modularity and allows other modules to validate patches without importing robot settings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c27e843 and e9fb7d8.

⛔ Files ignored due to path filters (3)
  • packages/plugins/robot/assets/failed.svg is excluded by !**/*.svg
  • packages/plugins/robot/assets/success.svg is excluded by !**/*.svg
  • packages/plugins/robot/assets/test.png is excluded by !**/*.png
📒 Files selected for processing (37)
  • designer-demo/src/composable/http/index.js (1 hunks)
  • docs/extension-capabilities-tutorial/ai-plugin-configuration.md (1 hunks)
  • packages/common/js/completion.js (1 hunks)
  • packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue (1 hunks)
  • packages/layout/src/DesignSettings.vue (0 hunks)
  • packages/layout/src/Main.vue (1 hunks)
  • packages/plugins/robot/index.ts (1 hunks)
  • packages/plugins/robot/package.json (1 hunks)
  • packages/plugins/robot/src/BuildLoadingRenderer.vue (0 hunks)
  • packages/plugins/robot/src/Main.vue (2 hunks)
  • packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1 hunks)
  • packages/plugins/robot/src/client/index.ts (1 hunks)
  • packages/plugins/robot/src/components/AgentRenderer.vue (1 hunks)
  • packages/plugins/robot/src/components/FooterButton.vue (1 hunks)
  • packages/plugins/robot/src/components/ImgRenderer.vue (1 hunks)
  • packages/plugins/robot/src/components/McpServer.vue (1 hunks)
  • packages/plugins/robot/src/components/RobotChat.vue (1 hunks)
  • packages/plugins/robot/src/components/RobotSettingPopover.vue (10 hunks)
  • packages/plugins/robot/src/components/RobotTypeSelect.vue (3 hunks)
  • packages/plugins/robot/src/composables/agent.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/composables/useMcp.ts (2 hunks)
  • packages/plugins/robot/src/composables/useRobot.ts (1 hunks)
  • packages/plugins/robot/src/icons/mcp-icon.vue (0 hunks)
  • packages/plugins/robot/src/icons/page-icon.vue (0 hunks)
  • packages/plugins/robot/src/icons/study-icon.vue (0 hunks)
  • packages/plugins/robot/src/js/prompts.ts (0 hunks)
  • packages/plugins/robot/src/js/useRobot.ts (0 hunks)
  • packages/plugins/robot/src/js/utils.ts (0 hunks)
  • packages/plugins/robot/src/mcp/utils.ts (0 hunks)
  • packages/plugins/robot/src/prompts/agent-prompt-en.md (1 hunks)
  • packages/plugins/robot/src/prompts/components.json (1 hunks)
  • packages/plugins/robot/src/prompts/examples.json (1 hunks)
  • packages/plugins/robot/src/prompts/index.ts (1 hunks)
  • packages/plugins/robot/src/types/mcp-types.ts (1 hunks)
  • packages/plugins/robot/src/types/types.ts (1 hunks)
  • packages/plugins/robot/src/utils/common-utils.ts (1 hunks)
💤 Files with no reviewable changes (9)
  • packages/plugins/robot/src/icons/page-icon.vue
  • packages/plugins/robot/src/js/prompts.ts
  • packages/plugins/robot/src/icons/study-icon.vue
  • packages/plugins/robot/src/icons/mcp-icon.vue
  • packages/plugins/robot/src/BuildLoadingRenderer.vue
  • packages/plugins/robot/src/js/utils.ts
  • packages/plugins/robot/src/mcp/utils.ts
  • packages/plugins/robot/src/js/useRobot.ts
  • packages/layout/src/DesignSettings.vue
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-01-14T08:42:18.574Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1038
File: packages/plugins/block/index.js:24-24
Timestamp: 2025-01-14T08:42:18.574Z
Learning: In the tiny-engine project, breaking changes are documented in the changelog rather than in JSDoc comments or separate migration guides.

Applied to files:

  • packages/plugins/robot/src/prompts/agent-prompt-en.md
  • docs/extension-capabilities-tutorial/ai-plugin-configuration.md
  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue
  • packages/plugins/robot/src/components/RobotTypeSelect.vue
  • packages/plugins/robot/package.json
📚 Learning: 2024-10-15T02:45:17.168Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 830
File: packages/common/component/MetaChildItem.vue:50-56
Timestamp: 2024-10-15T02:45:17.168Z
Learning: In `packages/common/component/MetaChildItem.vue`, when checking if `text` is an object in the computed property `title`, ensure that `text` is not `null` because `typeof null === 'object'` in JavaScript. Use checks like `text && typeof text === 'object'` to safely handle `null` values.

Applied to files:

  • packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue
  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: The SvgIcon component is globally registered and available throughout the application without requiring explicit imports.

Applied to files:

  • packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.

Applied to files:

  • packages/plugins/robot/src/components/RobotTypeSelect.vue
  • packages/plugins/robot/src/components/RobotSettingPopover.vue
📚 Learning: 2025-01-14T04:25:46.281Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/material-function/material-getter.ts:66-80
Timestamp: 2025-01-14T04:25:46.281Z
Learning: In the tiny-engine project, styles from block components are processed through Vite's CSS compilation pipeline, and additional style sanitization libraries should be avoided to maintain consistency with this approach.

Applied to files:

  • packages/layout/src/Main.vue
  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T10:06:25.508Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1041
File: packages/plugins/datasource/src/DataSourceList.vue:138-138
Timestamp: 2025-01-14T10:06:25.508Z
Learning: PR #1041 in opentiny/tiny-engine is specifically for reverting Prettier v3 formatting to v2, without any logical code changes or syntax improvements.

Applied to files:

  • packages/layout/src/Main.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.

Applied to files:

  • packages/layout/src/Main.vue
  • packages/plugins/robot/package.json
📚 Learning: 2025-03-19T03:13:51.520Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1226
File: packages/canvas/container/src/components/CanvasDivider.vue:184-185
Timestamp: 2025-03-19T03:13:51.520Z
Learning: The CSS bug in packages/canvas/container/src/components/CanvasDivider.vue where verLeft already includes "px" but is being appended again in the style object will be fixed in a future update, as confirmed by gene9831.

Applied to files:

  • packages/layout/src/Main.vue
📚 Learning: 2025-01-14T04:22:02.404Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/builtin/builtin.json:645-850
Timestamp: 2025-01-14T04:22:02.404Z
Learning: In TinyEngine, components must use inline styles instead of CSS classes because components cannot carry class styles when dragged into the canvas.

Applied to files:

  • packages/layout/src/Main.vue
  • packages/plugins/robot/src/prompts/components.json
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
Repo: opentiny/tiny-engine PR: 1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.

Applied to files:

  • packages/plugins/robot/index.ts
  • packages/plugins/robot/package.json
📚 Learning: 2024-12-14T05:53:28.501Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 917
File: docs/开始/快速上手.md:31-31
Timestamp: 2024-12-14T05:53:28.501Z
Learning: The latest stable version of `opentiny/tiny-engine-cli` is `2.0.0`, and documentation should reference this version instead of any release candidates.

Applied to files:

  • packages/plugins/robot/package.json
🧬 Code graph analysis (8)
packages/plugins/robot/src/client/index.ts (1)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
  • OpenAICompatibleProvider (39-359)
packages/plugins/robot/src/composables/agent.ts (4)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/register/src/hooks.ts (1)
  • useHistory (82-82)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/types/mcp-types.ts (3)
  • ResponseToolCall (49-55)
  • LLMMessage (29-33)
  • RobotMessage (35-40)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/plugins/robot/src/composables/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/prompts/index.ts (1)
  • getAgentSystemPrompt (30-51)
packages/plugins/robot/src/composables/agent.ts (1)
  • updatePageSchema (109-109)
packages/plugins/robot/src/utils/common-utils.ts (2)
  • formatMessages (8-18)
  • serializeError (20-30)
packages/plugins/robot/src/utils/common-utils.ts (4)
packages/plugins/robot/src/types/types.ts (4)
  • LLMMessage (31-35)
  • RequestTool (10-29)
  • RequestOptions (3-8)
  • LLMRequestBody (44-50)
packages/plugins/robot/src/types/mcp-types.ts (4)
  • LLMMessage (29-33)
  • RequestTool (9-27)
  • RequestOptions (3-7)
  • LLMRequestBody (42-47)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/common-utils.ts (1)
  • formatMessages (8-18)
packages/plugins/robot/src/composables/useRobot.ts (1)
packages/register/src/common.ts (1)
  • getOptions (32-34)
packages/plugins/robot/src/composables/useMcp.ts (4)
packages/plugins/robot/src/types/types.ts (1)
  • RequestTool (10-29)
packages/plugins/robot/src/types/mcp-types.ts (1)
  • RequestTool (9-27)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/index.ts (1)
packages/plugins/robot/src/composables/index.ts (1)
  • RobotService (4-11)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/agent-prompt-en.md

46-46: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


47-47: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


48-48: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


49-49: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


50-50: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


51-51: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


52-52: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


53-53: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


54-54: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


55-55: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


56-56: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


57-57: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


59-59: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


60-60: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


61-61: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


62-62: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


63-63: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


64-64: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


65-65: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


67-67: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


68-68: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


69-69: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


70-70: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


71-71: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


72-72: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


73-73: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


74-74: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


75-75: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


76-76: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


77-77: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


78-78: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


79-79: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


80-80: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


81-81: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


82-82: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


83-83: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


84-84: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


85-85: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


86-86: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


103-103: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (16)
packages/layout/src/Main.vue (1)

153-158: CSS positioning context adjustment looks good.

Adding position: relative to .tiny-engine-right-wrap creates an explicit positioning context for the child <design-settings> component, which pairs with the removal of top-offset absolute positioning adjustments in DesignSettings.vue.

Please verify that the coordinated layout changes in DesignSettings.vue (removing top offset from absolute positioning) work correctly with this new relative positioning context. You can confirm this by testing that the right panel and settings components render and position correctly in the UI.

packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue (1)

57-57: LGTM! Defensive programming improvement.

The optional chaining prevents runtime errors when SvgICons[props.modelValue] is undefined, making the component more resilient to invalid icon names.

Also applies to: 61-61

packages/common/js/completion.js (1)

221-221: Good defensive coding practice.

The || '' fallback prevents the literal strings "undefined" or "null" from appearing in the Authorization header. While technically redundant given the early return guard on line 199 (which already ensures apiKey is truthy), this defensive approach improves code resilience against future refactoring and doesn't introduce any issues.

packages/plugins/robot/package.json (3)

35-35: Dependency is actively used in the refactored architecture.

Verification confirms @vueuse/core is properly utilized in packages/plugins/robot/src/composables/agent.ts: the useThrottleFn function throttles the updatePageSchema export (line 109) with a 200ms delay during schema updates. The agent.ts module is part of the composable-driven architecture and is imported by useChat.


29-29: The workspace dependency is correctly referenced, but a type-only circular reference exists.

The robot plugin's dependency on @opentiny/tiny-engine-meta-register is properly configured and actively used across 8 source files (Main.vue, RobotSettingPopover.vue, composables, utils, etc.). However, the meta-register package imports a type from the robot plugin (RobotService from @opentiny/tiny-engine-plugin-robot in packages/register/src/types.ts), creating a circular reference.

While this is a type-only import (safe from runtime circular dependencies), it aligns with the known circular dependency risks in tiny-engine projects mentioned in your learnings. Verify that your build system and module resolution handle this pattern correctly, or consider refactoring to move shared types to a neutral package if issues arise.


31-33: Verify RC version compatibility through manual testing.

The tiny-robot packages are updated to 0.3.0-rc.5 from 0.3.0-rc.0. Code analysis confirms these packages are actively used throughout the codebase:

  • @opentily/tiny-robot: BubbleContentItem, PromptProps, BubbleRoleConfig, McpServerPicker, PluginInfo
  • @opentiny/tiny-robot-kit: AIClient, AIModelConfig, BaseModelProvider, StreamHandler, ChatMessage, GeneratingStatus (used in provider integration, streaming, and tool calling)
  • @opentiny/tiny-robot-svgs: Icon components

No official changelog is available for these RC versions. Given the major refactoring in this PR and the use of RC versions, manually verify that the upgrade doesn't introduce API breaking changes, particularly around streaming responses, tool/plugin integration, and model configuration. Also note: @vueuse/core addition is justified (useThrottleFn is used in agent.ts).

docs/extension-capabilities-tutorial/ai-plugin-configuration.md (1)

19-19: LGTM!

The documentation path update correctly reflects the migration from src/js/useRobot.ts to src/composables/useRobot.ts.

packages/plugins/robot/src/components/FooterButton.vue (1)

116-125: Verify parent container has container-type set.

The @container query (line 116) requires the parent element to have container-type: inline-size or container-name set. Ensure the parent component using FooterButton sets this CSS property, otherwise the responsive behavior won't activate.

packages/plugins/robot/src/components/ImgRenderer.vue (1)

1-26: LGTM!

The ImgRenderer component is well-structured, with clear prop definitions and appropriate conditional rendering. The use of v-if ensures the TinyImage only renders when content is available.

packages/plugins/robot/src/prompts/components.json (1)

1-998: Static component catalog looks well-structured.

This comprehensive JSON catalog serves as a component registry for the AI agent. The structure is consistent across entries, with each component providing metadata, properties, events, and demo schemas. No structural issues detected.

packages/plugins/robot/src/utils/common-utils.ts (2)

8-18: LGTM: Message formatting is clean and correct.

The function properly unwraps Vue reactivity with toRaw, filters out invalid messages, and maps to a clean structure for the LLM API. The conditional spreading of tool_calls and tool_call_id is a good practice to avoid sending undefined fields.


50-79: Add null check for regex match result.

Line 68 performs a regex match but Line 71 accesses dataMatch[1] without checking if dataMatch is null. If the SSE format is malformed, this will throw a runtime error despite the try-catch block (the error would be caught, but it's better to handle it explicitly).

Apply this diff:

     try {
       // 解析SSE消息
       const dataMatch = line.match(/^data: (.+)$/m)
-      if (!dataMatch) continue
+      if (!dataMatch || !dataMatch[1]) continue
 
       const data = JSON.parse(dataMatch[1])
       handler.onData(data)

Additionally, Line 54's lines.pop() appears to remove a trailing empty element but the result is unused—consider adding a comment explaining this is intentional, or use lines.slice(0, -1) for clarity.

   const lines = data.split('\n\n')
-  lines.pop()
+  lines.pop() // Remove trailing empty element after final \n\n

Likely an incorrect or invalid review comment.

packages/plugins/robot/src/composables/useChat.ts (1)

119-162: Streaming delta handling is well-structured.

The onReceiveData handler effectively processes streaming responses, handling reasoning content, regular content, and tool_calls. The use of preventDefault() to override default behavior and the status tracking via chatStatus are good patterns.

The integration with updatePageSchema (Line 138) demonstrates the agent-mode functionality, though per the PR comments, further separation of agent vs chat mode logic into dedicated processors would improve maintainability.

packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (2)

152-163: LGTM—clean request preparation with proper fallbacks.

The method correctly strips Vue reactivity with toRaw, sanitizes messages via formatMessages, and applies a three-tier model fallback. The beforeRequest hook is awaited, supporting async transformations.


283-302: Tool_call handling is currently in useChat.ts—future refactor suggested but not required.

The non-streaming chat implementation at lines 283-302 correctly handles both axios and fetch paths with appropriate type assertions. Error handling properly wraps and rethrows exceptions.

Current architecture places tool_call processing in the composable layer (useChat.ts, lines 225-305). Moving this logic into the provider would improve cohesion and centralize provider-specific tool formatting. However, this is a future architectural improvement and not a blocker for the current implementation.

packages/plugins/robot/src/composables/useRobot.ts (1)

200-214: LGTM—standard Vue 3 composable pattern.

The factory function cleanly exposes the public API. The reactive robotSettingState allows components to observe configuration changes.

Comment on lines +87 to +126
private toAIAdapterError(error: unknown): AIAdapterError {
if (error instanceof Error) {
// 根据错误消息判断错误类型
const message = error.message.toLowerCase()
let type = ErrorType.UNKNOWN_ERROR
let statusCode: number | undefined

if (message.includes('http error')) {
const statusMatch = message.match(/status:\s*(\d+)/)
if (statusMatch) {
statusCode = parseInt(statusMatch[1], 10)
if (statusCode === 401 || statusCode === 403) {
type = ErrorType.AUTHENTICATION_ERROR
} else if (statusCode === 429) {
type = ErrorType.RATE_LIMIT_ERROR
} else if (statusCode >= 500) {
type = ErrorType.SERVER_ERROR
} else {
type = ErrorType.NETWORK_ERROR
}
}
} else if (message.includes('network') || message.includes('fetch')) {
type = ErrorType.NETWORK_ERROR
} else if (message.includes('timeout')) {
type = ErrorType.TIMEOUT_ERROR
}

return {
type,
message: error.message,
statusCode,
originalError: error
}
}

return {
type: ErrorType.UNKNOWN_ERROR,
message: String(error)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Error type detection relies on fragile string matching.

The method infers error types by searching for substrings like "http error", "network", "fetch", and "timeout" in lowercased error messages. This approach is brittle—error messages can vary across browsers, fetch implementations, and network stacks.

Consider enhancing error detection:

   private toAIAdapterError(error: unknown): AIAdapterError {
+    // Check for specific error types first
+    if (error instanceof TypeError && error.message.includes('fetch')) {
+      return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error }
+    }
+    if (error instanceof DOMException && error.name === 'AbortError') {
+      return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error }
+    }
+
     if (error instanceof Error) {
       const message = error.message.toLowerCase()

Alternatively, preserve structured error info (status codes, error names) from the source and rely less on message parsing.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private toAIAdapterError(error: unknown): AIAdapterError {
if (error instanceof Error) {
// 根据错误消息判断错误类型
const message = error.message.toLowerCase()
let type = ErrorType.UNKNOWN_ERROR
let statusCode: number | undefined
if (message.includes('http error')) {
const statusMatch = message.match(/status:\s*(\d+)/)
if (statusMatch) {
statusCode = parseInt(statusMatch[1], 10)
if (statusCode === 401 || statusCode === 403) {
type = ErrorType.AUTHENTICATION_ERROR
} else if (statusCode === 429) {
type = ErrorType.RATE_LIMIT_ERROR
} else if (statusCode >= 500) {
type = ErrorType.SERVER_ERROR
} else {
type = ErrorType.NETWORK_ERROR
}
}
} else if (message.includes('network') || message.includes('fetch')) {
type = ErrorType.NETWORK_ERROR
} else if (message.includes('timeout')) {
type = ErrorType.TIMEOUT_ERROR
}
return {
type,
message: error.message,
statusCode,
originalError: error
}
}
return {
type: ErrorType.UNKNOWN_ERROR,
message: String(error)
}
}
private toAIAdapterError(error: unknown): AIAdapterError {
// Check for specific error types first
if (error instanceof TypeError && error.message.includes('fetch')) {
return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error }
}
if (error instanceof DOMException && error.name === 'AbortError') {
return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error }
}
if (error instanceof Error) {
// 根据错误消息判断错误类型
const message = error.message.toLowerCase()
let type = ErrorType.UNKNOWN_ERROR
let statusCode: number | undefined
if (message.includes('http error')) {
const statusMatch = message.match(/status:\s*(\d+)/)
if (statusMatch) {
statusCode = parseInt(statusMatch[1], 10)
if (statusCode === 401 || statusCode === 403) {
type = ErrorType.AUTHENTICATION_ERROR
} else if (statusCode === 429) {
type = ErrorType.RATE_LIMIT_ERROR
} else if (statusCode >= 500) {
type = ErrorType.SERVER_ERROR
} else {
type = ErrorType.NETWORK_ERROR
}
}
} else if (message.includes('network') || message.includes('fetch')) {
type = ErrorType.NETWORK_ERROR
} else if (message.includes('timeout')) {
type = ErrorType.TIMEOUT_ERROR
}
return {
type,
message: error.message,
statusCode,
originalError: error
}
}
return {
type: ErrorType.UNKNOWN_ERROR,
message: String(error)
}
}

Comment on lines 24 to 123
"value": {
"componentName": "div",
"id": "25153243",
"props": {
"className": "component-base-style"
},
"children": [
{
"componentName": "h1",
"props": {
"className": "component-base-style"
},
"children": "消息列表",
"id": "53222591"
},
{
"componentName": "div",
"props": {
"className": "component-base-style div-uhqto",
"alignItems": "flex-start"
},
"children": [
{
"componentName": "div",
"props": {
"className": "component-base-style div-vinko",
"onClick": {
"type": "JSExpression",
"value": "this.onClickMessage",
"params": ["message", "index"]
},
"key": {
"type": "JSExpression",
"value": "index"
}
},
"children": [
{
"componentName": "Text",
"props": {
"style": "display: inline-block;",
"text": {
"type": "JSExpression",
"value": "message.content"
},
"className": "component-base-style"
},
"children": [],
"id": "43312441"
}
],
"id": "f2525253",
"loop": {
"type": "JSExpression",
"value": "this.state.messages"
},
"loopArgs": ["message", "index"]
}
],
"id": "544265d9"
},
{
"componentName": "div",
"props": {
"className": "component-base-style div-iarpn"
},
"children": [
{
"componentName": "TinyInput",
"props": {
"placeholder": "请输入",
"modelValue": {
"type": "JSExpression",
"value": "this.state.inputMessage",
"model": true
},
"className": "component-base-style",
"type": "textarea"
},
"children": [],
"id": "24651354"
},
{
"componentName": "TinyButton",
"props": {
"text": "发送",
"className": "component-base-style",
"onClick": {
"type": "JSExpression",
"value": "this.sendMessage"
}
},
"children": [],
"id": "46812433"
}
],
"id": "3225416b"
}
]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix example component IDs to satisfy schema rules.

Several component id values in this example ("25153243", "53222591", "f2525253", "3225416b" …) lack the required mix of uppercase letters, lowercase letters, and digits that Section 3.1 of the agent prompt mandates. Because these examples feed directly into the system prompt, they now teach the model an invalid pattern and will cause generated patches to fail schema validation. Please update every new component ID in this file to an 8-character string containing at least one uppercase letter, one lowercase letter, and one digit, and keep them unique (e.g., "a7Kp2sN9").

Comment on lines 9 to 49
const formatComponentsToJsonl = (components: any[]): string => {
return '```jsonl\n' + components.map((comp) => JSON.stringify(comp)).join('\n') + '\n```'
}

/**
* Format examples object to readable text
*/
const formatExamples = (examples: Record<string, any>): string => {
return Object.entries(examples)
.map(([_key, example]) => {
const { name, description, note, patch } = example
const header = `### ${name}\n${description ? `${description}\n` : ''}${note ? `**Note**: ${note}\n` : ''}`
const patchContent = JSON.stringify(patch)
return `${header}\n${patchContent}`
})
.join('\n\n')
}

/**
* Generate agent system prompt with dynamic components and examples
*/
export const getAgentSystemPrompt = (currentPageSchema: object, referenceContext: string) => {
// Format components list
const ignoreComponents = ['TinyNumeric'] // 组件报错,先忽略
const componentsList = formatComponentsToJsonl(
componentsData.filter((component) => !ignoreComponents.includes(component.component))
)

// Format examples section
const examplesSection = formatExamples(examplesData)

// Format current page schema
const currentPageSchemaStr = JSON.stringify(currentPageSchema)

// Replace all placeholders in the prompt template
const prompt = agentPrompt
.replace('{{COMPONENTS_LIST}}', componentsList)
.replace('{{EXAMPLES_SECTION}}', examplesSection)
.replace('{{CURRENT_PAGE_SCHEMA}}', currentPageSchemaStr)
.replace('{{REFERENCE_KNOWLEDGE}}', referenceContext || '')

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape braces when injecting JSON into Markdown.

formatExamples stringifies the patch array without escaping braces, so when injected into the Markdown template the raw {/} pairs can be interpreted as Markdown lists or template placeholders, breaking prompt formatting. Wrap the JSON in fenced code (or escape braces) before substitution to keep the agent prompt structurally sound.

🤖 Prompt for AI Agents
In packages/plugins/robot/src/prompts/index.ts around lines 9-49, the code
injects raw JSON into the Markdown prompt which allows unescaped `{`/`}` to be
treated as template/Markdown and break formatting; update formatExamples so the
patch JSON is wrapped in a fenced code block (e.g. ```json\n<JSON>\n```) instead
of inserting raw JSON, and ensure formatComponentsToJsonl already returns a
fenced jsonl block (or change it to do so) so both COMPONENTS_LIST and
EXAMPLES_SECTION are safe when .replace(...) injects them into the agent prompt.

Comment on lines 9 to 27
export interface RequestTool {
type: 'function'
function: {
name: string
description: string
parameters: {
type: 'object'
required?: string[]
properties: Record<
string,
{
type: string
description: string
[prop: string]: unknown
}
>
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consolidate duplicated RequestTool type definition.

RequestTool is defined in both mcp-types.ts (lines 9-27) and types/types.ts with slight differences:

  • types/types.ts includes an optional title?: string field in the function object
  • mcp-types.ts omits this field

This duplication creates maintenance burden and potential type inconsistencies. The useMcp.ts file imports RequestTool from types/types.ts, while other files might use this version from mcp-types.ts.

Consolidate to a single definition in one shared location:

// In types/types.ts - keep this as the canonical definition
export interface RequestTool {
  type: 'function'
  function: {
    name: string
    description: string
    title?: string  // Keep the optional title field
    parameters: {
      type: 'object'
      required?: string[]
      properties: Record<string, {
        type: string
        description: string
        [prop: string]: unknown
      }>
    }
  }
}

Then in mcp-types.ts:

+export type { RequestTool } from './types'
-export interface RequestTool {
-  type: 'function'
-  ...
-}
🤖 Prompt for AI Agents
In packages/plugins/robot/src/types/mcp-types.ts around lines 9 to 27 you have a
duplicated RequestTool interface that omits the optional title field present in
types/types.ts; replace the local definition by removing the interface here and
importing RequestTool from the canonical types/types.ts, update any local
references to use the imported type, and ensure the shared definition in
types/types.ts includes the optional title?: string in the function object so
all consumers use the same shape.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/plugins/robot/src/prompts/index.ts (1)

16-25: Consider consistent fencing for all JSON injections.

While formatComponentsToJsonl correctly wraps output in a fenced code block, formatExamples (line 21) and the CURRENT_PAGE_SCHEMA injection (line 47) insert raw JSON into the Markdown template. Raw {/} characters can be misinterpreted as template placeholders or Markdown syntax. For consistency and safety, consider wrapping both in fenced code blocks.

Apply this pattern to formatExamples:

 const formatExamples = (examples: Record<string, any>): string => {
   return Object.entries(examples)
     .map(([_key, example]) => {
       const { name, description, note, patch } = example
       const header = `### ${name}\n${description ? `${description}\n` : ''}${note ? `**Note**: ${note}\n` : ''}`
-      const patchContent = JSON.stringify(patch)
+      const patchContent = '```json\n' + JSON.stringify(patch, null, 2) + '\n```'
       return `${header}\n${patchContent}`
     })
     .join('\n\n')
 }

And consider similar treatment for CURRENT_PAGE_SCHEMA if appropriate for the LLM prompt structure.

Also applies to: 30-52

🧹 Nitpick comments (3)
packages/plugins/resource/src/ResourceList.vue (1)

430-430: LGTM: Ensures consistent payload structure.

The fallback to an empty string ensures the description field is always present in the batch creation payload, handling cases where uploaded files or incomplete URL entries lack a description.

Optional nitpick: Consider using the nullish coalescing operator (??) instead of logical OR (||) for semantic precision:

-            description: item.description || '',
+            description: item.description ?? '',

Both operators produce the same result here, but ?? more clearly expresses the intent to default only null or undefined values, whereas || also coerces other falsy values (though for string fields this distinction rarely matters).

packages/plugins/robot/src/prompts/agent-prompt-en.md (1)

1-213: Consider addressing markdown linting issues for consistency.

Static analysis flagged multiple formatting issues: inconsistent list indentation (MD007) and missing language specifiers for fenced code blocks (MD040). While these don't affect functionality, addressing them would improve document consistency and tooling compatibility.

packages/plugins/robot/src/composables/useChat.ts (1)

117-118: Consider encapsulating module-level state.

Module-level mutable variables (chatStatus, pageSchema, afterToolCallAbortController) complicate testing and prevent multiple independent instances. Consider moving them into the composable's closure or the conversation state.

Also applies to: 251-251

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3991d9 and 59d1acf.

📒 Files selected for processing (5)
  • packages/plugins/resource/src/ResourceList.vue (1 hunks)
  • packages/plugins/robot/src/composables/agent.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/prompts/agent-prompt-en.md (1 hunks)
  • packages/plugins/robot/src/prompts/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/plugins/robot/src/composables/agent.ts (4)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/register/src/hooks.ts (1)
  • useHistory (82-82)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/types/mcp-types.ts (3)
  • ResponseToolCall (49-55)
  • LLMMessage (29-33)
  • RobotMessage (35-40)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/plugins/robot/src/composables/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/composables/agent.ts (2)
  • fetchAssets (129-137)
  • updatePageSchema (109-109)
packages/plugins/robot/src/prompts/index.ts (1)
  • getAgentSystemPrompt (30-52)
packages/plugins/robot/src/utils/common-utils.ts (2)
  • formatMessages (8-18)
  • serializeError (20-30)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/agent-prompt-en.md

46-46: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


47-47: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


48-48: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


49-49: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


50-50: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


51-51: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


52-52: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


53-53: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


54-54: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


55-55: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


56-56: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


57-57: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


59-59: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


60-60: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


61-61: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


62-62: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


63-63: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


64-64: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


65-65: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


67-67: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


68-68: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


69-69: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


70-70: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


71-71: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


72-72: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


73-73: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


74-74: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


75-75: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


76-76: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


77-77: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


78-78: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


79-79: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


80-80: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


81-81: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


82-82: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


83-83: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


84-84: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


85-85: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


86-86: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


103-103: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (10)
packages/plugins/robot/src/composables/agent.ts (4)

19-34: LGTM!

The helper functions provide sensible fallbacks for malformed schema data. The icon validation against SvgICons and the default componentName assignment improve robustness.


55-107: Core streaming logic is well-structured.

The function correctly handles progressive JSON patch parsing, validation, and application with appropriate error handling. The use of jsonrepair for non-final content and the recursive patch application with error recovery are solid design choices.


109-109: Good use of throttling for streaming updates.

The 200ms throttle with leading edge enabled strikes a good balance between responsiveness and performance during streaming schema updates.


129-137: LGTM!

The asset fetching logic correctly filters for items with descriptions and maps to the expected shape.

packages/plugins/robot/src/prompts/index.ts (1)

9-11: LGTM!

Wrapping the JSONL output in a fenced code block ensures safe injection into the Markdown prompt template.

packages/plugins/robot/src/composables/useChat.ts (5)

1-52: Setup and configuration are well-structured.

The module initialization, type definitions, and helper functions provide a clean foundation for the chat workflow. The addSystemPrompt logic correctly handles both insertion and updates.


120-163: Event handlers correctly orchestrate the streaming workflow.

The onReceiveData and onFinish handlers properly manage streaming state, delta processing, and finalization. The conditional logic for agent mode vs. tool_calls is well-structured.


169-241: Delta handlers are well-designed.

The incremental rendering strategy with handleDeltaReasoning, handleDeltaContent, and handleDeltaToolCalls cleanly separates concerns. The mergeStringFields recursive merge is an elegant solution for assembling fragmented tool_call data.


253-348: Tool-calling flow is comprehensive and robust.

The handleToolCall function elegantly handles complex scenarios: tool execution, result streaming, abort signals, error recovery, and recursive tool chains. The separation of UI state management and message flow is well done.


350-396: Public API is well-designed.

The composable's exported interface cleanly encapsulates conversation management, mode switching, and request control. The abortRequest override correctly handles both the custom abort controller and the base message manager.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (6)
packages/plugins/robot/src/composables/agent.ts (2)

89-100: Rename isFinial typo.

Line 89 and Line 98 still expose the isFinial parameter. This typo leaks into the public API and keeps propagating to call sites. Please rename it to isFinal (and update all references) for clarity and consistency.

-const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => {
+const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => {
   // …
-    isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
+    isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
   const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch)
   return validJsonPatches
 }
 
-const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => {
+const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinal: boolean = false) => {
   // …
-    if (!isFinial) {
+    if (!isFinal) {
       content = jsonrepair(content)
     }
-    jsonPatches = JSON.parse(content)
+    jsonPatches = JSON.parse(content)
   } catch (error) {
-    if (isFinial) {
+    if (isFinal) {
       logger.error('parse json patch error:', error)
     }
     return { isError: true, error }
   }
 
-  if (!isFinial && !isValidFastJsonPatch(jsonPatches)) {
+  if (!isFinal && !isValidFastJsonPatch(jsonPatches)) {
     return { isError: true, error: 'format error: not a valid json patch.' }
   }
-  const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinial)
+  const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal)
   // …
-  if (isFinial) {
+  if (isFinal) {
     useHistory().addHistory()
   }

166-168: Preserve search errors for observability.

Line 167 silently swallows every exception. When META search fails we lose diagnostics, making field debugging painful. Please at least log via logger.error (or the module logger) before returning so we keep a trace.

-  } catch (error) {
-    // error
-  }
+  } catch (error) {
+    logger.error('AI search failed:', error)
+  }
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)

341-357: Make updateConfig honour httpClientType / axiosClient or narrow the signature.

Line 341 still accepts ProviderConfig, but the body only updates apiUrl, apiKey, and defaultModel; httpClientType and axiosClient are silently ignored. That breaks the contract for callers that try to switch transports at runtime. Please either support those fields (including validation) or change the signature to exclude them so the API is truthful.

packages/plugins/robot/src/Main.vue (1)

177-196: Add visible error feedback for file upload failures.

The error handling in handleFileSelected (lines 191-195) only logs to console and calls updateAttachment(''). Users receive no visible indication that the upload failed. Consider importing and using a notification component (e.g., Message or Notify from @opentiny/vue) to display an error message.

Based on past review comments.

Apply this diff to add error notification:

+import { Message } from '@opentiny/vue'

  // ... in handleFileSelected catch block:
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error('上传失败', error)
    updateAttachment('')
+   Message({
+     type: 'error',
+     message: '文件上传失败,请重试'
+   })
  }
packages/plugins/robot/src/composables/useRobot.ts (2)

112-119: Fix error handling to return consistent type.

When JSON parsing fails (line 117), the function returns items (a string) instead of an object. Callers expect an object and may destructure it (line 127), causing runtime errors.

Based on past review comments.

Apply this diff:

 const loadRobotSettingState = () => {
   const items = localStorage.getItem(SETTING_STORAGE_KEY) || '{}'
   try {
     return JSON.parse(items)
   } catch (error) {
-    return items
+    console.warn('Failed to parse robot settings, using defaults:', error)
+    return {}
   }
 }

127-142: Guard against empty model options array.

Lines 133, 135, 136, and 137 access getAIModelOptions()[0] without checking if the array is empty. If customCompatibleAIModels removes all default providers or returns an empty array, this will throw a runtime error.

Based on past review comments.

Apply this diff to add a guard:

+const defaultOptions = getAIModelOptions()
+if (defaultOptions.length === 0 || defaultOptions[0].models.length === 0) {
+  throw new Error('At least one AI model provider with models must be configured')
+}
+
 const robotSettingState = reactive({
   selectedModel: {
-    label: storageSettingState.label || getAIModelOptions()[0].label,
+    label: storageSettingState.label || defaultOptions[0].label,
     activeName: activeName || EXISTING_MODELS,
-    baseUrl: storageSettingState.baseUrl || getAIModelOptions()[0].value,
-    model: storageSettingState.model || getAIModelOptions()[0].models[0].value,
-    completeModel: storageSettingState.completeModel || getAIModelOptions()[0].models[0].value || '',
+    baseUrl: storageSettingState.baseUrl || defaultOptions[0].baseUrl,
+    model: storageSettingState.model || defaultOptions[0].models[0].name,
+    completeModel: storageSettingState.completeModel || defaultOptions[0].models[0].name || '',
     apiKey: storageSettingState.apiKey || ''
   },

Note: Also corrected field names from value to baseUrl and name.

🧹 Nitpick comments (6)
packages/plugins/robot/src/prompts/agent-prompt.md (4)

40-86: Comprehensive constraint rules; consolidate duplicate ID requirements.

The constraint rules are well-organized and provide clear error examples. However, the 8-character ID requirement is stated twice—once at line 86 and again at line 166 (in the spec section)—with slightly different phrasing. This duplication risks divergence if one is updated without the other.

Consider consolidating into a single, authoritative statement. Additionally, line 81 mentions CSS style string escaping but lacks a concrete example; adding one (e.g., ".style { color: red; }\n.other { margin: 0; }") would improve clarity.


88-132: Practical error examples; minor coverage gap.

The error examples effectively demonstrate JSON pitfalls. However, Example 4 shows only the "escape double quotes" approach; consider also showing the recommended alternative from line 80 (using single quotes in JS code): {"value":"function test() { console.log('hello') }"}. This would help agents understand the preferred pattern.


45-86: Fix markdown list indentation violations (MD007).

The nested list structure in the constraint rules uses 4–6 space indentation for visual emphasis, but markdownlint expects 0–2 space increments. While the current formatting aids readability of complex nested rules, it conflicts with standard markdown formatting.

Refactor to use consistent 2-space indentation increments per markdownlint (MD007):

  Constraint Rules:
-  * **Strictly Prohibited**:
-      * Any explanatory text, preamble, or closing remarks (e.g., "Here's the JSON you requested...")
+  * **Strictly Prohibited**:
+    * Any explanatory text, preamble, or closing remarks (e.g., "Here's the JSON you requested...")

Apply similar corrections throughout lines 46–86 to align all nested lists with 2-space increments.


93-93: Add language specifiers to fenced code blocks (MD040).

Code blocks in the error examples section lack language identifiers (markdownlint MD040). Specify the appropriate language for each block (typically text for raw JSON output, or json for JSON examples):

-**❌ Wrong Example 1**: Using JavaScript template literals (causes JSON parse failure)
-```
-{"value":"function test(name) { console.log(`hello ${name}`) }"}
-```
+**❌ Wrong Example 1**: Using JavaScript template literals (causes JSON parse failure)
+```json
+{"value":"function test(name) { console.log(`hello ${name}`) }"}
+```

Apply similar corrections to all unmarked code blocks at lines 93, 98, 103, 110, 120, 125, and 130.

Also applies to: 98-98, 103-103, 110-110, 120-120, 125-125, 130-130

packages/plugins/robot/src/components/RobotSettingPopover.vue (1)

24-39: Consider extracting the duplicated label template.

The label template with tooltip for "补全模型名称" is duplicated between the existing models tab (lines 24-39) and the customize tab (lines 76-92). Consider extracting this into a reusable component or template ref to reduce duplication.

Also applies to: 76-92

packages/plugins/robot/src/Main.vue (1)

155-161: Clean up placeholder code.

The function saveSettingState (line 161) is empty and handleChatModeChange has commented code (lines 157-158). If these are no longer needed, remove them to reduce dead code. If they're placeholders for future functionality, add a TODO comment.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59d1acf and ce65137.

📒 Files selected for processing (10)
  • packages/plugins/robot/meta.js (1 hunks)
  • packages/plugins/robot/src/Main.vue (2 hunks)
  • packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1 hunks)
  • packages/plugins/robot/src/components/RobotSettingPopover.vue (10 hunks)
  • packages/plugins/robot/src/composables/agent.ts (1 hunks)
  • packages/plugins/robot/src/composables/const.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/composables/useRobot.ts (1 hunks)
  • packages/plugins/robot/src/prompts/agent-prompt.md (1 hunks)
  • packages/plugins/robot/src/prompts/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/plugins/robot/src/prompts/index.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.

Applied to files:

  • packages/plugins/robot/src/components/RobotSettingPopover.vue
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。

Applied to files:

  • packages/plugins/robot/src/Main.vue
📚 Learning: 2024-10-09T01:47:35.507Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 817
File: packages/vue-generator/src/plugins/appendElePlusStylePlugin.js:46-50
Timestamp: 2024-10-09T01:47:35.507Z
Learning: In `appendElePlusStylePlugin.js`, the code uses `|| {}` to set default values when obtaining files, so additional null checks may not be necessary.

Applied to files:

  • packages/plugins/robot/src/composables/useRobot.ts
🧬 Code graph analysis (4)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/types/mcp-types.ts (3)
  • ResponseToolCall (49-55)
  • LLMMessage (29-33)
  • RobotMessage (35-40)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getOptions (32-34)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/plugins/robot/src/composables/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/composables/agent.ts (3)
  • search (154-170)
  • fetchAssets (172-180)
  • updatePageSchema (152-152)
packages/plugins/robot/src/prompts/index.ts (1)
  • getAgentSystemPrompt (30-52)
packages/plugins/robot/src/utils/common-utils.ts (2)
  • formatMessages (8-18)
  • serializeError (20-30)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/common-utils.ts (1)
  • formatMessages (8-18)
packages/plugins/robot/src/composables/agent.ts (4)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/register/src/hooks.ts (1)
  • useHistory (82-82)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/composables/useRobot.ts (2)
packages/register/src/common.ts (1)
  • getOptions (32-34)
packages/plugins/robot/src/composables/const.ts (1)
  • DEFAULT_LLM_MODELS (11-90)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/agent-prompt.md

46-46: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


47-47: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


48-48: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


49-49: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


50-50: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


51-51: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


52-52: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


53-53: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


54-54: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


55-55: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


56-56: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


57-57: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


59-59: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


60-60: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


61-61: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


62-62: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


63-63: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


64-64: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


65-65: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


67-67: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


68-68: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


69-69: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


70-70: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


71-71: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


72-72: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


73-73: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


74-74: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


75-75: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


76-76: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


77-77: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


78-78: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


79-79: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


80-80: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


81-81: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


82-82: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


83-83: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


84-84: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


85-85: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


86-86: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


103-103: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (18)
packages/plugins/robot/src/prompts/agent-prompt.md (5)

1-12: Clear and well-scoped mission statement.

The preamble effectively establishes the agent's role and early emphasis on JSON formatting constraints is helpful for setting expectations.


14-37: Well-structured operational workflow and validation steps.

The workflow and pre-output validation are comprehensive, addressing common JSON formatting failures. The fallback to [] on validation failure is a reasonable safeguard.


156-185: Clarify lifecycle function semantics to match runtime implementation.

The PageSchema and ComponentSchema specifications are comprehensive. However, the setup lifecycle example (line 157) deserves clarification: the destructuring pattern function({props, state, watch, onMounted}) suggests lifecycle hooks are passed as parameters, which differs from standard Vue 3 behavior. If this is a custom convention for TinyEngine, it should be explicitly documented. If it should follow Vue 3 semantics, the example should be corrected to show direct hook registration (e.g., onMounted(() => { ... })).

Verify that the lifecycle names and function signatures match the actual runtime behavior to avoid agent confusion.


187-199: Appropriate use of placeholder content.

This section correctly relies on runtime-injected content ({{COMPONENTS_LIST}}, {{EXAMPLES_SECTION}}) and reinforces the form component two-way binding preference. Consistent with the overall template design.


203-212: Clean placeholder structure for runtime context injection.

The context section is appropriately parameterized for runtime injection of page schema, reference knowledge, and image assets. Aligns with the template design pattern.

packages/plugins/robot/src/components/RobotSettingPopover.vue (5)

147-154: LGTM!

The destructured helpers from useRobot() align well with the new persistence and configuration workflow described in the PR objectives.


182-199: LGTM!

The computed option derivations are well-structured. The separation between compact and non-compact models is clear, and the reactive derivation from state.existFormData.baseUrl ensures the UI updates correctly when the provider changes.


213-228: LGTM!

The changeBaseUrl handler correctly resets dependent fields (apiKey, model, completeModel) when the provider changes, persists the updated state, and synchronizes with robotSettingState.selectedModel.


241-258: LGTM!

The validation flow has been corrected—saveRobotSettingState is now called inside the valid branch (lines 246-250), ensuring invalid forms are never persisted. The optional chaining on form.value?.validate is also a good safety improvement.


261-283: Review comment is incorrect. Upstream initialization properly guards the concern.

The getAIModelOptions() function in useRobot.ts (lines 96-102) includes a guard: if (!customAIModels.length) return DEFAULT_LLM_MODELS. Since DEFAULT_LLM_MODELS is a non-empty, predefined array with multiple model configurations, getAIModelOptions() always returns a non-empty array. Consequently, robotSettingState.selectedModel is always initialized with complete field values (baseUrl, model, apiKey, completeModel, label) at lines 135-138 of useRobot.ts. The code in RobotSettingPopover.vue lines 261-283 correctly relies on this initialization and requires no changes.

Likely an incorrect or invalid review comment.

packages/plugins/robot/src/Main.vue (5)

65-82: LGTM!

The migration to <script setup> with well-organized imports aligns with modern Vue composition API patterns. All imports appear necessary for the new component architecture.


94-112: LGTM!

The computed properties and toggleActive function are well-structured. The optional chaining in fullscreen (line 95) safely handles null refs, and toggleActive correctly persists the state change.


114-132: LGTM!

The promptItems array is well-structured with clear labels, descriptions, and icons. Using h() to render icon components is the correct approach for the composition API.


139-153: LGTM!

The isVisualModel and isToolsModel computed properties correctly derive capabilities from the selected model. The check toolCalling !== false (line 152) appropriately treats both true and undefined as tool-enabled.


198-202: LGTM!

The onMounted hook with a 1-second delay before showing the teleport ensures the target DOM element is ready. This is a reasonable approach for dealing with dynamic teleport targets.

packages/plugins/robot/src/composables/useRobot.ts (3)

19-33: LGTM!

The constants and comprehensive JSDoc for mergeAIModelOptions clearly document the merge behavior, including provider removal, model removal, merging, and overriding.


96-102: LGTM!

The getAIModelOptions function correctly implements extensibility by allowing custom models via customCompatibleAIModels from meta configuration, addressing the PR requirements for configurable model parameters.

Based on PR objectives.


144-155: LGTM!

The factory function export pattern provides a clean API surface, exposing all necessary functions and reactive state for consumption by components.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (5)
packages/plugins/robot/src/composables/useAgent.ts (2)

154-170: Add error logging to aid debugging.

The empty catch block (lines 166-168) suppresses all search errors. Consider logging errors to aid debugging, even if the function gracefully returns an empty result.

Apply this diff:

   } catch (error) {
-    // error
+    logger.error('Search failed:', error)
   }

89-89: Fix typo: isFinialisFinal.

The parameter name isFinial at lines 89 and 98 is a typo and should be isFinal. Update all references within both functions.

Apply this diff:

-const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => {
+const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => {
   // 流式渲染过程中,画布只渲染children字段,避免不完整的methods/states/css等字段导致解析报错
   const childrenFilter = (patch, index, arr) =>
-    isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
+    isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
   const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch)
 
   return validJsonPatches
 }
 
-const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => {
+const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinal: boolean = false) => {

And update all other references to isFinial within _updatePageSchema (lines 110, 115, 122, 125, 133, 145).

Also applies to: 98-98

packages/plugins/robot/src/Main.vue (1)

178-197: Add user notification for upload failures.

The catch block (lines 192-196) only logs to console. Users receive no visible feedback when upload fails, potentially causing confusion.

Apply this diff:

+import { TinyPopover, Notify } from '@opentiny/vue'
-import { TinyPopover } from '@opentiny/vue'

And update the catch block:

   } catch (error) {
     // eslint-disable-next-line no-console
     console.error('上传失败', error)
     updateAttachment('')
+    Notify({
+      type: 'error',
+      message: '文件上传失败,请重试',
+      position: 'top-right',
+      duration: 5000
+    })
   }
 }
packages/plugins/robot/src/components/chat/RobotChat.vue (2)

163-218: Fix type inconsistency and property access in file upload.

Two issues:

  1. Type mismatch: The function signature (line 163) accepts FileList | null, but when retry is true (line 217), a single File is passed via file.file. This creates type inconsistency.

  2. Wrong property: Line 217 accesses file.file, but line 185 shows the file is stored as rawFile, not file.

Apply this diff:

-const handleSingleFilesSelected = (files: FileList | null, retry = false) => {
+const handleSingleFilesSelected = (files: FileList | File | null, retry = false) => {
   if (retry) {
     singleAttachmentItems.value[0].status = 'uploading'
     singleAttachmentItems.value[0].isUploading = true
     singleAttachmentItems.value[0].messageType = 'uploading'
   } else {
-    if (!files.length) return
+    if (!files || (files instanceof FileList && !files.length)) return
 
-    if (files && files.length > 1) {
+    if (files instanceof FileList && files.length > 1) {
       Notify({
         type: 'error',
         message: '当前仅支持上传一张图片',
         position: 'top-right',
         duration: 5000
       })
       return
     }
 
-    if (files && files.length > 0) {
+    if (files instanceof FileList && files.length > 0) {
       // 将选中的文件转换为 Attachment 格式并添加到附件列表
       const newAttachments = Array.from(files).map((file) => ({

And fix the retry handler:

 const handleSingleFileRetry = (file: any) => {
-  handleSingleFilesSelected(file.file, true)
+  handleSingleFilesSelected(file.rawFile, true)
 }

345-348: Add bounds check to prevent runtime error.

Line 347 uses a non-null assertion (!) when accessing the last message. If messages.value is empty when the user cancels, this will throw a runtime error.

Apply this diff:

 const handleAbortRequest = () => {
   abortRequest()
-  messages.value.at(-1)!.aborted = true
+  const lastMessage = messages.value.at(-1)
+  if (lastMessage) {
+    lastMessage.aborted = true
+  }
 }
🧹 Nitpick comments (13)
packages/plugins/robot/src/prompts/data/components.json (1)

2-9: Box component demo uses generic div structure.

The Box demo shows "componentName": "div" instead of demonstrating the Box component itself. While this may be intentional (container components often don't have complex internal structure), consider whether a more representative example would better serve AI context generation. For consistency with other container patterns, you might show a Box with child components.

packages/plugins/robot/src/prompts/templates/agent-prompt.md (2)

45-86: Markdown list indentation needs correction (MD007).

The static analysis flagged 22 instances of improper unordered list indentation throughout the constraint rules section. While the content is clear and correct, standardized markdown formatting improves readability and maintainability.

Consider using a consistent indentation level (typically 2 spaces per level) across all nested lists. This will also resolve the MD007 linting errors.


93-133: Code blocks should specify language identifiers (MD040).

The error example code blocks (lines 93, 98, 103, 110, 120, 125, 130) lack language specifiers. Adding ```javascript or ```json will enable proper syntax highlighting and resolve MD040 linting violations.

packages/plugins/robot/src/prompts/data/examples.json (1)

1-146: Example structure is well-formed but mixes styling approaches.

The chatMessageList example demonstrates good JSON Patch structure with proper event binding, two-way binding via model: true, and string concatenation (avoiding template literals per spec). However, the example uses both inline styles (line 64: "style": "display: inline-block;") and className bindings to CSS classes defined in the /css section (lines 42, 49, 88, 128).

Per the TinyEngine best practice (from PR 1011), components should use inline styles instead of CSS classes to avoid losing styles when dragged in the canvas designer. Consider refactoring this example to prioritize inline styles over className bindings for maximum compatibility.

Replace className-based styling with inline styles. For example, line 42-43:

- "className": "component-base-style div-uhqto",
- "alignItems": "flex-start"
+ "style": "margin: 8px; display: flex; flex-direction: column; align-items: flex-start;"

This ensures the example aligns with inline-style best practices and improves component portability within the designer.

packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (1)

213-228: Consider auto-selecting the first compact model on baseUrl change.

When the base URL changes, completeModel is cleared (line 220) but not automatically set, unlike model which defaults to the first available model (line 219). For better UX, consider auto-selecting the first compact model if available.

Apply this enhancement:

  state.existFormData.label = provider?.label || ''
  state.existFormData.model = models[0]?.name || ''
- state.existFormData.completeModel = ''
+ const compactModels = provider?.models.filter(isCompactModel).map(modelTransformer) || []
+ state.existFormData.completeModel = compactModels[0]?.value || ''
packages/plugins/robot/src/components/renderers/ImgRenderer.vue (1)

22-25: Consider making the width configurable.

The fixed width of 200px may not suit all use cases. Consider exposing a width prop to allow parent components to customize the size.

+const { content, width } = defineProps({
-const { content } = defineProps({
   content: {
     type: String,
     default: ''
+  },
+  width: {
+    type: String,
+    default: '200px'
   }
 })
 .img-renderer-container {
-  width: 200px;
+  width: v-bind(width);
   height: auto;
 }
packages/plugins/robot/src/constants/model-config.ts (2)

15-15: Consider making base URLs configurable.

The base URLs for providers are hardcoded, which reduces flexibility for different deployment environments (development, staging, production) or for users who want to use custom API endpoints.

Consider moving these to environment variables or a separate config file:

export const DEFAULT_LLM_MODELS = [
  {
    provider: 'bailian',
    label: '阿里云百炼',
    baseUrl: import.meta.env.VITE_BAILIAN_BASE_URL || 'https://dashscope.aliyuncs.com/compatible-mode/v1',
    models: [
      // ...
    ]
  },
  {
    provider: 'deepseek',
    label: 'DeepSeek',
    baseUrl: import.meta.env.VITE_DEEPSEEK_BASE_URL || 'https://api.deepseek.com/v1',
    models: [
      // ...
    ]
  }
]

Also applies to: 73-73


11-90: Align with reviewer's recommendation to enhance model configuration schema.

The current model configuration provides a good foundation but could be enhanced based on the reviewer's suggestions to include additional metadata such as:

  • Context window size
  • Max tokens
  • Default max tokens
  • Input/output pricing
  • Default model flag
  • More detailed capability descriptions

This would improve extensibility and allow the UI to display richer information to users when selecting models.

Based on coding guidelines from the PR objectives. Consider expanding the model schema:

{
  label: 'Qwen 通用模型(Plus)',
  name: 'qwen-plus',
  contextWindow: 32768,
  maxTokens: 8192,
  defaultMaxTokens: 2000,
  inputPrice: 0.0004,  // per 1k tokens
  outputPrice: 0.0012,
  isDefault: true,
  description: 'Qwen Plus is a general-purpose model with strong reasoning capabilities',
  capabilities: {
    toolCalling: true,
    reasoning: reasoningExtraBody
  }
}
packages/plugins/robot/src/components/chat/FooterButton.vue (2)

78-113: Remove unused CSS classes or document their purpose.

The .plugin-common and .plugin-active classes are defined but not referenced in the template. If these are intended for future use or for consumers of this component, consider documenting this. Otherwise, remove them to reduce code bloat.

-  .plugin-common {
-    &_text {
-      font-size: 12px;
-      font-weight: 400;
-      line-height: 20px;
-      letter-spacing: 0;
-      text-align: left;
-    }
-
-    &_icon {
-      font-size: 16px;
-    }
-  }
-
-  .plugin-active {
-    &_count {
-      width: 12px;
-      height: 12px;
-      background: #1476ff;
-      // border-radius: 100%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-
-      font-size: 9px;
-      font-weight: 500;
-      line-height: 12px;
-      color: #fff;
-    }
-
-    &:hover {
-      color: #1476ff;
-      background-color: #eaf0f8;
-      border: 1px solid #1476ff;
-    }
-  }

97-97: Remove commented-out code.

Line 97 contains a commented-out border-radius: 100%. Either apply the style or remove the comment.

       width: 12px;
       height: 12px;
       background: #1476ff;
-      // border-radius: 100%;
       display: flex;
packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1)

95-101: Fixed width may cause layout issues on narrow screens.

The .build-loading-renderer-content-body has a fixed width of 160px, which might be too wide for very narrow screens or too narrow for wider layouts. Consider using max-width instead or making it responsive.

   &-body {
     color: var(--te-chat-model-helper-text);
     font-size: 12px;
-    width: 160px;
+    max-width: 160px;
     height: 30px;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
   }
packages/plugins/robot/src/utils/chat.utils.ts (2)

19-29: Enhance error serialization to capture stack traces.

The current error serialization only captures name and message, losing valuable debugging information like stack traces.

 export const serializeError = (err: unknown): string => {
   if (err instanceof Error) {
-    return JSON.stringify({ name: err.name, message: err.message })
+    return JSON.stringify({ 
+      name: err.name, 
+      message: err.message,
+      stack: err.stack 
+    })
   }
   if (typeof err === 'string') return err
   try {
     return JSON.stringify(err)
   } catch {
     return String(err)
   }
 }

58-74: Consider making hardcoded values configurable.

The function has several hardcoded values:

  • Default model: 'deepseek-chat' (line 61)
  • Stream flag: false (line 62)
  • API endpoint: '/app-center/api/chat/completions' (line 68)

While some are provided via options, the defaults reduce flexibility. Consider making these configurable via a config file or environment variables, especially for the API endpoint.

-export const fetchLLM = async (messages: LLMMessage[], tools: RequestTool[], options: RequestOptions = {}) => {
+export const fetchLLM = async (
+  messages: LLMMessage[], 
+  tools: RequestTool[], 
+  options: RequestOptions & { stream?: boolean } = {}
+) => {
   const bodyObj: LLMRequestBody = {
     baseUrl: options.baseUrl,
     model: options?.model || 'deepseek-chat',
-    stream: false,
+    stream: options.stream ?? false,
     messages: toRaw(messages)
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce65137 and 2bcbd32.

📒 Files selected for processing (29)
  • packages/plugins/robot/index.ts (1 hunks)
  • packages/plugins/robot/src/Main.vue (2 hunks)
  • packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1 hunks)
  • packages/plugins/robot/src/components/chat/FooterButton.vue (1 hunks)
  • packages/plugins/robot/src/components/chat/RobotChat.vue (1 hunks)
  • packages/plugins/robot/src/components/footer-extension/McpServer.vue (1 hunks)
  • packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue (3 hunks)
  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (11 hunks)
  • packages/plugins/robot/src/components/icons/mcp-icon.vue (0 hunks)
  • packages/plugins/robot/src/components/icons/page-icon.vue (0 hunks)
  • packages/plugins/robot/src/components/icons/study-icon.vue (0 hunks)
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1 hunks)
  • packages/plugins/robot/src/components/renderers/ImgRenderer.vue (1 hunks)
  • packages/plugins/robot/src/components/renderers/LoadingRenderer.vue (1 hunks)
  • packages/plugins/robot/src/composables/useAgent.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/composables/useConfig.ts (1 hunks)
  • packages/plugins/robot/src/constants/index.ts (1 hunks)
  • packages/plugins/robot/src/constants/model-config.ts (1 hunks)
  • packages/plugins/robot/src/metas/index.ts (1 hunks)
  • packages/plugins/robot/src/prompts/data/components.json (1 hunks)
  • packages/plugins/robot/src/prompts/data/examples.json (1 hunks)
  • packages/plugins/robot/src/prompts/index.ts (1 hunks)
  • packages/plugins/robot/src/prompts/templates/agent-prompt.md (1 hunks)
  • packages/plugins/robot/src/types/chat.types.ts (3 hunks)
  • packages/plugins/robot/src/types/index.ts (1 hunks)
  • packages/plugins/robot/src/types/mcp.types.ts (1 hunks)
  • packages/plugins/robot/src/utils/chat.utils.ts (1 hunks)
  • packages/plugins/robot/src/utils/index.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • packages/plugins/robot/src/components/icons/mcp-icon.vue
  • packages/plugins/robot/src/components/icons/study-icon.vue
  • packages/plugins/robot/src/components/icons/page-icon.vue
✅ Files skipped from review due to trivial changes (1)
  • packages/plugins/robot/src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/plugins/robot/src/prompts/index.ts
  • packages/plugins/robot/src/composables/useChat.ts
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-01-14T04:22:02.404Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/builtin/builtin.json:645-850
Timestamp: 2025-01-14T04:22:02.404Z
Learning: In TinyEngine, components must use inline styles instead of CSS classes because components cannot carry class styles when dragged into the canvas.

Applied to files:

  • packages/plugins/robot/src/prompts/data/components.json
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
Repo: opentiny/tiny-engine PR: 1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.

Applied to files:

  • packages/plugins/robot/index.ts
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.

Applied to files:

  • packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue
  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue
  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-15T02:19:06.755Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 940
File: packages/canvas/DesignCanvas/src/DesignCanvas.vue:0-0
Timestamp: 2025-01-15T02:19:06.755Z
Learning: In Vue components using message subscriptions from opentiny/tiny-engine-meta-register, always clean up subscriptions in the onUnmounted hook using useMessage().unsubscribe() to prevent memory leaks.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。

Applied to files:

  • packages/plugins/robot/src/Main.vue
🧬 Code graph analysis (5)
packages/plugins/robot/src/utils/chat.utils.ts (3)
packages/plugins/robot/src/types/chat.types.ts (4)
  • LLMMessage (32-36)
  • RequestTool (11-30)
  • RequestOptions (4-9)
  • LLMRequestBody (45-51)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/composables/useAgent.ts (4)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/register/src/hooks.ts (1)
  • useHistory (82-82)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/composables/useConfig.ts (2)
packages/register/src/common.ts (1)
  • getOptions (32-34)
packages/plugins/robot/src/constants/model-config.ts (1)
  • DEFAULT_LLM_MODELS (11-90)
packages/plugins/robot/src/types/chat.types.ts (1)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • formatMessages (7-17)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/prompts/templates/agent-prompt.md

46-46: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


47-47: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


48-48: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


49-49: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


50-50: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


51-51: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


52-52: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


53-53: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


54-54: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


55-55: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


56-56: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


57-57: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


59-59: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


60-60: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


61-61: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


62-62: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


63-63: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


64-64: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


65-65: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


67-67: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


68-68: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


69-69: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


70-70: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


71-71: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


72-72: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


73-73: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


74-74: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


75-75: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


76-76: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


77-77: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


78-78: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


79-79: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


80-80: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


81-81: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


82-82: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


83-83: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


84-84: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


85-85: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


86-86: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


103-103: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (23)
packages/plugins/robot/src/prompts/data/components.json (1)

1-997: Component catalog is well-structured and comprehensive.

The catalog provides solid reference data for AI prompt generation with consistent metadata structure (component name, properties, events, and realistic demos). All samples correctly use inline styles per TinyEngine best practices.

packages/plugins/robot/src/prompts/data/examples.json (1)

125-145: Methods are well-structured with proper string handling.

The sendMessage and onClickMessage methods correctly use string concatenation instead of template literals (per spec), and event parameter passing is correct. The example effectively demonstrates method definition and event binding patterns.

packages/plugins/robot/src/prompts/templates/agent-prompt.md (1)

1-622: System prompt template is comprehensive and correctly integrated with placeholder replacement logic.

The specification provides clear guidance for RFC 6902 JSON Patch generation with strict validation rules, good error examples, and complete PageSchema documentation. Verification confirms that all placeholders ({{COMPONENTS_LIST}}, {{EXAMPLES_SECTION}}, {{CURRENT_PAGE_SCHEMA}}, {{REFERENCE_KNOWLEDGE}}, {{IMAGE_ASSETS}}) in the template are correctly replaced by the generator in packages/plugins/robot/src/prompts/index.ts (lines 45-49).

packages/plugins/robot/src/components/renderers/LoadingRenderer.vue (1)

1-3: LGTM! Simple and functional loading indicator.

The template-only component is straightforward and serves its purpose well. The hardcoded dimensions and relative asset path are acceptable for this use case.

packages/plugins/robot/src/types/mcp.types.ts (1)

1-38: LGTM! Well-structured type definitions for MCP interactions.

The centralized type definitions appropriately separate MCP-related concerns from chat types. The flexible index signatures ([prop: string]: unknown) provide good extensibility while maintaining type safety for known fields.

packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (4)

51-81: Constructor validation is well-implemented.

The constructor properly validates that axiosClient is provided when httpClientType is set to 'axios', preventing runtime errors. The initialization logic with sensible defaults and the extensibility via the beforeRequest hook are good design choices.


132-162: LGTM! Header construction and request preparation are solid.

The buildHeaders method appropriately sets headers based on streaming mode and API key presence. The prepareRequestData method correctly applies message formatting, model selection precedence, and the beforeRequest hook with proper async handling.


168-224: Well-designed fetch adapter for axios compatibility.

The createFetchAdapter method effectively bridges fetch and axios interfaces with proper URL resolution, response structure mapping, and error handling. The JSON parsing error recovery is particularly good.


282-335: Chat methods handle streaming and non-streaming flows correctly.

Both chat and chatStream methods properly branch on httpClientType, handle errors appropriately, and the streaming method correctly honors abort signals by silently returning when aborted (lines 330-332).

packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (2)

182-199: Well-structured computed properties for dynamic model options.

The computed properties effectively derive model options from the selected provider, with clear separation between regular and compact models. The helper functions (modelTransformer, isCompactModel, getProviderByBaseUrl) improve readability.


48-53: Good security improvement with password input type.

Changing the API key inputs to type="password" prevents accidental exposure of sensitive credentials during screen sharing or over-the-shoulder viewing.

Also applies to: 100-105

packages/plugins/robot/index.ts (1)

17-17: LGTM! Import path updated to match refactored structure.

The updated import path correctly references the new location of RobotService in the refactored codebase structure.

packages/plugins/robot/src/constants/index.ts (1)

1-1: LGTM! Standard barrel export for constants.

The barrel export provides centralized access to model configuration constants, following common JavaScript/TypeScript patterns.

packages/plugins/robot/src/utils/index.ts (1)

1-1: LGTM! Barrel export for chat utilities.

The export provides centralized access to chat-related utilities, aligning with the refactored architecture.

packages/plugins/robot/src/types/chat.types.ts (3)

2-2: Good type organization improvements.

Importing ResponseToolCall from the dedicated mcp.types module improves type organization. The addition of baseUrl to RequestOptions provides necessary flexibility for multi-provider scenarios.

Also applies to: 8-8


40-40: Enhanced content type enables rich message rendering.

The union type string | BubbleContentItem[] maintains backward compatibility while enabling structured content rendering, aligning with the enhanced chat UI capabilities in this refactor.


58-58: Type reference correctly updated.

The tool_calls type now references ResponseToolCall from mcp.types, fixing the previous typo ("ReponseToolCall") and aligning with the centralized MCP type definitions.

packages/plugins/robot/src/components/footer-extension/McpServer.vue (1)

3-32: LGTM! Clean refactoring to use FooterButton component.

The refactoring successfully extracts the button UI into a reusable FooterButton component while maintaining the existing functionality. The active state is correctly derived from activeCount > 0, and the visibility toggle logic is properly wired.

packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1)

33-54: Verify safe handling of content property in statusDataMap.

Lines 37 and 42 use props.content?.slice(-30), which correctly uses optional chaining. However, if content is an empty string, slice(-30) returns an empty string, which may not provide useful feedback to users during the reasoning/loading states.

Consider adding a fallback message:

 reasoning: {
   title: '深度思考中,请稍等片刻',
   icon: 'loading.webp',
-  content: () => props.content?.slice(-30)
+  content: () => props.content?.slice(-30) || '正在处理中...'
 },
 loading: {
   title: '页面生成中,请稍等片刻',
   icon: 'loading.webp',
-  content: () => props.content?.slice(-30)
+  content: () => props.content?.slice(-30) || '正在生成页面...'
 },
packages/plugins/robot/src/components/footer-extension/RobotTypeSelect.vue (2)

62-68: LGTM! Clean computed pattern for v-model binding.

The computed getter/setter pattern correctly implements two-way binding with the parent component. The guard on line 65 prevents unnecessary event emissions when the value hasn't actually changed.


55-58: No breaking change detected in this codebase.

Verification found:

  • Zero usages of the old aiType or ai-type prop anywhere in the codebase
  • RobotTypeSelect has only one consumer: Main.vue (line 41)
  • The consumer already uses the new chatMode prop correctly: :chatMode="robotSettingState.chatMode"
  • All related state management code already references chatMode, not the old prop name

Since no code in the repository uses the old prop name, this is not a breaking change within this codebase. The concern raised in the review comment does not apply to the actual state of the code.

Likely an incorrect or invalid review comment.

packages/plugins/robot/src/utils/chat.utils.ts (1)

7-17: LGTM! Correct message formatting with reactive proxy unwrapping.

The formatMessages function correctly:

  • Uses toRaw() to unwrap Vue reactive proxies before sending to external APIs
  • Filters out invalid messages (those without content, tool_calls, or tool_call_id)
  • Conditionally includes optional fields to keep the payload clean
packages/plugins/robot/src/metas/index.ts (1)

7-10: Review comment is incorrect. The code does not have reactivity issues.

The useModelConfig() composable implements a module-scope singleton pattern via Vue's reactive() and closure:

  • robotSettingState is created once at module load using reactive() (not re-created on each call)
  • Each call to useModelConfig() returns a wrapper object referencing the same underlying reactive instance via closure
  • Calling useModelConfig() twice at module scope (lines 8-9) accesses the same reactive state both times—no separate instances are created

Vue's composables can call reactive() at module scope; this is a valid singleton pattern used throughout the codebase. The current code is functionally correct.

Minor note: There is a minor inefficiency (creating two wrapper objects instead of one), but this is not a correctness or reactivity concern.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (9)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (2)

87-126: Error type detection relies on fragile string matching.

The method infers error types by searching for substrings in lowercased error messages (lines 94-111). This approach is brittle—error messages can vary across browsers and network stacks.

Consider enhancing error detection:

   private toAIAdapterError(error: unknown): AIAdapterError {
+    // Check for specific error types first
+    if (error instanceof TypeError && error.message.includes('fetch')) {
+      return { type: ErrorType.NETWORK_ERROR, message: error.message, originalError: error }
+    }
+    if (error instanceof DOMException && error.name === 'AbortError') {
+      return { type: ErrorType.TIMEOUT_ERROR, message: error.message, originalError: error }
+    }
+
     if (error instanceof Error) {
       const message = error.message.toLowerCase()

342-358: updateConfig has incomplete ProviderConfig support.

The method accepts ProviderConfig but only updates baseURL, apiKey, and defaultModel, silently ignoring httpClientType and axiosClient. This creates a type/behavior mismatch.

Consider either:

  1. Narrow the signature to exclude immutable properties:
    updateConfig(config: Omit<ProviderConfig, 'httpClientType' | 'axiosClient'>): void
  2. Or add handling for these properties with appropriate validation.
packages/plugins/robot/src/composables/useChat.ts (3)

55-99: Consider refactoring to align with provider-layer architecture.

Per the PR comments, tool_call handling and mode-specific logic (agent vs chat) should be encapsulated within the provider layer rather than in a composable's beforeRequest hook. The current implementation mixes concerns across tool integration, agent-mode prompts, and model capability checks.

Based on PR comments.

This would improve separation of concerns and make it easier to add new modes without modifying core chat logic.


55-99: Guard reasoning extraBody assignments against null.

Line 87-92: When robotSettingState.enableThinking is false, the code may pass null to Object.assign, which throws TypeError: Cannot convert undefined or null to object. This occurs for models like bailian where extraBody.disable is null.

Apply this diff:

   if (modelCapabilities?.reasoning?.extraBody) {
-    Object.assign(
-      requestParams,
-      robotSettingState.enableThinking
-        ? modelCapabilities.reasoning.extraBody.enable
-        : modelCapabilities.reasoning.extraBody.disable
-    )
+    const extraBody = robotSettingState.enableThinking
+      ? modelCapabilities.reasoning.extraBody.enable
+      : modelCapabilities.reasoning.extraBody.disable
+    if (extraBody) {
+      Object.assign(requestParams, extraBody)
+    }
   }

147-203: Fix await updatePageSchema(...) runtime crash.

Line 190 awaits updatePageSchema, but updatePageSchema is the throttled wrapper from useAgent.ts (line 193) returned by useThrottleFn, which returns void, not a Promise. This will cause incorrect behavior when the agent reaches finalization.

Consider exporting an unthrottled helper from useAgent.ts for final updates:

In packages/plugins/robot/src/composables/useAgent.ts:

export const updatePageSchemaImmediate = _updatePageSchema
export const updatePageSchema = useThrottleFn(_updatePageSchema, 200, true)

Then in useChat.ts:

+import { updatePageSchema, updatePageSchemaImmediate } from './useAgent'

...

-        const result = await updatePageSchema(lastMessage.content, pageSchema, true)
+        const result = updatePageSchemaImmediate(lastMessage.content, pageSchema, true)
packages/plugins/robot/src/composables/useAgent.ts (3)

108-115: Fix typo: isFinialisFinal.

The parameter name isFinial appears to be a typo and should be isFinal for clarity.

Apply this diff:

-const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => {
+const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => {
   // 流式渲染过程中,画布只渲染完整的字段或流式的children字段,避免不完整的methods/states/css等字段导致解析报错
   const childrenFilter = (patch, index, arr) =>
-    isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
+    isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
   const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch)

140-191: Fix typo: isFinialisFinal throughout this function.

The parameter and variable name isFinial (line 140 and throughout) should be isFinal for consistency.

Apply this diff:

-const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => {
+const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinal: boolean = false) => {
   const { robotSettingState, CHAT_MODE } = useModelConfig()
   if (robotSettingState.chatMode !== CHAT_MODE.Agent) {
     return
   }

   // 解析流式返回的schema patch
   let content = getJsonObjectString(streamContent)
   let jsonPatches = []
   try {
-    if (!isFinial) {
+    if (!isFinal) {
       content = jsonrepair(content)
     }
     jsonPatches = JSON.parse(content)
   } catch (error) {
-    if (isFinial) {
+    if (isFinal) {
       logger.error('parse json patch error:', error)
     }
     return { isError: true, error }
   }

   // 过滤有效的json patch
-  if (!isFinial && !isValidFastJsonPatch(jsonPatches)) {
+  if (!isFinal && !isValidFastJsonPatch(jsonPatches)) {
     return { isError: true, error: 'format error: not a valid json patch.' }
   }
-  const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinial)
+  const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal)

   // 生成新schema
   const originSchema = deepClone(currentPageSchema)
   const newSchema = validJsonPatches.reduce((acc: object, patch: any) => {
     try {
       return jsonpatch.applyPatch(acc, [patch], false, false).newDocument
     } catch (error) {
-      if (isFinial) {
+      if (isFinal) {
         logger.error('apply patch error:', error, patch)
       }
       return acc
     }
   }, originSchema)

   // schema纠错
   fixMethods(newSchema.methods)
   schemaAutoFix(newSchema.children)

   // 更新Schema
   setSchema(newSchema)
-  if (isFinial) {
+  if (isFinal) {
     useHistory().addHistory()
   }

   return { schema: newSchema, isError: false }
 }

195-211: Silent error handling may hide issues.

The empty catch block (lines 207-209) suppresses all search errors. Consider logging errors to aid debugging.

Apply this diff:

   } catch (error) {
-    // error
+    logger.error('Search failed:', error)
   }
packages/plugins/robot/src/prompts/index.ts (1)

16-25: Escape braces when injecting JSON into Markdown.

Line 21 stringifies the patch array without escaping braces, so raw {/} pairs in the template can be interpreted as Markdown syntax, potentially breaking prompt formatting.

Apply this diff:

 const formatExamples = (examples: Record<string, any>): string => {
   return Object.entries(examples)
     .map(([_key, example]) => {
       const { name, description, note, patch } = example
       const header = `### ${name}\n${description ? `${description}\n` : ''}${note ? `**Note**: ${note}\n` : ''}`
-      const patchContent = JSON.stringify(patch)
-      return `${header}\n${patchContent}`
+      const patchContent = '```json\n' + JSON.stringify(patch, null, 2) + '\n```'
+      return `${header}\n\n${patchContent}`
     })
     .join('\n\n')
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2bcbd32 and eee31c8.

📒 Files selected for processing (5)
  • packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1 hunks)
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1 hunks)
  • packages/plugins/robot/src/composables/useAgent.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/prompts/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.

Applied to files:

  • packages/plugins/robot/src/composables/useChat.ts
📚 Learning: 2025-01-14T06:50:21.158Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/data-function/parser.ts:191-195
Timestamp: 2025-01-14T06:50:21.158Z
Learning: The `newFn` function in `packages/canvas/render/src/data-function/parser.ts` has a known security issue with evaluating untrusted code through `data.value`. This was identified during code review but intentionally deferred as the original implementation was kept during code refactoring.

Applied to files:

  • packages/plugins/robot/src/composables/useChat.ts
🧬 Code graph analysis (3)
packages/plugins/robot/src/composables/useAgent.ts (5)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • serializeError (19-29)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/register/src/hooks.ts (1)
  • useHistory (82-82)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • formatMessages (7-17)
packages/plugins/robot/src/composables/useChat.ts (9)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getOptions (32-34)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/types/chat.types.ts (2)
  • LLMMessage (32-36)
  • RobotMessage (38-43)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/plugins/robot/src/composables/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/composables/useAgent.ts (4)
  • search (195-211)
  • fetchAssets (213-225)
  • updatePageSchema (193-193)
  • isValidJsonPatchObjectString (123-138)
packages/plugins/robot/src/prompts/index.ts (2)
  • getAgentSystemPrompt (30-52)
  • getJsonFixPrompt (56-87)
packages/plugins/robot/src/utils/chat.utils.ts (3)
  • mergeStringFields (37-56)
  • formatMessages (7-17)
  • serializeError (19-29)
🔇 Additional comments (38)
packages/plugins/robot/src/composables/useAgent.ts (9)

1-12: LGTM!

The imports and initial setup are appropriate. Using console as the logger is acceptable for this context.


14-18: LGTM!

The setSchema function correctly updates the canvas schema and marks it as unsaved.


20-25: LGTM!

The icon fallback logic appropriately handles missing icons by defaulting to IconWarning.


30-35: LGTM!

The function correctly avoids fixing patch operation objects by checking for op and path properties.


55-63: LGTM!

The recursive schema auto-fix logic correctly handles arrays and nested children.


65-97: LGTM! Note non-standard _get operation.

The validation logic is comprehensive and correct. The _get operation (line 66) is a non-standard extension to RFC 6902 but appears to be intentionally supported.


99-106: LGTM!

The patch validation correctly handles both single operations and arrays.


193-193: LGTM!

The 200ms throttle with leading edge is appropriate for streaming schema updates.


213-225: LGTM!

The function appropriately returns an empty array on error, which is acceptable for this non-critical helper.

packages/plugins/robot/src/client/OpenAICompatibleProvider.ts (9)

1-37: LGTM!

The type definitions and imports are well-structured and appropriate for an OpenAI-compatible provider implementation.


39-81: LGTM!

The constructor properly validates configuration, especially the axios client requirement when httpClientType is set to 'axios'.


132-146: LGTM!

The header construction logic correctly handles streaming and authentication scenarios.


152-163: LGTM!

The request preparation logic correctly applies the model selection waterfall and beforeRequest hook.


169-225: LGTM!

The fetch adapter correctly handles URL construction, streaming responses, and JSON parsing with appropriate error handling.


231-249: LGTM!

The fetch request implementation properly handles errors and includes helpful error details.


255-276: LGTM!

The axios request handling correctly validates client presence and handles both function and instance forms of axiosClient.


283-302: LGTM!

The non-streaming chat method correctly branches on HTTP client type and handles responses appropriately.


309-336: LGTM!

The streaming implementation correctly handles both transport types, abort signals, and error mapping.

packages/plugins/robot/src/prompts/index.ts (4)

1-4: LGTM!

The imports appropriately use raw string imports for Markdown templates and JSON data.


9-11: LGTM!

The function correctly wraps the JSONL output in a fenced code block, preventing Markdown interpretation issues.


30-52: LGTM!

The prompt generation logic correctly filters components, formats sections, and replaces placeholders. The TinyNumeric exclusion (line 32) is appropriately documented.


54-87: LGTM!

Both functions provide clear, comprehensive prompts. The JSON fix prompt includes detailed format requirements and examples.

packages/plugins/robot/src/components/renderers/AgentRenderer.vue (4)

1-9: LGTM!

The template cleanly renders status information with appropriate conditional rendering.


11-27: LGTM!

The props are well-typed with appropriate defaults.


28-80: LGTM!

The setup logic appropriately handles different status states and dynamically resolves content. The reasoning completion logic (line 61) correctly determines visibility.


84-109: LGTM!

The styles appropriately handle layout and text overflow scenarios.

packages/plugins/robot/src/composables/useChat.ts (12)

1-34: LGTM!

The imports and initial setup are comprehensive and appropriate for the chat composable.


36-45: LGTM!

The configuration object is well-structured and appropriately uses the meta API for HTTP client access.


47-53: LGTM!

The function correctly handles both adding new system prompts and updating existing ones.


101-101: LGTM!

The client creation appropriately passes config and beforeRequest hook.


103-106: LGTM!

The config update function correctly updates both provider and local configuration.


108-120: LGTM!

The loading removal logic correctly handles both specific and latest loading indicators.


205-207: LGTM!

The conversation manager initialization correctly passes the client and event handlers.


209-254: LGTM!

The delta handlers correctly process reasoning, content, and tool_calls, with appropriate incremental merging logic for tool call chunks.


256-262: LGTM!

The argument parsing appropriately falls back to raw string on JSON parse errors.


264-359: LGTM!

The tool call orchestration correctly handles execution, error states, abort signals, and recursive tool call chains.


361-373: LGTM!

The chat mode change logic correctly updates metadata, persists state, and adjusts the API URL.


375-407: LGTM!

The exported API comprehensively exposes chat functionality with appropriate abort handling and conversation mode restoration.

Comment on lines 37 to 53
const fixMethods = (methods: Record<string, any>) => {
if (methods && Object.keys(methods).length) {
Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => {
if (
typeof methodValue !== 'object' ||
methodValue?.type !== 'JSFunction' ||
!methodValue?.value.startsWith('function')
) {
methods[methodName] = {
type: 'JSFunction',
value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}'
}
logger.log('autofix method to empty function:', methodName, methods[methodName])
}
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against non-string value before calling startsWith.

Line 43 calls .startsWith('function') on methodValue?.value without verifying it's a string, which could throw if value is not a string type.

Apply this diff:

       if (
         typeof methodValue !== 'object' ||
         methodValue?.type !== 'JSFunction' ||
-        !methodValue?.value.startsWith('function')
+        typeof methodValue?.value !== 'string' ||
+        !methodValue.value.startsWith('function')
       ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fixMethods = (methods: Record<string, any>) => {
if (methods && Object.keys(methods).length) {
Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => {
if (
typeof methodValue !== 'object' ||
methodValue?.type !== 'JSFunction' ||
!methodValue?.value.startsWith('function')
) {
methods[methodName] = {
type: 'JSFunction',
value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}'
}
logger.log('autofix method to empty function:', methodName, methods[methodName])
}
})
}
}
const fixMethods = (methods: Record<string, any>) => {
if (methods && Object.keys(methods).length) {
Object.entries(methods).forEach(([methodName, methodValue]: [string, any]) => {
if (
typeof methodValue !== 'object' ||
methodValue?.type !== 'JSFunction' ||
typeof methodValue?.value !== 'string' ||
!methodValue.value.startsWith('function')
) {
methods[methodName] = {
type: 'JSFunction',
value: 'function ' + methodName + '() {\n console.log("' + methodName + '");\n}'
}
logger.log('autofix method to empty function:', methodName, methods[methodName])
}
})
}
}
🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/useAgent.ts around lines 37 to 53, the
conditional at line 43 calls methodValue?.value.startsWith('function') without
ensuring value is a string; update the check to first verify typeof
methodValue?.value === 'string' (e.g. replace the startsWith clause with typeof
methodValue?.value === 'string' && methodValue.value.startsWith('function')) so
non-string values won't throw, keeping the rest of the autofix behavior the
same.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
packages/plugins/robot/src/Main.vue (1)

180-198: Handle upload failures and notify users.

getMetaApi(...).post returns a promise, so network failures bypass this try/catch, leaving the attachment stuck in “uploading” with no user feedback. Please await the request (or add a .catch) and surface the error via TinyNotify, so updateAttachment('') always runs and the user sees the failure.

-const handleFileSelected = (formData: unknown, updateAttachment: (resourceUrl: string) => void) => {
-  try {
-    getMetaApi(META_SERVICE.Http)
-      .post('/material-center/api/resource/upload', formData, {
-        headers: {
-          'Content-Type': 'multipart/form-data'
-        }
-      })
-      .then((res: any) => {
-        updateAttachment(res?.resourceUrl)
-        if (!inputMessage.value) {
-          inputMessage.value = '生成图片中UI效果'
-        }
-      })
-  } catch (error) {
-    // eslint-disable-next-line no-console
-    console.error('上传失败', error)
-    updateAttachment('')
-  }
-}
+const handleFileSelected = async (
+  formData: FormData,
+  updateAttachment: (resourceUrl: string) => void
+) => {
+  try {
+    const res = await getMetaApi(META_SERVICE.Http).post('/material-center/api/resource/upload', formData, {
+      headers: {
+        'Content-Type': 'multipart/form-data'
+      }
+    })
+    updateAttachment(res?.resourceUrl)
+    if (!inputMessage.value) {
+      inputMessage.value = '生成图片中UI效果'
+    }
+  } catch (error) {
+    updateAttachment('')
+    TinyNotify({
+      type: 'error',
+      message: '文件上传失败,请重试',
+      position: 'top-right',
+      duration: 5000
+    })
+  }
+}

Based on learnings

packages/plugins/robot/src/components/chat/RobotChat.vue (2)

166-214: Fix retry upload type handling and null guards.

files is declared as FileList | null, yet retry passes a single File, and the logic assumes .length exists. This breaks TypeScript expectations and can send undefined to FormData, leaving uploads in limbo. Guard null, normalize to File[], and use the stored rawFile on retry.

-const handleSingleFilesSelected = (files: FileList | null, retry = false) => {
+const handleSingleFilesSelected = (files: FileList | File | null, retry = false) => {
   if (retry) {
     singleAttachmentItems.value[0].status = 'uploading'
     singleAttachmentItems.value[0].isUploading = true
     singleAttachmentItems.value[0].messageType = 'uploading'
   } else {
-    if (!files.length) return
-
-    if (files && files.length > 1) {
+    if (!files) return
+    const selectedFiles = files instanceof FileList ? Array.from(files) : [files]
+    if (!selectedFiles.length) return
+    if (selectedFiles.length > 1) {
       Notify({
         type: 'error',
         message: '当前仅支持上传一张图片',
         position: 'top-right',
         duration: 5000
       })
       return
     }
 
-    if (files && files.length > 0) {
-      // 将选中的文件转换为 Attachment 格式并添加到附件列表
-      const newAttachments = Array.from(files).map((file) => ({
-        size: file.size,
-        rawFile: file
-      }))
-      singleAttachmentItems.value.push(...newAttachments)
-    }
+    const newAttachments = selectedFiles.map((file) => ({
+      size: file.size,
+      rawFile: file
+    }))
+    singleAttachmentItems.value.push(...newAttachments)
   }
 
   // 开始上传
   const formData = new FormData()
-  const fileData = retry ? files : files[0]
-  formData.append('file', fileData)
+  const fileData =
+    retry && files instanceof File
+      ? files
+      : files instanceof FileList
+        ? files[0]
+        : files instanceof File
+          ? files
+          : null
+  if (!fileData) {
+    return
+  }
+  formData.append('file', fileData)
@@
-const handleSingleFileRetry = (file: any) => {
-  handleSingleFilesSelected(file.file, true)
+const handleSingleFileRetry = (file: any) => {
+  handleSingleFilesSelected(file.rawFile, true)
 }

358-360: Guard abort on empty message list.

messages.value.at(-1)! throws if the list is empty (e.g., aborting before any assistant response exists). Add a null check before setting aborted.

 const handleAbortRequest = () => {
   abortRequest()
-  messages.value.at(-1)!.aborted = true
+  const lastMessage = messages.value.at(-1)
+  if (lastMessage) {
+    lastMessage.aborted = true
+  }
 }
packages/plugins/robot/src/composables/useChat.ts (1)

86-93: Guard reasoning extraBody merge.

Some providers return extraBody.disable = null; passing that into Object.assign throws TypeError: Cannot convert undefined or null to object, breaking agent flows when thinking is off. Select the branch and only merge when it’s truthy.

-  if (modelCapabilities?.reasoning?.extraBody) {
-    Object.assign(
-      requestParams,
-      robotSettingState.enableThinking
-        ? modelCapabilities.reasoning.extraBody.enable
-        : modelCapabilities.reasoning.extraBody.disable
-    )
-  }
+  if (modelCapabilities?.reasoning?.extraBody) {
+    const extraBody = robotSettingState.enableThinking
+      ? modelCapabilities.reasoning.extraBody.enable
+      : modelCapabilities.reasoning.extraBody.disable
+    if (extraBody) {
+      Object.assign(requestParams, extraBody)
+    }
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eee31c8 and 2e06be5.

📒 Files selected for processing (5)
  • packages/plugins/robot/src/Main.vue (2 hunks)
  • packages/plugins/robot/src/components/chat/RobotChat.vue (1 hunks)
  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue (11 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/constants/model-config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/plugins/robot/src/constants/model-config.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-15T02:19:06.755Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 940
File: packages/canvas/DesignCanvas/src/DesignCanvas.vue:0-0
Timestamp: 2025-01-15T02:19:06.755Z
Learning: In Vue components using message subscriptions from opentiny/tiny-engine-meta-register, always clean up subscriptions in the onUnmounted hook using useMessage().unsubscribe() to prevent memory leaks.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/header-extension/RobotSettingPopover.vue
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.

Applied to files:

  • packages/plugins/robot/src/composables/useChat.ts
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。

Applied to files:

  • packages/plugins/robot/src/Main.vue
🧬 Code graph analysis (1)
packages/plugins/robot/src/composables/useChat.ts (9)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getOptions (32-34)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-24)
packages/plugins/robot/src/types/chat.types.ts (2)
  • LLMMessage (32-36)
  • RobotMessage (38-43)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/plugins/robot/src/composables/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/composables/useAgent.ts (4)
  • search (195-211)
  • fetchAssets (213-225)
  • updatePageSchema (193-193)
  • isValidJsonPatchObjectString (123-138)
packages/plugins/robot/src/prompts/index.ts (2)
  • getAgentSystemPrompt (30-52)
  • getJsonFixPrompt (56-87)
packages/plugins/robot/src/utils/chat.utils.ts (3)
  • mergeStringFields (37-56)
  • formatMessages (7-17)
  • serializeError (19-29)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/plugins/robot/src/components/footer-extension/McpServer.vue (1)

36-48: Incorrect withDefaults structure.

The withDefaults call has structural issues:

  1. Line 41 sets type: 'fixed' but type is not a defined prop
  2. Line 42-46 provides position as a flat object but it should be a factory function for object defaults in Vue 3
 const props = withDefaults(
   defineProps<{
     position: PopupConfig
   }>(),
   {
-    type: 'fixed',
-    position: {
+    position: () => ({
       top: 'var(--base-top-panel-height)',
       bottom: 0,
       right: 'var(--tr-container-width)'
-    }
+    })
   }
 )
♻️ Duplicate comments (6)
packages/plugins/robot/src/constants/prompts/data/examples.json (1)

26-120: Component IDs still do not comply with specification requirements.

As previously noted, the specification requires component IDs to be unique 8-character random IDs containing at least one uppercase letter, one lowercase letter, and one digit. The IDs in this file (lines 26, 37, 43, 75, 83, 104, 117, 120) are either purely numeric or lack uppercase letters.

packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)

63-102: Error type detection relies on fragile string matching.

As previously noted, the method infers error types by searching for substrings in lowercased error messages, which is brittle across different browsers and network stacks.

packages/plugins/robot/src/Main.vue (1)

252-265: Add user notification for file upload failures.

The catch block logs the error but provides no visible feedback to the user. Additionally, the try-catch won't catch errors from the Promise chain since apiService.uploadFile is not awaited.

Apply this diff to fix error handling and add user notification:

-const handleFileSelected = (formData: FormData, updateAttachment: (resourceUrl: string) => void) => {
-  try {
-    apiService.uploadFile(formData).then((res: any) => {
+const handleFileSelected = async (formData: FormData, updateAttachment: (resourceUrl: string) => void) => {
+  try {
+    const res: any = await apiService.uploadFile(formData)
+    updateAttachment(res?.resourceUrl)
+    if (!inputMessage.value) {
+      inputMessage.value = '生成图片中UI效果'
+    }
+  } catch (error) {
+    // eslint-disable-next-line no-console
+    console.error('上传失败', error)
+    updateAttachment('')
+    TinyNotify({
+      type: 'error',
+      title: '文件上传失败',
+      message: '请重试',
+      position: 'top-right',
+      duration: 5000
+    })
+  }
+}
-      updateAttachment(res?.resourceUrl)
-      if (!inputMessage.value) {
-        inputMessage.value = '生成图片中UI效果'
-      }
-    })
-  } catch (error) {
-    // eslint-disable-next-line no-console
-    console.error('上传失败', error)
-    updateAttachment('')
-  }
-}
packages/plugins/robot/src/constants/model-config.ts (1)

1-9: Previous review noted reasoningExtraBody should be exported.

A past review comment already flagged that reasoningExtraBody is not exported but should be for reusability.

packages/plugins/robot/src/utils/chat.utils.ts (1)

57-86: SSE parsing robustness concerns already flagged.

The issues with lines.pop() assuming trailing newline, regex matching, and error handling were previously identified.

packages/plugins/robot/src/components/chat/RobotChat.vue (1)

121-152: Type inconsistency in file upload retry logic persists.

The files parameter type mismatch from previous review remains unaddressed. When retry is true (Line 175 calls with file.file), the function receives a File object, not FileList. However:

  • Line 127 calls files.length without null check when retry is false
  • Line 151 uses files directly when retry is true, but files[0] otherwise

Apply this diff to fix the type handling:

-const handleSingleFilesSelected = (files: FileList | null, retry = false) => {
+const handleSingleFilesSelected = (files: FileList | File | null, retry = false) => {
   if (retry) {
     singleAttachmentItems.value[0].status = 'uploading'
     singleAttachmentItems.value[0].isUploading = true
     singleAttachmentItems.value[0].messageType = 'uploading'
   } else {
-    if (!files.length) return
+    if (!files || !('length' in files) || !files.length) return
 
     if (files && files.length > 1) {

And update the retry call at Line 174-176:

 const handleSingleFileRetry = (file: any) => {
-  handleSingleFilesSelected(file.file, true)
+  handleSingleFilesSelected(file.rawFile, true)
 }
🟡 Minor comments (11)
packages/plugins/robot/src/utils/schema.utils.ts-139-146 (1)

139-146: Fix parameter naming typo.

The parameter name isFinial should be isFinal throughout this function and wherever it's called.

Apply this diff:

-export const jsonPatchAutoFix = (jsonPatches: any[], isFinial: boolean) => {
+export const jsonPatchAutoFix = (jsonPatches: any[], isFinal: boolean) => {
   // 流式渲染过程中,画布只渲染完整的字段或流式的children字段,避免不完整的methods/states/css等字段导致解析报错
   const childrenFilter = (patch: any, index: number, arr: any[]) =>
-    isFinial || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
+    isFinal || index < arr.length - 1 || (index === arr.length - 1 && patch.path?.startsWith('/children'))
   const validJsonPatches = jsonPatches.filter(childrenFilter).filter(isValidFastJsonPatch)
 
   return validJsonPatches
 }

Also update all call sites of this function (e.g., in parseAndRepairJson at line 180 if it uses this parameter).

packages/plugins/robot/src/Main.vue-173-176 (1)

173-176: Potential null access when aborting an empty message list.

If messages.value is empty when handleAbortRequest is called, messages.value.at(-1) returns undefined, causing a runtime error when accessing .aborted.

Apply this diff to add a guard:

 const handleAbortRequest = () => {
   abortRequest()
-  messages.value.at(-1)!.aborted = true
+  const lastMessage = messages.value.at(-1)
+  if (lastMessage) {
+    lastMessage.aborted = true
+  }
 }
packages/plugins/robot/src/constants/prompts/data/components.json-529-542 (1)

529-542: Trailing space in event name.

Line 532 has a trailing space in "onCurrentChange " and "onPrevClick " which could cause issues when matching event names programmatically.

-    "events": ["onCurrentChange ", "onPrevClick ", "onNextClick"],
+    "events": ["onCurrentChange", "onPrevClick", "onNextClick"],
packages/plugins/robot/src/composables/modes/useAgentMode.ts-150-158 (1)

150-158: Potential null reference in onRequestEnd.

Lines 153-156 access messages.at(-1).renderContent.at(-1) without checking if messages or renderContent are non-empty. If called with an empty messages array, this will throw.

   if (finishReason === 'aborted' || finishReason === 'error') {
     removeLoading(messages)
     const errorInfo = { content: extraData?.error || '请求失败', status: 'failed' }
-    if (messages.at(-1).renderContent.at(-1)) {
-      Object.assign(messages.at(-1).renderContent.at(-1), errorInfo)
+    const lastMessage = messages.at(-1)
+    const lastRenderContent = lastMessage?.renderContent?.at(-1)
+    if (lastRenderContent) {
+      Object.assign(lastRenderContent, errorInfo)
     } else {
-      messages.at(-1).renderContent = [{ type: getContentType(), ...errorInfo }]
+      if (lastMessage) {
+        lastMessage.renderContent = [{ type: getContentType(), ...errorInfo }]
+      }
     }
   }
packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue-163-168 (1)

163-168: Props destructuring loses reactivity.

Destructuring fullscreen directly from defineProps loses reactivity. If the parent changes fullscreen, the component won't react to the change.

-const { fullscreen } = defineProps({
+const props = defineProps({
   fullscreen: {
     type: Boolean,
     default: false
   }
 })

Then use props.fullscreen in the template, or use toRefs if local destructuring is preferred.

Committable suggestion skipped: line range outside the PR's diff.

packages/plugins/robot/src/constants/prompts/index.ts-181-186 (1)

181-186: Incorrect JSON Patch specification in prompt.

Line 185 states "value" is required for "add", "replace", "move", "copy", "test" operations, but per RFC 6902:

  • move and copy require from, not value
  • test requires value
  • remove requires neither

This could cause the LLM to generate invalid patches.

-- "value" is required for "add", "replace", "move", "copy", "test" operations
-- "from" is required for "move", "copy" operations
+- "value" is required for "add", "replace", "test" operations
+- "from" is required for "move", "copy" operations
packages/plugins/robot/src/constants/prompts/index.ts-163-168 (1)

163-168: Template substitution could break if dynamic content contains placeholder patterns.

The replace() calls on lines 164-168 use simple string replacement. If referenceContext or currentPageSchemaStr contains a string like {{COMPONENTS_LIST}}, it would be incorrectly substituted in subsequent replacements.

Consider using a single-pass replacement or ensuring dynamic content is processed after all static placeholders:

-  const prompt = agentPrompt
-    .replace('{{COMPONENTS_LIST}}', componentsList)
-    .replace('{{EXAMPLES_SECTION}}', examplesSection)
-    .replace('{{CURRENT_PAGE_SCHEMA}}', currentPageSchemaStr)
-    .replace('{{REFERENCE_KNOWLEDGE}}', referenceContext || '')
-    .replace('{{IMAGE_ASSETS}}', imageAssets.map((item) => `- ![${item.describe}](${item.url})`).join('\n'))
+  const replacements: Record<string, string> = {
+    '{{COMPONENTS_LIST}}': componentsList,
+    '{{EXAMPLES_SECTION}}': examplesSection,
+    '{{CURRENT_PAGE_SCHEMA}}': currentPageSchemaStr,
+    '{{REFERENCE_KNOWLEDGE}}': referenceContext || '',
+    '{{IMAGE_ASSETS}}': imageAssets.map((item) => `- ![${item.describe}](${item.url})`).join('\n')
+  }
+  const prompt = Object.entries(replacements).reduce(
+    (acc, [key, value]) => acc.replace(key, value),
+    agentPrompt
+  )
packages/plugins/robot/src/composables/core/useMessageStream.ts-53-68 (1)

53-68: Sparse array risk with index-based tool call assignment.

When chunk.index is used directly for array assignment (line 64), non-contiguous indices could create sparse arrays (e.g., [toolCall0, undefined, undefined, toolCall3]). This may cause issues when iterating or serializing downstream.

Consider using a Map or object keyed by index, then converting to array, or ensuring indices are always contiguous from the provider:

+// Alternative: Use object-based accumulation
+if (!lastMessage._toolCallsMap) {
+  lastMessage._toolCallsMap = {}
+}
 for (const chunk of toolCallChunks) {
   const { index, ...chunkWithoutIndex } = chunk
-  if (lastMessage.tool_calls[index]) {
-    mergeStringFields(lastMessage.tool_calls[index], chunkWithoutIndex)
+  if (lastMessage._toolCallsMap[index]) {
+    mergeStringFields(lastMessage._toolCallsMap[index], chunkWithoutIndex)
   } else {
-    lastMessage.tool_calls[index] = chunkWithoutIndex
+    lastMessage._toolCallsMap[index] = chunkWithoutIndex
   }
 }
+lastMessage.tool_calls = Object.values(lastMessage._toolCallsMap)
packages/plugins/robot/src/composables/features/useToolCalls.ts-115-117 (1)

115-117: Silent error swallowing hides failures.

When callTools throws (including on abort), the error is caught and the function silently returns without notifying the caller or logging. This makes debugging difficult.

Consider at minimum logging the error or calling an error handler:

     } catch (error) {
+      // eslint-disable-next-line no-console
+      console.error('Tool call execution failed:', error)
       return
     }

Or propagate to streamHandlers.onError if appropriate.

packages/plugins/robot/src/composables/core/useConversation.ts-35-38 (1)

35-38: Context parameter mismatch with ModeHooks.onMessageProcessed.

Line 37 passes an empty object {} as the context, but according to mode.types.ts (line 77), onMessageProcessed expects context: { abortControllerMap: Record<string, AbortController> }. This type mismatch could cause runtime errors in hook implementations.

Ensure the context matches the expected interface:

       if (lastMessage) {
-        await onMessageProcessed(finishReason ?? 'unknown', lastMessage.content ?? '', messages.value, {})
+        await onMessageProcessed(finishReason ?? 'unknown', lastMessage.content ?? '', messages.value, {
+          abortControllerMap: {} // Or pass the actual map if available
+        })
       }
packages/plugins/robot/src/composables/features/useToolCalls.ts-34-54 (1)

34-54: Abort signal checked too late and tool objects mutated.

Two concerns:

  1. Abort timing: The abort check (lines 49-51) occurs after each tool call completes. For long-running tools, this means an aborted request won't stop the current tool, only prevent subsequent ones.

  2. Object mutation: Lines 39-40 mutate the original tool objects by adding parsedArgs and name properties, which could cause issues if the caller expects the original objects unchanged.

Consider checking abort before each tool call and avoiding mutation:

 export const callTools = async (tool_calls: any, hooks: CallToolHooks, signal: AbortController['signal']) => {
   const result = []
   for (const tool of tool_calls) {
+    if (signal?.aborted) {
+      return Promise.reject('aborted')
+    }
+
     const { name, arguments: args } = tool.function
     const parsedArgs = parseArgs(args)
-    tool.parsedArgs = parsedArgs
-    tool.name = name
+    const enrichedTool = { ...tool, parsedArgs, name }

-    hooks.onBeforeCallTool(tool)
+    hooks.onBeforeCallTool(enrichedTool)

     const { toolCallResult, toolCallStatus } = await callTool(name, parsedArgs)
-    result.push({ toolCallResult, toolCallStatus, ...tool })
+    result.push({ toolCallResult, toolCallStatus, ...enrichedTool })

-    hooks.onPostCallTool(tool, toolCallResult, toolCallStatus)
-
-    if (signal?.aborted) {
-      return Promise.reject('aborted')
-    }
+    hooks.onPostCallTool(enrichedTool, toolCallResult, toolCallStatus)
   }
   return result
 }
🧹 Nitpick comments (32)
packages/layout/src/composable/useLayout.ts (1)

136-139: Default toolbars.render sentinel seems reasonable; ensure consumers handle empty string

Initializing toolbars.render to '' matches the “no active panel” convention already used for plugins.render / settings.render. Just make sure any components reading layoutState.toolbars.render treat '' as “no toolbar selected” and don’t assume a non‑empty ID.

packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)

145-205: Prefer typed error objects over any.

Line 163 uses any type for the custom error object, which weakens type safety. Consider defining a proper error interface with a response property.

Apply this approach:

+interface HttpError extends Error {
+  response?: Response
+}
+
 private createFetchAdapter(requestData: ChatRequestData, isStream = false) {
   return async (config: AxiosRequestConfig) => {
     // ...
     if (!fetchResponse.ok) {
       const errorText = await fetchResponse.text()
-      const customError: any = new Error(
+      const customError = new Error(
         `HTTP error! status: ${fetchResponse.status}${errorText ? ', details: ' + errorText : ''}`
-      )
+      ) as HttpError
       customError.response = fetchResponse
       throw customError
     }

211-234: Consider extracting shared error handling logic.

The error handling in sendFetchRequest (lines 223-231) duplicates logic from createFetchAdapter (lines 161-168). Consider extracting this into a shared helper method to improve maintainability.

Example:

private createHttpError(response: Response, errorText: string): Error {
  const error = new Error(
    `HTTP error! status: ${response.status}${errorText ? ', details: ' + errorText : ''}`
  ) as HttpError
  error.response = response
  return error
}

Then use it in both methods:

if (!fetchResponse.ok) {
  const errorText = await fetchResponse.text()
  throw this.createHttpError(fetchResponse, errorText)
}

268-287: Inconsistent error handling loses type information.

The chat method wraps all errors in a generic error message (line 285), losing the structured error information that toAIAdapterError provides. Consider using the adapter's error handling consistently.

Apply this diff:

   async chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse> {
     try {
       const requestData = await this.prepareRequestData(request, false)
       const headers = this.buildHeaders(false)

       if (this.httpClientType === 'axios' && this.axiosClient) {
         const response = await this.sendAxiosRequest(requestData, headers, false)
         return (response as { data: ChatCompletionResponse }).data || response
       } else {
         const response = await this.sendFetchRequest(requestData, headers)
         return await response.json()
       }
-    } catch (error: unknown) {
-      const errorMessage = error instanceof Error ? error.message : String(error)
-      throw new Error(`Error in chat request: ${errorMessage}`)
+    } catch (error) {
+      throw this.toAIAdapterError(error)
     }
   }
packages/plugins/robot/src/Main.vue (1)

267-272: Consider removing the artificial delay for teleport.

The 1-second delay before showing the teleport container may cause a noticeable lag in the UI. If this is to ensure the target element exists, consider using a more deterministic approach.

 onMounted(async () => {
   initChatClient()
-  setTimeout(() => {
-    showTeleport.value = true
-  }, 1000)
+  // Consider using nextTick or checking if target element exists
+  await nextTick()
+  showTeleport.value = true
 })
packages/plugins/robot/src/services/agentServices.ts (2)

29-34: Prefer for...of with break for early termination.

Using forEach with return only exits the current iteration callback; the loop continues iterating over remaining items even after the limit is reached. This is inefficient for large result sets.

-    res.forEach((item: { content: string }) => {
-      if (result.length + item.content.length > MAX_SEARCH_LENGTH) {
-        return
-      }
-      result += item.content
-    })
+    for (const item of res as { content: string }[]) {
+      if (result.length + item.content.length > MAX_SEARCH_LENGTH) {
+        break
+      }
+      result += item.content
+    }

47-59: Consider adding type definitions for resource items.

The function uses any type for resource items. Adding an interface would improve type safety and documentation.

interface ResourceItem {
  resourceUrl: string
  description?: string
}

export const fetchAssets = async (): Promise<{ url: string; describe: string }[]> => {
  try {
    const res = (await apiService.getResourceList('1')) as ResourceItem[] || []
    return res
      .filter((item) => item.description)
      .map((item) => ({
        url: item.resourceUrl,
        describe: item.description!
      }))
  } catch (error) {
    logger.warn('Fetch assets failed:', error)
    return []
  }
}
packages/plugins/robot/src/components/footer-extension/McpServer.vue (1)

34-34: Initial activeCount may be misleading.

activeCount is initialized to 1 before the picker component populates its actual value via v-model:activeCount. Consider initializing to 0 and letting the picker set the correct value.

-const activeCount = ref(1)
+const activeCount = ref(0)
packages/plugins/robot/src/components/header-extension/robot-setting/ServiceEditDialog.vue (1)

59-65: Non-null assertion on capabilities may cause runtime errors.

The template uses model.capabilities! without null checking. While createEmptyModel() initializes capabilities, if a model from service.models is missing this field, this will throw.

Consider using optional chaining with defaults or ensure capabilities exist:

           <tiny-form-item label="模型能力">
             <div class="capabilities-group">
-              <tiny-checkbox v-model="model.capabilities!.toolCalling">工具调用</tiny-checkbox>
-              <tiny-checkbox v-model="model.capabilities!.vision">视觉理解</tiny-checkbox>
-              <tiny-checkbox v-model="model.capabilities!.compact">快速模型</tiny-checkbox>
+              <tiny-checkbox v-model="model.capabilities.toolCalling">工具调用</tiny-checkbox>
+              <tiny-checkbox v-model="model.capabilities.vision">视觉理解</tiny-checkbox>
+              <tiny-checkbox v-model="model.capabilities.compact">快速模型</tiny-checkbox>
             </div>
           </tiny-form-item>

And ensure capabilities are always initialized when cloning existing models in updateForm:

-    formData.models = service.isBuiltIn ? [] : JSON.parse(JSON.stringify(service.models))
+    formData.models = service.isBuiltIn ? [] : JSON.parse(JSON.stringify(service.models)).map((m: ModelConfig) => ({
+      ...m,
+      capabilities: m.capabilities ?? { toolCalling: false, vision: false, reasoning: false, compact: false }
+    }))
packages/plugins/robot/src/composables/core/pageUpdater.ts (1)

20-24: Inconsistent return type when not in Agent mode.

The function returns undefined when not in Agent mode but returns { schema, isError } or { isError, error } in other paths. This inconsistency may confuse callers.

   const { getSelectedModelInfo } = useModelConfig()
   if (getSelectedModelInfo().config?.chatMode !== ChatMode.Agent) {
-    return
+    return { isError: false }
   }
packages/plugins/robot/src/components/header-extension/History.vue (1)

116-132: Hardcoded background color may break theming.

The background-color: white doesn't use a CSS variable, which could cause issues with dark mode or custom themes.

 .tr-history-container {
   position: absolute;
   right: 100%;
   top: 100%;
   z-index: var(--tr-z-index-popover);
   width: 300px;
   height: 400px;
   box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
-  background-color: white;
+  background-color: var(--tr-color-bg-container, white);
   padding: 16px;
   border-radius: 16px;
packages/plugins/robot/src/composables/modes/useChatMode.ts (1)

19-39: Non-null assertions on tool.function may cause runtime errors.

Lines 26, 30, and 33 use tool.function! but if the tool object doesn't have a function property (e.g., newer OpenAI tool formats), this will throw.

Consider defensive access:

-    currentToolCallContent.content.params = tool.parsedArgs || tool.function!.arguments || {}
+    currentToolCallContent.content.params = tool.parsedArgs || tool.function?.arguments || {}
   } else {
     renderContent.push({
       type: 'tool',
-      name: tool.name || tool.function!.name,
+      name: (tool.name || tool.function?.name) as string,
       status: 'running',
       content: {
-        params: tool.parsedArgs || tool.function!.arguments || {}
+        params: tool.parsedArgs || tool.function?.arguments || {}
       },
packages/plugins/robot/src/composables/core/useMessageStream.ts (2)

6-17: Consider using stricter type definitions for hooks.

The hooks interface uses any types extensively (messages: any[], data: any, content: any, tools: any[], currentMessage: any). This reduces type safety and makes it harder to catch integration errors at compile time.

 export interface StreamDataHandlerOptions {
   getContentType: () => string
   hooks: {
-    onStreamStart: (messages: any[]) => void
-    onStreamData: (data: any, content: any, messages: any[]) => void
-    onStreamTools: (tools: any[], context: { currentMessage: any }) => void
+    onStreamStart: (messages: Message[]) => void
+    onStreamData: (data: ChatCompletionStreamResponse, content: string, messages: Message[]) => void
+    onStreamTools: (tools: ResponseToolCall[], context: { currentMessage: Message }) => void
   }
   statusManager: {
     isStreaming: () => boolean
     setStreaming: () => void
   }
 }

21-31: Non-null assertion after .at(-1) is safe here but consider i18n for the hardcoded string.

The non-null assertion on line 31 is guarded by the check on line 21 which ensures the last item exists. However, the hardcoded Chinese string '深度思考' should be externalized for internationalization support.

packages/plugins/robot/src/composables/features/useMcp.ts (1)

120-124: Consider reusing listTools to avoid duplicate API call patterns.

getLLMTools duplicates the MCP client call from listTools. This could lead to maintenance issues if the API call logic changes.

 const getLLMTools = async () => {
-  const mcpTools = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools()
-  llmTools = convertMCPToOpenAITools(mcpTools?.tools || [])
-  return llmTools
+  const mcpToolsResponse = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools()
+  return convertMCPToOpenAITools(mcpToolsResponse?.tools || [])
 }

Also, the module-level llmTools variable (line 110) appears unused if getLLMTools always fetches fresh data. Consider removing it if caching isn't needed.

packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (3)

233-248: Model value parsing assumes :: won't appear in serviceId or modelName.

The split on '::' (lines 234, 260) assumes neither serviceId nor modelName contain this delimiter. If a model name like provider::v2::large exists, parsing would break.

Consider using a more robust delimiter or limiting the split:

-const [defaultServiceId, defaultModelName] = state.modelSelection.defaultModel.split('::')
+const [defaultServiceId, ...rest] = state.modelSelection.defaultModel.split('::')
+const defaultModelName = rest.join('::')

284-292: Avoid as any type assertion; use proper typing.

The as any on line 290 bypasses type checking. Since addCustomService expects a specific type, ensure the partial data conforms to required fields or use a proper type guard.

 const handleServiceConfirm = (serviceData: Partial<ModelService>) => {
   if (serviceData.id) {
     // 更新现有服务
     updateService(serviceData.id, serviceData)
   } else {
     // 添加新服务
-    addCustomService(serviceData as any)
+    addCustomService(serviceData as Omit<ModelService, 'id' | 'isBuiltIn'>)
   }
 }

183-183: Unused ref: modelSelectionForm.

The modelSelectionForm ref is declared but not used anywhere in the component. If form validation is planned, keep it; otherwise, consider removing.

packages/plugins/robot/src/constants/prompts/index.ts (1)

72-86: Hardcoded ignore lists may need maintenance.

The ignoreGroups, ignoreComponents, and ignoreProperties arrays are hardcoded. Consider externalizing these to a configuration file for easier maintenance, especially the comment noting TinyNumeric is ignored due to an error.

packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)

169-180: Non-null assertions assume correct lifecycle ordering.

Lines 175-179 use .at(-1)! which assumes renderContent has items. This should be safe if onBeforeCallTool is always called before onPostCallTool, but adding a defensive check would improve robustness.

packages/plugins/robot/src/types/mode.types.ts (1)

25-82: Consider replacing any types with more specific interfaces.

The ModeHooks interface uses any extensively for parameters like conversationState, messages, apis, requestParams, and context. This reduces type safety and IDE support.

Consider defining specific interfaces for these parameters:

+export interface ConversationState {
+  currentId: string
+  conversations: Conversation[]
+  // ... other properties
+}
+
+export interface ModeApis {
+  sendMessage: (content: string) => Promise<void>
+  // ... other methods
+}
+
 export interface ModeHooks {
   // ...
-  onConversationStart: (conversationState: any, messages: any[], apis: any) => void
+  onConversationStart: (conversationState: ConversationState, messages: LLMMessage[], apis: ModeApis) => void
   // ...
 }

This would improve maintainability and catch type errors at compile time.

packages/plugins/robot/src/services/api.ts (2)

39-46: Inconsistent handling of options.url compared to chatCompletions.

chatCompletions (line 26) uses options?.url to allow customizable endpoints, but agentChat ignores the options.url parameter entirely. This inconsistency may confuse consumers who expect uniform behavior.

Consider aligning the behavior:

   agentChat: (body: LLMRequestBody, options: RequestOptions = {}) => {
-    return getMetaApi(META_SERVICE.Http).post('/app-center/api/ai/chat', body, {
+    return getMetaApi(META_SERVICE.Http).post(options?.url || '/app-center/api/ai/chat', body, {
       headers: {
         'Content-Type': 'application/json',
         ...options?.headers
       }
     })
   },

85-92: getHttpClient may return undefined without explicit typing.

The optional chaining getMetaApi(META_SERVICE.Http)?.getHttp() can return undefined if getMetaApi returns a falsy value. The return type should be explicitly annotated to reflect this.

 export const httpApi = {
-  getHttpClient: () => {
+  getHttpClient: (): ReturnType<typeof getMetaApi> | undefined => {
     return getMetaApi(META_SERVICE.Http)?.getHttp()
   }
 }

Alternatively, document or handle the undefined case explicitly.

packages/plugins/robot/src/utils/chat.utils.ts (2)

36-55: mergeStringFields doesn't handle arrays.

When targetValue or value is an array, the function will attempt to iterate it as an object, which may produce unexpected results. Arrays with numeric indices will be converted to object-like structures.

 export const mergeStringFields = (target: Record<string, any>, source: Record<string, any>) => {
   for (const [key, value] of Object.entries(source)) {
     const targetValue = target[key]

     if (targetValue) {
       if (typeof targetValue === 'string' && typeof value === 'string') {
         target[key] = targetValue + value
-      } else if (targetValue && typeof targetValue === 'object' && value && typeof value === 'object') {
+      } else if (
+        targetValue &&
+        typeof targetValue === 'object' &&
+        !Array.isArray(targetValue) &&
+        value &&
+        typeof value === 'object' &&
+        !Array.isArray(value)
+      ) {
         target[key] = mergeStringFields(targetValue, value)
+      } else if (Array.isArray(targetValue) && Array.isArray(value)) {
+        // Handle array concatenation if needed
+        target[key] = [...targetValue, ...value]
       }
     } else {
       target[key] = value
     }
   }

   return target
 }

99-105: addSystemPrompt mutates the input array.

The function directly mutates messages via unshift and property assignment. This side-effect pattern can be error-prone and harder to debug. Consider documenting this behavior clearly or returning a new array.

If mutation is intentional for performance, add a JSDoc note:

+/**
+ * Ensures the first message is a system prompt.
+ * @param messages - Message array (mutated in place)
+ * @param prompt - System prompt content
+ */
 export const addSystemPrompt = (messages: LLMMessage[], prompt: string = '') => {
packages/plugins/robot/src/composables/features/useToolCalls.ts (1)

119-119: Mutating currentMessage by deleting tool_calls.

Directly deleting a property from currentMessage (which comes from the messages array) creates a side effect that may not be expected by callers. This could interfere with message history or debugging.

Consider tracking tool_calls state separately rather than mutating the message object, or document this behavior clearly.

packages/plugins/robot/src/composables/core/useConversation.ts (2)

66-78: Silent failure when conversation not found.

switchConversation returns early without any indication when the conversation ID doesn't exist (line 68). This could make debugging difficult for callers.

Consider returning a result indicating success/failure or throwing:

   const switchConversation = (conversationId: string, onStart?: (state: any, messages: any, methods: any) => void) => {
     const conversation = conversationState.conversations.find((c) => c.id === conversationId)
-    if (!conversation) return
+    if (!conversation) {
+      console.warn(`Conversation not found: ${conversationId}`)
+      return false
+    }

     const result = conversationMethods.switchConversation(conversationId)

     if (onStart) {
       onStart(conversationState, messageManager.messages.value, conversationMethods)
     }

-    return result
+    return result ?? true
   }

90-94: substring(0, 20) may split multi-byte Unicode characters.

Using substring on a string with multi-byte characters (e.g., emoji, CJK characters) can result in corrupted display when cutting mid-character.

Consider using spread or Array.from for safe Unicode handling:

-      conversationMethods.updateTitle(currentId, contentStr.substring(0, 20))
+      const chars = [...contentStr]
+      conversationMethods.updateTitle(currentId, chars.slice(0, 20).join(''))
packages/plugins/robot/src/composables/useChat.ts (3)

42-44: Module-level mutable state may cause issues in multi-instance scenarios.

chatStatus and abortControllerMap are module-level variables shared across all consumers. This singleton pattern works for a single chat instance but could cause state leakage if multiple chat instances are needed, or issues in SSR environments where state persists across requests.

If multiple chat instances are needed in the future, consider moving these into the composable's closure or using a Map keyed by conversation ID:

+const createChatState = () => ({
+  chatStatus: CHAT_STATUS.IDLE as CHAT_STATUS,
+  abortControllerMap: {} as Record<string, AbortController>
+})

 export default function () {
+  const state = createChatState()
   // ... use state.chatStatus, state.abortControllerMap
 }

75-87: Consider making the fallback model configurable.

The hardcoded fallback 'deepseek-v3' at Line 81 may not be appropriate for all deployments. Consider making this configurable via the mode or config system.


193-202: Type assertion may hide undefined content.

At Line 201, messages.value.at(-1)?.content as string will be undefined cast to string if the array is empty or content is undefined. Consider using nullish coalescing for safety.

-  onRequestEnd('aborted', messageManager.messages.value.at(-1)?.content as string, messageManager.messages.value)
+  onRequestEnd('aborted', messageManager.messages.value.at(-1)?.content ?? '', messageManager.messages.value)
packages/plugins/robot/src/composables/core/useConfig.ts (1)

171-178: Read-modify-write pattern may cause data loss on concurrent updates.

saveRobotSettingState reads the current state at Line 175, then merges and writes. If two updates happen concurrently (e.g., from different tabs or rapid UI interactions), one update may overwrite the other. Consider using a lock or always saving the full state.

 const saveRobotSettingState = (state: Partial<RobotSettings>, updateState = true) => {
   if (updateState) {
     Object.assign(robotSettingState, state)
   }
-  const currentState = loadRobotSettingState() || initDefaultSettings()
-  const newState = { ...currentState, ...state, version: SETTING_VERSION }
+  // Use in-memory state as source of truth to avoid race conditions
+  const newState = { ...robotSettingState, version: SETTING_VERSION }
   localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(newState))
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e06be5 and c03b8bf.

⛔ Files ignored due to path filters (2)
  • packages/design-core/assets/back.svg is excluded by !**/*.svg
  • packages/design-core/assets/intelligent-construction.svg is excluded by !**/*.svg
📒 Files selected for processing (49)
  • designer-demo/public/mock/bundle.json (3 hunks)
  • packages/canvas/container/src/components/CanvasResize.vue (1 hunks)
  • packages/common/js/completion.js (3 hunks)
  • packages/layout/src/composable/useLayout.ts (2 hunks)
  • packages/plugins/materials/src/composable/useMaterial.ts (1 hunks)
  • packages/plugins/robot/index.ts (1 hunks)
  • packages/plugins/robot/meta.js (1 hunks)
  • packages/plugins/robot/package.json (1 hunks)
  • packages/plugins/robot/src/Main.vue (2 hunks)
  • packages/plugins/robot/src/components/chat/RobotChat.vue (1 hunks)
  • packages/plugins/robot/src/components/footer-extension/McpServer.vue (1 hunks)
  • packages/plugins/robot/src/components/footer-extension/index.ts (1 hunks)
  • packages/plugins/robot/src/components/header-extension/History.vue (1 hunks)
  • packages/plugins/robot/src/components/header-extension/index.ts (1 hunks)
  • packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1 hunks)
  • packages/plugins/robot/src/components/header-extension/robot-setting/ServiceEditDialog.vue (1 hunks)
  • packages/plugins/robot/src/components/icons/index.ts (1 hunks)
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1 hunks)
  • packages/plugins/robot/src/components/renderers/index.ts (1 hunks)
  • packages/plugins/robot/src/composables/core/pageUpdater.ts (1 hunks)
  • packages/plugins/robot/src/composables/core/useConfig.ts (1 hunks)
  • packages/plugins/robot/src/composables/core/useConversation.ts (1 hunks)
  • packages/plugins/robot/src/composables/core/useMessageStream.ts (1 hunks)
  • packages/plugins/robot/src/composables/features/useMcp.ts (2 hunks)
  • packages/plugins/robot/src/composables/features/useToolCalls.ts (1 hunks)
  • packages/plugins/robot/src/composables/modes/useAgentMode.ts (1 hunks)
  • packages/plugins/robot/src/composables/modes/useChatMode.ts (1 hunks)
  • packages/plugins/robot/src/composables/modes/useMode.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/constants/model-config.ts (1 hunks)
  • packages/plugins/robot/src/constants/prompts/data/components.json (1 hunks)
  • packages/plugins/robot/src/constants/prompts/data/examples.json (1 hunks)
  • packages/plugins/robot/src/constants/prompts/index.ts (1 hunks)
  • packages/plugins/robot/src/constants/prompts/templates/agent-prompt.md (1 hunks)
  • packages/plugins/robot/src/metas/index.ts (1 hunks)
  • packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1 hunks)
  • packages/plugins/robot/src/services/agentServices.ts (1 hunks)
  • packages/plugins/robot/src/services/aiClient.ts (1 hunks)
  • packages/plugins/robot/src/services/api.ts (1 hunks)
  • packages/plugins/robot/src/types/chat.types.ts (3 hunks)
  • packages/plugins/robot/src/types/index.ts (1 hunks)
  • packages/plugins/robot/src/types/mode.types.ts (1 hunks)
  • packages/plugins/robot/src/types/setting.types.ts (1 hunks)
  • packages/plugins/robot/src/utils/chat.utils.ts (1 hunks)
  • packages/plugins/robot/src/utils/index.ts (1 hunks)
  • packages/plugins/robot/src/utils/meta.utils.ts (1 hunks)
  • packages/plugins/robot/src/utils/schema.utils.ts (1 hunks)
  • packages/register/src/constants.ts (1 hunks)
  • tsconfig.app.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/plugins/robot/index.ts
  • packages/plugins/robot/src/metas/index.ts
  • packages/common/js/completion.js
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue
🧰 Additional context used
🧠 Learnings (18)
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/icons/index.ts
  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/icons/index.ts
  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: The SvgIcon component is globally registered and available throughout the application without requiring explicit imports.

Applied to files:

  • packages/plugins/robot/src/components/icons/index.ts
📚 Learning: 2025-03-19T03:13:51.520Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1226
File: packages/canvas/container/src/components/CanvasDivider.vue:184-185
Timestamp: 2025-03-19T03:13:51.520Z
Learning: The CSS bug in packages/canvas/container/src/components/CanvasDivider.vue where verLeft already includes "px" but is being appended again in the style object will be fixed in a future update, as confirmed by gene9831.

Applied to files:

  • packages/canvas/container/src/components/CanvasResize.vue
📚 Learning: 2025-03-20T07:20:12.221Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1233
File: packages/canvas/container/src/components/CanvasDivider.vue:184-185
Timestamp: 2025-03-20T07:20:12.221Z
Learning: In CanvasDivider.vue, even though state.verLeft and state.horizontalTop already include 'px' suffix, the CSS properties in state.dividerStyle still need to append 'px' again according to gene9831, suggesting that these state variables might be processed differently than expected when used in style binding.

Applied to files:

  • packages/canvas/container/src/components/CanvasResize.vue
📚 Learning: 2025-01-14T06:55:14.457Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/canvas-function/design-mode.ts:6-13
Timestamp: 2025-01-14T06:55:14.457Z
Learning: The code in `packages/canvas/render/src/canvas-function/design-mode.ts` is migrated code that should be preserved in its current form during the migration process. Refactoring suggestions for type safety and state management improvements should be considered in future PRs.

Applied to files:

  • packages/canvas/container/src/components/CanvasResize.vue
📚 Learning: 2025-01-14T08:45:57.032Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/application-function/global-state.ts:12-25
Timestamp: 2025-01-14T08:45:57.032Z
Learning: The code in `packages/canvas/render/src/application-function/global-state.ts` is migrated from an existing codebase and should be handled with care when making modifications.

Applied to files:

  • packages/canvas/container/src/components/CanvasResize.vue
📚 Learning: 2025-01-14T04:22:02.404Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/builtin/builtin.json:645-850
Timestamp: 2025-01-14T04:22:02.404Z
Learning: In TinyEngine, components must use inline styles instead of CSS classes because components cannot carry class styles when dragged into the canvas.

Applied to files:

  • packages/plugins/robot/src/constants/prompts/data/components.json
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
Repo: opentiny/tiny-engine PR: 1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.

Applied to files:

  • packages/plugins/robot/src/utils/index.ts
  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T04:25:08.323Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/material-function/material-getter.ts:55-88
Timestamp: 2025-01-14T04:25:08.323Z
Learning: The BlockLoadError component in packages/canvas/render/src/material-function/material-getter.ts requires a `name` prop to display which block failed to load.

Applied to files:

  • packages/plugins/materials/src/composable/useMaterial.ts
📚 Learning: 2024-10-15T02:45:17.168Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 830
File: packages/common/component/MetaChildItem.vue:50-56
Timestamp: 2024-10-15T02:45:17.168Z
Learning: In `packages/common/component/MetaChildItem.vue`, when checking if `text` is an object in the computed property `title`, ensure that `text` is not `null` because `typeof null === 'object'` in JavaScript. Use checks like `text && typeof text === 'object'` to safely handle `null` values.

Applied to files:

  • packages/plugins/materials/src/composable/useMaterial.ts
📚 Learning: 2025-01-14T06:59:02.999Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/material-function/support-collection.ts:3-15
Timestamp: 2025-01-14T06:59:02.999Z
Learning: The code in `packages/canvas/render/src/material-function/support-collection.ts` is migrated code that should not be modified at this time to maintain stability during the migration process.

Applied to files:

  • packages/plugins/materials/src/composable/useMaterial.ts
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。

Applied to files:

  • packages/plugins/robot/src/Main.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.

Applied to files:

  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T04:25:46.281Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/material-function/material-getter.ts:66-80
Timestamp: 2025-01-14T04:25:46.281Z
Learning: In the tiny-engine project, styles from block components are processed through Vite's CSS compilation pipeline, and additional style sanitization libraries should be avoided to maintain consistency with this approach.

Applied to files:

  • packages/plugins/robot/package.json
📚 Learning: 2024-12-14T05:53:28.501Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 917
File: docs/开始/快速上手.md:31-31
Timestamp: 2024-12-14T05:53:28.501Z
Learning: The latest stable version of `opentiny/tiny-engine-cli` is `2.0.0`, and documentation should reference this version instead of any release candidates.

Applied to files:

  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T08:42:18.574Z
Learnt from: gene9831
Repo: opentiny/tiny-engine PR: 1038
File: packages/plugins/block/index.js:24-24
Timestamp: 2025-01-14T08:42:18.574Z
Learning: In the tiny-engine project, breaking changes are documented in the changelog rather than in JSDoc comments or separate migration guides.

Applied to files:

  • packages/plugins/robot/package.json
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.

Applied to files:

  • packages/plugins/robot/src/composables/useChat.ts
🧬 Code graph analysis (15)
packages/plugins/robot/src/utils/meta.utils.ts (1)
packages/register/src/common.ts (1)
  • getOptions (32-34)
packages/plugins/robot/src/services/agentServices.ts (1)
packages/plugins/robot/src/services/api.ts (1)
  • apiService (99-104)
packages/plugins/robot/src/composables/modes/useChatMode.ts (3)
packages/plugins/robot/src/types/mode.types.ts (1)
  • ModeHooks (25-82)
packages/plugins/robot/src/utils/chat.utils.ts (2)
  • removeLoading (88-97)
  • serializeError (18-28)
packages/plugins/robot/src/composables/features/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/types/chat.types.ts (1)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/plugins/robot/src/composables/modes/useMode.ts (2)
packages/plugins/robot/src/types/mode.types.ts (1)
  • ModeHooks (25-82)
packages/plugins/robot/src/utils/meta.utils.ts (1)
  • getRobotServiceOptions (4-6)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • formatMessages (6-16)
packages/plugins/datasource/src/js/datasource.ts (1)
  • response (101-103)
packages/plugins/robot/src/types/mode.types.ts (1)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
  • ModeHooks (83-83)
packages/plugins/robot/src/utils/schema.utils.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • serializeError (18-28)
packages/plugins/robot/src/composables/features/useToolCalls.ts (4)
packages/plugins/robot/src/composables/features/useMcp.ts (1)
  • useMcpServer (126-137)
packages/plugins/robot/src/utils/chat.utils.ts (2)
  • serializeError (18-28)
  • formatMessages (6-16)
packages/plugins/robot/src/types/chat.types.ts (2)
  • LLMMessage (33-37)
  • RobotMessage (44-49)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/plugins/robot/src/composables/core/pageUpdater.ts (3)
packages/plugins/robot/src/utils/schema.utils.ts (5)
  • getJsonObjectString (151-155)
  • isValidFastJsonPatch (127-134)
  • jsonPatchAutoFix (139-146)
  • fixMethods (48-64)
  • schemaAutoFix (69-82)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/register/src/hooks.ts (1)
  • useHistory (82-82)
packages/plugins/robot/src/composables/modes/useAgentMode.ts (9)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
  • ModeHooks (83-83)
packages/utils/src/utils/index.ts (1)
  • deepClone (328-330)
packages/common/js/completion.js (3)
  • useCanvas (178-178)
  • referenceContext (180-180)
  • referenceContext (195-195)
packages/plugins/robot/src/services/agentServices.ts (2)
  • search (22-41)
  • fetchAssets (47-60)
packages/plugins/robot/src/constants/prompts/index.ts (2)
  • getAgentSystemPrompt (151-171)
  • getJsonFixPrompt (175-206)
packages/plugins/robot/src/utils/chat.utils.ts (2)
  • addSystemPrompt (99-105)
  • removeLoading (88-97)
packages/plugins/robot/src/composables/core/pageUpdater.ts (1)
  • updatePageSchema (73-73)
packages/plugins/robot/src/utils/schema.utils.ts (1)
  • isValidJsonPatchObjectString (160-175)
packages/plugins/robot/src/services/aiClient.ts (1)
  • client (21-21)
packages/plugins/robot/src/utils/chat.utils.ts (1)
packages/plugins/robot/src/types/chat.types.ts (2)
  • LLMMessage (33-37)
  • RobotMessage (44-49)
packages/plugins/robot/src/composables/core/useMessageStream.ts (3)
packages/plugins/robot/src/types/chat.types.ts (1)
  • Message (39-42)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • ResponseToolCall (1-7)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • mergeStringFields (36-55)
packages/plugins/robot/src/services/api.ts (2)
packages/plugins/robot/src/types/chat.types.ts (2)
  • LLMRequestBody (51-57)
  • RequestOptions (5-10)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-25)
packages/plugins/robot/src/composables/features/useMcp.ts (2)
packages/plugins/robot/src/types/chat.types.ts (1)
  • RequestTool (12-31)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-25)
🪛 markdownlint-cli2 (0.18.1)
packages/plugins/robot/src/constants/prompts/templates/agent-prompt.md

46-46: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


47-47: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


48-48: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


49-49: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


50-50: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


51-51: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


52-52: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


53-53: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


54-54: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


55-55: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


56-56: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


57-57: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


59-59: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


60-60: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


61-61: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


62-62: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


63-63: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


64-64: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


65-65: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


67-67: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


68-68: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


69-69: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


70-70: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


71-71: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


72-72: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


73-73: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


74-74: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


75-75: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


76-76: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


77-77: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


78-78: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


79-79: Unordered list indentation
Expected: 4; Actual: 10

(MD007, ul-indent)


80-80: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


81-81: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


82-82: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


83-83: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


84-84: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


85-85: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


86-86: Unordered list indentation
Expected: 2; Actual: 6

(MD007, ul-indent)


93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


103-103: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


110-110: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Comment on lines +49 to +52
const getCurrentMode = (): ModeHooks => {
const { getSelectedModelInfo } = useModelConfig()
return getModeInstance(getSelectedModelInfo().config!.chatMode)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Non-null assertion on config may throw.

Line 51 uses config!.chatMode which assumes config is always defined. If getSelectedModelInfo() returns an object with undefined config, this will throw a runtime error.

 const getCurrentMode = (): ModeHooks => {
   const { getSelectedModelInfo } = useModelConfig()
-  return getModeInstance(getSelectedModelInfo().config!.chatMode)
+  const modelInfo = getSelectedModelInfo()
+  const chatMode = modelInfo.config?.chatMode ?? ChatMode.Chat
+  return getModeInstance(chatMode)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getCurrentMode = (): ModeHooks => {
const { getSelectedModelInfo } = useModelConfig()
return getModeInstance(getSelectedModelInfo().config!.chatMode)
}
const getCurrentMode = (): ModeHooks => {
const { getSelectedModelInfo } = useModelConfig()
const modelInfo = getSelectedModelInfo()
const chatMode = modelInfo.config?.chatMode ?? ChatMode.Chat
return getModeInstance(chatMode)
}
🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/modes/useMode.ts around lines 49 to
52, the code uses a non-null assertion config!.chatMode which will throw if
getSelectedModelInfo().config is undefined; change this to safely handle a
missing config by first retrieving selected = getSelectedModelInfo(), then if
selected.config is undefined provide a sensible fallback (e.g., a default
chatMode or default ModeHooks) or throw a clear, descriptive error; finally pass
the resolved chatMode into getModeInstance (use optional chaining and nullish
coalescing or an explicit guard) so runtime exceptions are avoided and behavior
is deterministic.

Comment on lines +171 to +191
const sendUserMessage = async () => {
nextTick(() => {
const assistantMessage: ChatMessage = {
role: 'assistant',
content: '',
renderContent: [{ type: getLoadingType() }]
}
messageManager.messages.value.push(assistantMessage)
})
await messageManager.send()
if (messageManager.messageState.status === STATUS.ERROR) {
removeLoading(messageManager.messages.value)
await handleRequestError(
messageManager.messageState.errorMsg,
messageManager.messages.value,
messageManager.messageState
)
}
onMessageSent()
autoSetTitle()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

nextTick is not awaited, causing potential race condition.

The nextTick at Line 172 is called but not awaited, meaning messageManager.send() at Line 180 may execute before the assistant message is pushed to the array. This could cause the loading indicator to not appear or the message order to be incorrect.

 const sendUserMessage = async () => {
-  nextTick(() => {
+  await nextTick()
+  {
     const assistantMessage: ChatMessage = {
       role: 'assistant',
       content: '',
       renderContent: [{ type: getLoadingType() }]
     }
     messageManager.messages.value.push(assistantMessage)
-  })
+  }
   await messageManager.send()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sendUserMessage = async () => {
nextTick(() => {
const assistantMessage: ChatMessage = {
role: 'assistant',
content: '',
renderContent: [{ type: getLoadingType() }]
}
messageManager.messages.value.push(assistantMessage)
})
await messageManager.send()
if (messageManager.messageState.status === STATUS.ERROR) {
removeLoading(messageManager.messages.value)
await handleRequestError(
messageManager.messageState.errorMsg,
messageManager.messages.value,
messageManager.messageState
)
}
onMessageSent()
autoSetTitle()
}
const sendUserMessage = async () => {
await nextTick()
const assistantMessage: ChatMessage = {
role: 'assistant',
content: '',
renderContent: [{ type: getLoadingType() }]
}
messageManager.messages.value.push(assistantMessage)
await messageManager.send()
if (messageManager.messageState.status === STATUS.ERROR) {
removeLoading(messageManager.messages.value)
await handleRequestError(
messageManager.messageState.errorMsg,
messageManager.messages.value,
messageManager.messageState
)
}
onMessageSent()
autoSetTitle()
}
🤖 Prompt for AI Agents
In packages/plugins/robot/src/composables/useChat.ts around lines 171 to 191,
nextTick() is invoked but not awaited which can let messageManager.send() run
before the assistant message is actually pushed; update the flow to await
nextTick() (or chain a .then) so the assistant message is guaranteed to be in
messageManager.messages before calling await messageManager.send(), preserving
correct message order and ensuring the loading indicator is present.

@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from c03b8bf to 299a48c Compare December 1, 2025 12:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (8)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)

63-102: Error type detection relies on fragile string matching.

The method infers error types by searching for substrings like "http error", "network", "fetch", and "timeout" in lowercased error messages. This approach is brittle—error messages can vary across browsers, fetch implementations, and network stacks.

Consider enhancing error detection by checking for specific error types first (TypeError for fetch failures, DOMException for aborts) before falling back to string matching, or preserve structured error info (status codes, error names) from the source.

packages/plugins/robot/src/composables/useChat.ts (4)

62-73: Guard against undefined service in beforeRequest.

getSelectedModelInfo().service can be null (as seen in useConfig.ts Line 401), but Lines 66-67 use non-null assertion service!.apiKey. This will throw a runtime error if no service is configured.

Add a guard:

   const { service } = getSelectedModelInfo()
 
-  if (getConfig().apiKey !== service!.apiKey) {
-    updateConfig({ apiKey: service!.apiKey })
+  if (service && getConfig().apiKey !== service.apiKey) {
+    updateConfig({ apiKey: service.apiKey })
   }

89-106: Add null check for lastMessage.

messages.at(-1) at Line 91 can return undefined if the messages array is empty. The subsequent access to lastMessage.content (Line 93) and lastMessage.tool_calls (Line 95) would throw a runtime error.

Add a guard:

   const lastMessage = messages.at(-1)
+  if (!lastMessage) {
+    chatStatus = CHAT_STATUS.FINISHED
+    return
+  }
 
   await onRequestEnd(finishReason, lastMessage.content, messages)

108-112: Add null check for last message in error handler.

Similar to handleFinishRequest, messages.at(-1).content at Line 110 will throw if the messages array is empty.

Apply this diff:

   chatStatus = CHAT_STATUS.FINISHED
-  await onRequestEnd('error', messages.at(-1).content, messages, { error })
+  const lastMessage = messages.at(-1)
+  await onRequestEnd('error', lastMessage?.content ?? '', messages, { error })
   messageState.status = STATUS.ERROR

171-191: nextTick is not awaited, causing potential race condition.

The nextTick at Line 172 is called but not awaited, meaning messageManager.send() at Line 180 may execute before the assistant message is pushed to the array. This could cause the loading indicator to not appear or the message order to be incorrect.

Apply this diff:

   const sendUserMessage = async () => {
-    nextTick(() => {
+    await nextTick()
+    {
       const assistantMessage: ChatMessage = {
         role: 'assistant',
         content: '',
         renderContent: [{ type: getLoadingType() }]
       }
       messageManager.messages.value.push(assistantMessage)
-    })
+    }
     await messageManager.send()
packages/plugins/robot/src/composables/modes/useChatMode.ts (1)

107-117: Potential TypeError: accessing extraData.error without null check.

The extraData parameter is marked as optional (extraData?: Record<string, unknown>), but line 115 accesses extraData.error directly without checking if extraData exists. This will throw a TypeError when the request is aborted or errors without extraData being passed.

Apply this diff:

     if (finishReason === 'aborted' || finishReason === 'error') {
       removeLoading(messages)
-      messages.at(-1)!.renderContent.push({ type: 'text', content: serializeError(extraData.error) })
+      messages.at(-1)!.renderContent.push({ type: 'text', content: serializeError(extraData?.error) })
     }
packages/plugins/robot/src/composables/modes/useAgentMode.ts (2)

93-94: Variable shadowing: pageSchema is redeclared.

Line 94 declares const pageSchema which shadows the function-scoped let pageSchema from line 59. This appears unintentional since line 137 also reassigns the outer pageSchema.

Remove the const to use the outer variable:

   const onBeforeRequest = async (requestParams: any) => {
-    const pageSchema = deepClone(useCanvas().pageState.pageSchema)
+    pageSchema = deepClone(useCanvas().pageState.pageSchema)

207-220: Global config mutation during JSON fix could cause race conditions.

Lines 207 and 220 temporarily change the global apiUrl via updateConfig. If another request fires concurrently, it would use the wrong URL.

Consider passing the URL directly to the client.chat call via a per-request option or use a dedicated client instance for fix requests:

-      updateConfig({ apiUrl: '/app-center/api/chat/completions' })
       messages.at(-1).renderContent.at(-1).status = 'fix'
       const fixedResponse = await client.chat({
         messages: [{ role: 'user', content: getJsonFixPrompt(content, jsonValidResult.error) }],
-        options: { signal: abortControllerMap.errorFix?.signal, beforeRequest: beforeRequest as any }
+        options: { 
+          signal: abortControllerMap.errorFix?.signal, 
+          beforeRequest: (params) => {
+            params.apiUrl = '/app-center/api/chat/completions'
+            return beforeRequest(params)
+          }
+        }
       })
-      updateConfig({ apiUrl: getApiUrl() })
🧹 Nitpick comments (3)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)

268-287: Consider using a consistent return type from axios pathway.

Line 277 attempts to unwrap the response with (response as { data: ChatCompletionResponse }).data || response, which suggests uncertainty about whether the axios client returns the raw data or a wrapper. This fallback pattern can mask bugs if the axios client changes.

Clarify the axios client contract: either always expect { data: ... } or always expect the raw response, and document this assumption. If the axios client is user-provided, validate its response shape at runtime.

packages/plugins/robot/src/components/renderers/AgentRenderer.vue (2)

33-59: Hoist statusDataMap out of setup to avoid per-instance reallocation.

statusDataMap is entirely static and doesn’t depend on reactive state, but it’s recreated on every component instance. Consider moving it to module scope (and optionally freezing/typing it) so it’s allocated once and reused, which also makes it easier to share types and keep status definitions centralized.


35-56: Consider driving titles/content from config or i18n instead of hard-coding.

All user-visible copy and status variants are hard-coded here (Chinese strings + fixed icons). Given the PR’s focus on making the AI plugin more extensible, it might be worth exposing these strings/icons via the robot config or an i18n layer so downstream consumers can localize or customize the agent statuses without touching this renderer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c03b8bf and 299a48c.

📒 Files selected for processing (5)
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue (1 hunks)
  • packages/plugins/robot/src/composables/modes/useAgentMode.ts (1 hunks)
  • packages/plugins/robot/src/composables/modes/useChatMode.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.

Applied to files:

  • packages/plugins/robot/src/composables/modes/useAgentMode.ts
  • packages/plugins/robot/src/composables/useChat.ts
📚 Learning: 2025-05-28T03:58:31.212Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 1440
File: packages/plugins/materials/src/composable/useResource.ts:82-84
Timestamp: 2025-05-28T03:58:31.212Z
Learning: In the TinyEngine codebase, there are two different data structures for page information:
1. App schema components tree (appSchemaState.pageTree) uses nested meta structure with page.meta?.id
2. API responses from pagePluginApi.getPageById() return flattened structure with pageInfo.id and pageInfo.occupier directly
The code should use page.meta?.id when working with pageTree data and pageInfo.id when working with API response data.

Applied to files:

  • packages/plugins/robot/src/composables/modes/useAgentMode.ts
📚 Learning: 2024-10-30T02:19:37.775Z
Learnt from: yy-wow
Repo: opentiny/tiny-engine PR: 886
File: packages/plugins/state/src/js/http.js:19-19
Timestamp: 2024-10-30T02:19:37.775Z
Learning: In the `packages/plugins/state/src/js/http.js` file, errors for the `requestGlobalState` function are handled by the user, so additional error handling is unnecessary.

Applied to files:

  • packages/plugins/robot/src/composables/useChat.ts
🧬 Code graph analysis (3)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • formatMessages (6-16)
packages/plugins/datasource/src/js/datasource.ts (1)
  • response (101-103)
packages/plugins/robot/src/composables/useChat.ts (6)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
  • useMode (59-80)
packages/plugins/robot/src/composables/core/useMessageStream.ts (1)
  • createStreamDataHandler (76-102)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)
  • ChatRequestData (27-32)
  • updateConfig (372-374)
  • ProviderConfig (34-39)
packages/plugins/robot/src/services/api.ts (1)
  • apiService (99-104)
packages/plugins/robot/src/composables/features/useToolCalls.ts (1)
  • createToolCallHandler (83-132)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • removeLoading (88-97)
packages/plugins/robot/src/composables/modes/useChatMode.ts (4)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
  • ModeHooks (83-83)
packages/plugins/robot/src/types/mode.types.ts (1)
  • ModeHooks (25-82)
packages/plugins/robot/src/utils/chat.utils.ts (2)
  • removeLoading (88-97)
  • serializeError (18-28)
packages/plugins/robot/src/composables/features/useMcp.ts (1)
  • useMcpServer (126-137)
🔇 Additional comments (5)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)

323-357: LGTM: Config validation enforces axios client requirement.

The validation at Lines 354-356 correctly ensures that when httpClientType is 'axios', an axiosClient must be provided. This prevents runtime errors downstream.

packages/plugins/robot/src/composables/modes/useChatMode.ts (1)

77-97: LGTM: MCP tool integration follows best practices.

The onBeforeRequest hook correctly fetches MCP tools, checks model capabilities, and conditionally injects them into the request. The logic properly respects the toolCalling capability flag.

packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)

93-133: LGTM: Agent mode request preparation is well-structured.

The onBeforeRequest hook properly builds the RAG context, formats components, injects system prompts, and configures JSON response format. The conditional logic for thinking mode and resource context is appropriate.

packages/plugins/robot/src/components/renderers/AgentRenderer.vue (2)

2-8: Clarify reasoning visibility logic (hasReasoningFinished vs statusData).

For contentType === 'reasoning', statusData always uses the reasoning entry, but hasReasoningFinished flips to true as soon as status !== 'reasoning', which hides this block entirely. That means the “页面生成中” and other non‑reasoning titles will never show for reasoning content, and the component disappears instead. Please double‑check that this matches the intended lifecycle for reasoning → loading → success/failed states.

Also applies to: 61-73


84-108: Styles and layout look clean and appropriate for a compact status bubble.

The flex layout, typography, and ellipsis constraints are straightforward and match the component’s purpose as a small helper/agent-status renderer. No issues from a structure/readability perspective.

Comment on lines 145 to 205
private createFetchAdapter(requestData: ChatRequestData, isStream = false) {
return async (config: AxiosRequestConfig) => {
// 构建完整URL
let url = config.url
if (!url.startsWith('http') && config.apiUrl) {
url = new URL(url, config.apiUrl).href
}

try {
const fetchResponse = await fetch(url, {
method: config.method.toUpperCase(),
headers: config.headers,
body: JSON.stringify(requestData),
signal: config.signal
})

if (!fetchResponse.ok) {
const errorText = await fetchResponse.text()
const customError: any = new Error(
`HTTP error! status: ${fetchResponse.status}${errorText ? ', details: ' + errorText : ''}`
)
customError.response = fetchResponse
throw customError
}

if (isStream) {
// 流式响应处理
return {
data: { response: fetchResponse },
status: fetchResponse.status,
statusText: fetchResponse.statusText,
headers: fetchResponse.headers,
config
}
}

// 非流式响应处理
let responseData: unknown
try {
responseData = await fetchResponse.json()
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
throw new Error(`Failed to parse response JSON: ${errorMessage}`)
}

return {
data: responseData,
status: fetchResponse.status,
statusText: fetchResponse.statusText,
headers: fetchResponse.headers,
config
}
} catch (error) {
// 增强错误信息
if (error instanceof Error) {
throw error
}
throw new Error(`Request failed: ${String(error)}`)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

createFetchAdapter always uses requestData from closure instead of config.data.

The adapter function at Line 145 closes over requestData but ignores config.data passed by axios. This means any transformations axios applies to the request body are silently discarded, breaking interceptors or request transforms.

Apply this diff to use the data from the axios config:

-  private createFetchAdapter(requestData: ChatRequestData, isStream = false) {
+  private createFetchAdapter(isStream = false) {
     return async (config: AxiosRequestConfig) => {
+      const requestData = config.data as ChatRequestData
       // 构建完整URL
       let url = config.url

Then update the call sites at Lines 256 and 304:

-        adapter: this.createFetchAdapter(requestData, isStream)
+        adapter: this.createFetchAdapter(isStream)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/plugins/robot/src/services/OpenAICompatibleProvider.ts around lines
145 to 205, the fetch adapter closes over the outer requestData and ignores
config.data so any axios-transformed request body or interceptors are discarded;
change the adapter to prefer config.data (falling back to the closed-over
requestData if config.data is undefined), serialize appropriately
(JSON.stringify for objects/arrays, pass through strings/Blob/FormData as-is),
and ensure headers like Content-Type are respected; after that, update the
adapter call sites at lines ~256 and ~304 to stop passing the outer requestData
and instead let axios config.data flow through (or explicitly set config.data
where needed).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)

63-102: Error type detection via string matching is fragile.

This concern was already raised in a previous review. The approach of parsing lowercased error messages for substrings like "http error", "network", "fetch", and "timeout" is brittle across different browsers and fetch implementations.


145-157: Adapter ignores config.data in favor of closed-over requestData.

This concern was raised in a previous review. The adapter should use config.data to respect any axios interceptor transformations, rather than always using the requestData captured in the closure.

🧹 Nitpick comments (6)
docs/advanced-features/new-ai-plugin-usage.md (3)

320-320: Add language specifications to fenced code blocks.

Several code blocks lack language identifiers, which prevents syntax highlighting and violates Markdown best practices:

  • Line 320: empty code block (should be text)
  • Line 342, 350, 358, 370, 377, 388: missing language spec (should be appropriate language like text, bash, etc.)

Example fix:

-```
+```text
 用户:帮我查看当前项目有哪些页面

Also applies to: 342-342, 350-350, 358-358, 370-370, 377-377, 388-388


85-86: Clarify Chinese grammar for model selection guidance.

Per LanguageTool, the phrasing around "不全" may benefit from clarification. Current: "模型列表会标注能力标签(工具调用、视觉理解)" suggests the labels are incomplete. Consider: "模型列表会标注模型的能力标签(如工具调用、视觉理解)" for clarity.


101-101: Minor grammar improvement for API Key configuration note.

The phrase "切换AI模型会开启新的会话" can be improved to "切换AI模型会开启一个新的会话" or "切换AI模型会开启新建会话" for better grammatical correctness in Chinese.

packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)

128-129: Redundant toRaw call.

formatMessages already calls toRaw(messages) internally (per chat.utils.ts), so wrapping it again here is unnecessary.

-    const messages = formatMessages(toRaw(request.messages))
+    const messages = formatMessages(request.messages)

283-286: Consider preserving the original error for debugging.

Wrapping the error loses the original stack trace. Using the cause option preserves the error chain.

     } catch (error: unknown) {
       const errorMessage = error instanceof Error ? error.message : String(error)
-      throw new Error(`Error in chat request: ${errorMessage}`)
+      throw new Error(`Error in chat request: ${errorMessage}`, { cause: error })
     }

305-307: Complex and fragile response extraction logic.

The nested type casting and fallback chain is hard to follow and may fail silently if the response structure doesn't match expectations. Consider adding explicit validation.

-        const fetchResponse = (
-          (response as { data: { response: Response } }).data || (response as { response: Response })
-        ).response
+        const axiosResponse = response as { data?: { response?: Response }; response?: Response }
+        const fetchResponse = axiosResponse.data?.response ?? axiosResponse.response
+        if (!fetchResponse) {
+          throw new Error('Failed to extract Response from axios result')
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 299a48c and 2bc03bc.

📒 Files selected for processing (3)
  • docs/advanced-features/new-ai-plugin-usage.md (2 hunks)
  • packages/plugins/robot/index.ts (1 hunks)
  • packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/plugins/robot/index.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • formatMessages (6-16)
packages/plugins/datasource/src/js/datasource.ts (1)
  • response (101-103)
🪛 LanguageTool
docs/advanced-features/new-ai-plugin-usage.md

[uncategorized] ~85-~85: 您的意思是“"不"全”?
Context: ...选。模型列表会标注能力标签(工具调用、视觉理解) - 快速模型:用于代码补全、话题命名等场景,建议选择轻量模型(如flash类型或8b/14b模型)以获得...

(BU)


[uncategorized] ~101-~101: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:新"地"会话
Context: ...sistant-settings.png) 注意:切换AI模型会开启新的会话;若选择的模型未配置API Key,会提示前往模型服务页签进行配置。 ##...

(wb4)

🪛 markdownlint-cli2 (0.18.1)
docs/advanced-features/new-ai-plugin-usage.md

11-11: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


17-17: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


320-320: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


342-342: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


350-350: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


358-358: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


370-370: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


377-377: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


388-388: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


507-507: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


513-513: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


518-518: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


535-535: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


541-541: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (10)
docs/advanced-features/new-ai-plugin-usage.md (2)

1-547: Documentation comprehensiveness aligns well with PR objectives.

The guide effectively documents the refactored AI plugin architecture with clear coverage of:

  • Agent/Chat dual-mode design and mode switching
  • MCP tool integration and usage
  • Multi-model support and custom service configuration
  • Session management and conversation history
  • Troubleshooting and performance guidance

The examples, especially the custom AI model configuration (lines 156-241) and provider setup, provide practical value to users. Consider adding cross-references to the code modules mentioned in PR objectives (e.g., useAgentMode.ts, useChatMode.ts, useConfig.ts) if applicable for developers who may extend the plugin.


34-34: Verify UI positioning inconsistency across documentation.

Line 34 describes the AI assistant plugin bar as "右上角插件栏" (top-right), while Line 117 refers to "左下角插件栏" (bottom-left). Confirm the actual UI location and ensure consistent positioning terminology throughout the document.

Also applies to: 117-117

packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (8)

1-39: LGTM!

The imports are well-organized, and the interface definitions provide good flexibility. Defining local AxiosRequestConfig and AxiosInstance interfaces rather than importing from axios keeps the dependency optional, which aligns well with the configurable HTTP client approach.


41-47: LGTM!

Class properties are well-typed with sensible defaults. The identity function for beforeRequest and 'fetch' as default HTTP client are appropriate choices.


49-57: LGTM!

The constructor correctly initializes the base class and delegates configuration to setConfig. The destructuring at line 54 extracts fields that shouldn't be passed to the parent, though setConfig handles the full config again—this is fine for ensuring consistent initialization.


104-122: LGTM!

Header construction is clean and handles optional authentication correctly. The conditional Accept: text/event-stream for streaming requests is appropriate for SSE.


211-234: LGTM!

Clean fetch implementation with proper error handling. Returning the raw Response object allows flexible handling for both JSON and streaming scenarios.


240-261: LGTM with noted caveat.

The method is well-structured. Note that config.data (line 254) is effectively unused due to the fetch adapter closure issue mentioned in prior review comments.


359-366: LGTM!

The method returns serializable configuration values. Omitting beforeRequest (function) and axiosClient (instance/function) is appropriate for config inspection and persistence use cases.


368-375: LGTM!

Simple delegation pattern provides a consistent API that matches the parent class naming convention.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2bc03bc and 55f90c3.

📒 Files selected for processing (5)
  • designer-demo/public/mock/bundle.json (3 hunks)
  • packages/design-core/src/preview/src/Toolbar.vue (1 hunks)
  • packages/layout/src/Main.vue (1 hunks)
  • packages/register/src/constants.ts (1 hunks)
  • tsconfig.app.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/design-core/src/preview/src/Toolbar.vue
🚧 Files skipped from review as they are similar to previous changes (3)
  • designer-demo/public/mock/bundle.json
  • packages/register/src/constants.ts
  • packages/layout/src/Main.vue
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (1)
tsconfig.app.json (1)

13-13: Verify lib settings do not conflict with base tsconfig.

The addition of "lib": ["ES2023", "DOM", "DOM.Iterable"] may override settings in tsconfig.base.json. Confirm that this does not introduce unintended library-level changes or conflicts, and that DOM/DOM.Iterable are necessary additions (rather than already included via the extended config).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是否可以考虑做成 mcp 工具,然后存放到物料中?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前已经是从物料api获取components了,默认不会用这个json,这个文件我去掉吧

* 创建Axios适配器,使用fetch实现
* @private
*/
private createFetchAdapter(requestData: ChatRequestData, isStream = false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里 requestDataisStream 参数是不是可以从 config 中传进来?这样的话,就不需要每次都创建一个闭包函数了?

比如:

private async fetchAdapter(config: AxiosRequestConfig) {
  // 直接从 config 获取,没有闭包
  const isStream = config.isStream;
  const requestData = JSON.parse(config.data); 
  // ...
}

Comment on lines +74 to +82
if (statusCode === 401 || statusCode === 403) {
type = ErrorType.AUTHENTICATION_ERROR
} else if (statusCode === 429) {
type = ErrorType.RATE_LIMIT_ERROR
} else if (statusCode >= 500) {
type = ErrorType.SERVER_ERROR
} else {
type = ErrorType.NETWORK_ERROR
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里看下要不要用 map 结构,这样后续继续扩展

const errorTypeMap = {
    '401': ErrorType.AUTHENTICATION_ERROR,
    '403': ErrorType.AUTHENTICATION_ERROR,
    '429': ErrorType.RATE_LIMIT_ERROR,
    ....
}

type = ERROR_CODE_MAP[statusCode] 
    || (statusCode >= 500 ? ErrorType.SERVER_ERROR : ErrorType.NETWORK_ERROR)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

后续建议优化:

  1. 颜色使用 css 变量
  2. 支持亮色暗色模式

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robot插件当前还没做暗色支持,后续支持

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

后续优化建议:

  1. tr-welcome 提示词支持配置
  2. Notify 组件使用公共的 useNotify

Comment on lines +156 to +163
singleAttachmentItems.value[0].status = 'done'
singleAttachmentItems.value[0].isUploading = false
singleAttachmentItems.value[0].messageType = 'success'
singleAttachmentItems.value[0].url = resourceUrl
} else {
singleAttachmentItems.value[0].status = 'error'
singleAttachmentItems.value[0].isUploading = false
singleAttachmentItems.value[0].messageType = 'error'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议一次性变更完成(原子操作):

Object.assign(singleAttachmentItems.value[0], {
    status: 'done',
    isUploading: false,
    messageType: 'success',
    url: resourceUrl
})

Comment on lines +5 to +11
<div><h3 style="padding-left: 12px">历史对话</h3></div>
<tr-icon-button
:icon="IconClose"
size="28"
svgSize="20"
@click="showHistory = false"
style="position: absolute; right: 14px; top: 14px"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议不要直接写行内样式

Comment on lines +50 to +89
const convertFlatToGrouped = (flatData: Conversation[]): Array<{ group: string; items: Conversation[] }> => {
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const yesterday = new Date(today)
yesterday.setDate(yesterday.getDate() - 1)
const threeDaysAgo = new Date(today)
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3)
const sevenDaysAgo = new Date(today)
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
const groups: Record<string, { group: string; items: Conversation[] }> = {
today: { group: '今天', items: [] },
yesterday: { group: '昨天', items: [] },
lastThreeDays: { group: '近3天', items: [] },
lastSevenDays: { group: '近7天', items: [] },
earlier: { group: '更早', items: [] }
}
flatData.forEach((item) => {
const itemDate = new Date(item.createdAt)
if (itemDate >= today) {
groups.today.items.push(item)
} else if (itemDate >= yesterday) {
groups.yesterday.items.push(item)
} else if (itemDate >= threeDaysAgo) {
groups.lastThreeDays.items.push(item)
} else if (itemDate >= sevenDaysAgo) {
groups.lastSevenDays.items.push(item)
} else {
groups.earlier.items.push(item)
}
})
Object.values(groups).forEach((group) => {
group.items.sort((a, b) => b.createdAt - a.createdAt)
})
return Object.values(groups).filter((group) => group.items.length > 0)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以试试 AI 建议的重构方法。
优点:

  • 配置化驱动:将时间分组逻辑提取到 groupConfigs 数组中,替代了冗长的 if-else 判断链。这使得添加或修改时间范围变得非常简单。
  • 逻辑复用:通过 getStartOfDay 辅助函数统一处理日期计算,消除了重复代码。
  • 声明式编程:使用 find 方法查找匹配的分组,逻辑更加清晰直观。
  • 健壮性增强:在排序时统一使用 new Date(...).getTime(),确保无论 createdAt 是时间戳还是字符串都能正确排序。
const convertFlatToGrouped = (flatData: Conversation[]): Array<{ group: string; items: Conversation[] }> => {
  const now = new Date()
  // 辅助函数:获取指定天数前的 00:00:00 时间戳
  const getStartOfDay = (offset: number) => {
    const d = new Date(now)
    d.setDate(d.getDate() - offset)
    return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime()
  }

  // 分组配置:按时间倒序排列,确保 find 方法能匹配到最近的时间段
  const groupConfigs = [
    { label: '今天', minTime: getStartOfDay(0) },
    { label: '昨天', minTime: getStartOfDay(1) },
    { label: '近3天', minTime: getStartOfDay(3) },
    { label: '近7天', minTime: getStartOfDay(7) },
    { label: '更早', minTime: 0 }
  ]

  // 初始化分组结构
  const groups = groupConfigs.map((config) => ({
    ...config,
    items: [] as Conversation[]
  }))

  // 遍历数据并分配到对应分组
  flatData.forEach((item) => {
    const itemTime = new Date(item.createdAt).getTime()
    // 查找第一个满足条件的分组(因为配置是按时间倒序的,所以第一个满足的就是最近的范围)
    const targetGroup = groups.find((group) => itemTime >= group.minTime)
    if (targetGroup) {
      targetGroup.items.push(item)
    }
  })

  // 过滤空分组并对组内数据按时间倒序排序
  return groups
    .filter((group) => group.items.length > 0)
    .map(({ label, items }) => ({
      group: label,
      items: items.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
    }))
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的接口,建议补充接口返回的数据类型

*/
export const fetchAssets = async () => {
try {
const res = (await apiService.getResourceList('1')) || []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里为什么写死?

setSaved(false)
}

const _updatePageSchema = (streamContent: string, currentPageSchema: object, isFinial: boolean = false) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应该是 isFinal ?

Comment on lines +207 to +220
updateConfig({ apiUrl: '/app-center/api/chat/completions' })
messages.at(-1).renderContent.at(-1).status = 'fix'
const fixedResponse = await client.chat({
messages: [{ role: 'user', content: getJsonFixPrompt(content, jsonValidResult.error) }],
options: { signal: abortControllerMap.errorFix?.signal, beforeRequest: beforeRequest as any }
})
if (!isValidJsonPatchObjectString(fixedResponse.choices[0].message.content).isError) {
lastMessage.originContent = lastMessage.content
lastMessage.content = fixedResponse.choices[0].message.content
}
} catch (error) {
logger.error('json fix failed', error)
}
updateConfig({ apiUrl: getApiUrl() })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里为什么 207 行使用 updateConfig({ apiUrl: '/app-center/api/chat/completions' }),然后 220 行又 updateConfig({ apiUrl: getApiUrl() }) 呢?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants