Skip to content

feat: ai plugin refactor#1657

Merged
chilingling merged 31 commits intoopentiny:developfrom
hexqi:feat/ai-plugin-refactor
Dec 5, 2025
Merged

feat: ai plugin refactor#1657
chilingling merged 31 commits intoopentiny:developfrom
hexqi:feat/ai-plugin-refactor

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插件使用文档

工具调用生命周期(ChatMode):
image

Agent模式生命周期(AgentMode):
image

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

    • Two-mode AI assistant (Agent for visual page building with live canvas/schema streaming; Chat for conversational workflows)
    • New chat UI, renderers, file-attachment/upload, history panel, footer button, settings and service editor, MCP tool integration, provider/client for OpenAI-compatible backends
    • Conversation management: multi-conversations, auto-titles, mode switching
  • Bug Fixes

    • Silenced canceled-request errors, safer icon handling, right-panel layout adjustment, guards for missing component data
  • Documentation

    • Expanded AI plugin usage guide for two-mode architecture and advanced workflows

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 28, 2025

Walkthrough

Restructures the robot plugin into a composable dual-mode AI system (Agent & Chat), adds streaming/chat/tool-call pipelines and an OpenAI-compatible provider, introduces model/service configuration, JSON-Patch schema streaming/repair, new UI components, and removes legacy JS utilities and prompts.

Changes

Cohort / File(s) Summary
Core Plugin & Meta
packages/plugins/robot/index.ts, packages/plugins/robot/meta.js, packages/plugins/robot/src/metas/index.ts
Updated imports/exports and RobotService wiring; added meta options (customCompatibleAIModels, enableResourceContext, enableRagContext, encryptServiceApiKey, modeImplementation); RobotService.apis now uses useConfig().
Removed Legacy Modules
packages/plugins/robot/src/js/useRobot.ts, packages/plugins/robot/src/js/prompts.ts, packages/plugins/robot/src/js/utils.ts, packages/plugins/robot/src/mcp/utils.ts, packages/plugins/robot/src/BuildLoadingRenderer.vue
Deleted legacy Options-API state, hardcoded PROMPTS, old SSE/chat helpers, MCP utils, and a deprecated loading component.
Core Composables & Adapters
packages/plugins/robot/src/composables/core/useConfig.ts, .../useConversation.ts, .../useMessageStream.ts, .../pageUpdater.ts
Added configuration management, conversation adapter, streaming message handler with delta processors, and throttled page schema updater for Agent mode.
Mode System
packages/plugins/robot/src/composables/modes/useMode.ts, .../useAgentMode.ts, .../useChatMode.ts
Introduced mode registry and runtime dispatcher; implemented Agent and Chat mode hooks and lifecycles.
Chat Workflow
packages/plugins/robot/src/composables/useChat.ts
Implemented streaming chat lifecycle, per-request abort handling, conversation management, tool-call orchestration hooks, and public chat API (send, abort, create/switch conversation).
Tooling & MCP
packages/plugins/robot/src/composables/features/useMcp.ts, .../useToolCalls.ts
Reworked MCP integration (removed server connect stubs), changed tool listing return types, added sequential tool-call orchestration with hooks and abort support.
Provider & Services
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts, .../aiClient.ts, .../agentServices.ts, .../api.ts
Added OpenAI-compatible provider (fetch/axios + streaming), aiClient wiring, agent search/asset helpers, and consolidated apiService (chat, agent, resources, encrypt).
UI — Chat & Renderers
packages/plugins/robot/src/components/chat/RobotChat.vue, .../FooterButton.vue, .../renderers/*.vue, .../renderers/index.ts
New RobotChat UI, FooterButton, and renderer components (Loading, Img, Agent, Markdown) with centralized exports.
UI — Header & Footer Extensions
packages/plugins/robot/src/components/header-extension/*, .../footer-extension/*
Added History, RobotSetting, ServiceEditDialog; refactored McpServer to use FooterButton; RobotTypeSelect now driven by chatMode prop and emits typeChange; added re-export indexes.
Icons & Indexes
packages/plugins/robot/src/components/icons/*, .../icons/index.ts, .../renderers/index.ts
Removed script exports from some SVGs, added centralized icon and renderer re-exports.
Main Component Migration
packages/plugins/robot/src/Main.vue
Migrated to script-setup composition API and integrated new composables, mode-driven rendering, settings, history, and file upload flows.
Types & Constants
packages/plugins/robot/src/types/*.ts, packages/plugins/robot/src/constants/*
Added types for modes, settings, MCP, chat; model-config catalog; prompt builders; re-exported constants.
Utilities & Schema Helpers
packages/plugins/robot/src/utils/chat.utils.ts, schema.utils.ts, meta.utils.ts, utils/index.ts
New message formatting, SSE parsing, error serialization, schema auto-fix, JSON-Patch validation/repair, meta option getter; expanded utils barrel exports.
Schema/Prompt Templates
packages/plugins/robot/src/constants/prompts/templates/agent-prompt.md, .../index.ts, .../data/examples.json
Added strict agent prompt template, dynamic prompt builders, and example payloads consumed by Agent mode.
Layout & Canvas Adjustments
packages/layout/src/DesignSettings.vue, packages/layout/src/Main.vue, packages/layout/src/composable/useLayout.ts, packages/canvas/container/src/components/CanvasResize.vue
Removed top offset on right-panel, set right-wrap position relative, added toolbars.render state and watcher to recalc canvas scale on toolbar render changes.
Misc. Integrations & Fixes
packages/common/js/completion.js, packages/register/src/constants.ts, packages/plugins/resource/src/ResourceList.vue
Consolidated meta-register imports, added META_SERVICE.Robot, and included description in resource batch payload.
Package & Config
packages/plugins/robot/package.json, tsconfig.app.json
Bumped several robot packages from RC to stable, added @vueuse/core, reordered dependencies; tsconfig: "jsx": "preserve" and updated lib targets.
Demo & Docs
designer-demo/*, docs/*
Suppressed canceled request errors in demo http helper, renamed demo snippets, moved useRobot implementation path, and replaced docs with a dual-mode usage guide.
Small Component Fixes
packages/configurator/src/select-icon-configurator/SelectIconConfigurator.vue
Added optional chaining when resolving SVG icon creators to prevent runtime errors.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Main / RobotChat
    participant ChatFlow as useChat
    participant ModeMgr as useMode
    participant ModeImpl as Agent/Chat Mode
    participant API as apiService / OpenAICompatibleProvider
    participant MCP as MCP / useMcp

    User->>UI: Submit message / upload image / click prompt
    UI->>ChatFlow: sendUserMessage(payload)
    ChatFlow->>ModeMgr: getCurrentMode()
    ModeMgr-->>ChatFlow: ModeHooks instance
    ChatFlow->>ModeImpl: onBeforeRequest(requestParams)
    ModeImpl->>API: chatStream(requestData)
    API-->>ModeImpl: stream chunks (content, tool_calls)
    ModeImpl->>ChatFlow: onStreamData(chunk)
    ChatFlow->>UI: update renderContent / messages

    alt tool_calls present
        ModeImpl->>MCP: callTools(tool_calls)
        MCP-->>ModeImpl: tool results
        ModeImpl->>API: stream tool-derived content (chatStream)
        API-->>ModeImpl: tool stream chunks
        ModeImpl->>ChatFlow: onStreamTools / onPostCallTools
        ChatFlow->>UI: render tool results
    end

    ModeImpl->>ChatFlow: onRequestEnd(finishReason)
    ChatFlow->>UI: finalize message state
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Areas requiring extra attention:
    • useChat.ts and conversation adapter: streaming delta handling, abortControllerMap concurrency, state race conditions.
    • useConfig.ts: migration, persistence, encryptServiceApiKey behavior, and model/service merging correctness.
    • useAgentMode.ts and pageUpdater: JSON-Patch parsing/repair, apply/rollback semantics, and throttled updates.
    • OpenAICompatibleProvider & aiClient: SSE vs axios streaming paths, error mapping, and headers/auth handling.
    • useToolCalls.ts and useMcp.ts: sequential tool-call orchestration, abort semantics, and MCP integration edge cases.
    • New types & re-exports: check for circular imports and consistent type shapes across modules.
    • Main.vue and new components: ensure public props/events compatibility and template-driven behavior.

Poem

🐰
I nibble code and stitch the streams,
Two modes awaken from my dreams,
Patches hop to paint the page,
Tools converse upon the stage,
A rabbit smiles — the UI beams!

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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: ai plugin refactor' is directly related to the changeset, which involves a comprehensive refactor of the AI plugin with agent-mode improvements, new features, UX/UI updates, and bug fixes.
✨ 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.

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: 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)

@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.

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).

@hexqi hexqi changed the title feat: ai plugin refactor [WIP] [modify CR...] feat: ai plugin refactor Dec 3, 2025
@github-actions github-actions bot removed the enhancement New feature or request label Dec 3, 2025
@hexqi
Copy link
Collaborator Author

hexqi commented Dec 4, 2025

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

  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.


I feel that 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 the 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, // Enter 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.
    Suggestions:
  • Integrate the 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.

Already supported

@hexqi
Copy link
Collaborator Author

hexqi commented Dec 4, 2025

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

已支持

@hexqi hexqi changed the title [WIP] [modify CR...] feat: ai plugin refactor feat: ai plugin refactor Dec 4, 2025
@github-actions github-actions bot added the enhancement New feature or request label Dec 4, 2025
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: 12

♻️ Duplicate comments (13)
packages/plugins/robot/src/components/chat/FooterButton.vue (1)

44-61: Replace hardcoded colors with CSS variables for theme support.

Multiple hardcoded color values prevent dark mode support and theming. This was flagged in previous reviews but remains unresolved.

Hardcoded colors to replace:

  • Line 44: rgb(194, 194, 194) (border)
  • Line 50: rgba(0, 0, 0, 0.08) (hover background)
  • Lines 54-56, 59: rgb(20, 118, 255) variations (active state)

Apply this diff to use CSS variables:

   width: fit-content;
   border-radius: 32px;
   height: 32px;
   padding: 0px 8px;
-  border: 1px solid rgb(194, 194, 194);
+  border: 1px solid var(--te-base-border-color);
   cursor: pointer;
   box-sizing: border-box;
   background-color: var(--te-base-gray-10);
 
   &:hover {
-    background-color: rgba(0, 0, 0, 0.08);
+    background-color: var(--te-base-hover-bg);
   }
 
   &.active {
-    border: 1px solid rgb(20, 118, 255);
-    background: rgba(20, 118, 255, 0.08);
-    color: rgb(20, 118, 255);
+    border: 1px solid var(--te-base-primary-color);
+    background: var(--te-base-primary-bg-light);
+    color: var(--te-base-primary-color);
 
     &:hover {
-      background: rgba(20, 118, 255, 0.12);
+      background: var(--te-base-primary-bg-lighter);
     }
   }
packages/plugins/robot/src/components/header-extension/History.vue (1)

2-2: Inline style remains on wrapper element.

A past review requested avoiding inline styles. This wrapper span still uses style="display: inline-flex; line-height: 0; position: relative". Consider moving these styles to the scoped CSS block.

 <template>
-  <span style="display: inline-flex; line-height: 0; position: relative">
+  <span class="history-trigger">

Add to scoped styles:

.history-trigger {
  display: inline-flex;
  line-height: 0;
  position: relative;
}
packages/plugins/robot/src/composables/modes/useMode.ts (1)

49-52: Non-null assertion on config may throw at runtime.

This was flagged in a previous review. If getSelectedModelInfo().config is undefined, line 51 will throw. The suggested fix was to use optional chaining with a fallback.

 const getCurrentMode = (): ModeHooks => {
   const { getSelectedModelInfo } = useModelConfig()
-  return getModeInstance(getSelectedModelInfo().config!.chatMode ?? ChatMode.Agent)
+  const modelInfo = getSelectedModelInfo()
+  const chatMode = modelInfo.config?.chatMode ?? ChatMode.Agent
+  return getModeInstance(chatMode)
 }
packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)

28-48: Non-null assertions on tool.function may cause runtime errors.

Similar to useChatMode.ts, lines 35, 39, and 42 use non-null assertions (tool.function!) when accessing properties. If malformed tool calls arrive, these will throw.

Apply the same fix as suggested for useChatMode.ts:

-    currentToolCallContent.content.params = tool.parsedArgs || tool.function!.arguments || {}
+    currentToolCallContent.content.params = tool.parsedArgs || tool.function?.arguments || {}

And:

-      name: tool.name || tool.function!.name,
+      name: tool.name || tool.function?.name || 'unknown',
packages/plugins/robot/src/utils/schema.utils.ts (1)

139-146: Typo: isFinial should be isFinal.

The parameter name at line 139 and its usage at line 142 have the same spelling error that was corrected in pageUpdater.ts. This affects searchability and consistency across the codebase.

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 the call site in pageUpdater.ts line 45 to match:

-  const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal)
+  const validJsonPatches = jsonPatchAutoFix(jsonPatches, isFinal)
packages/plugins/robot/src/composables/useChat.ts (2)

89-108: Missing null check for lastMessage.

Line 96 retrieves lastMessage with .at(-1) which returns undefined if the messages array is empty. Line 99 then accesses lastMessage.content without checking if lastMessage exists, which will throw a TypeError.

Add a guard:

 const handleFinishRequest = async (
   finishReason: string,
   messages: ChatMessage[],
   contextMessages: ChatMessage[],
   messageState: MessageState
 ) => {
   chatStatus = CHAT_STATUS.PROCESSING
   const lastMessage = messages.at(-1)
+  if (!lastMessage) {
+    console.warn('handleFinishRequest called with empty messages array')
+    return
+  }
 
   delete abortControllerMap.main
   await onRequestEnd(finishReason, lastMessage.content, messages)

110-115: Missing null check in error handler.

Similar issue: line 113 accesses messages.at(-1).content without checking if the array is empty.

Apply the same fix:

 const handleRequestError = async (error: Error, messages: ChatMessage[], messageState: MessageState) => {
   chatStatus = CHAT_STATUS.FINISHED
   delete abortControllerMap.main
-  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
 }
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (3)

161-221: createFetchAdapter ignores transformed request body from axios config.

The adapter at line 162 uses the data from its closure (from the outer scope when createFetchAdapter is called), but axios may have transformed the request body via interceptors or transforms. Line 173 casts config.data as string, but this ignores any transformations axios applied and assumes the data is always a string.

This breaks axios interceptors and any custom request transformations. Consider reading from config.data instead:

 private createFetchAdapter(isStream = false) {
   return async (config: AxiosRequestConfig) => {
+    const requestBody = typeof config.data === 'string' 
+      ? config.data 
+      : JSON.stringify(config.data)
+
     // 构建完整URL
     let url = config.url
     if (!url.startsWith('http') && config.baseURL) {
       url = new URL(url, config.baseURL).href
     }
 
     try {
       const fetchResponse = await fetch(url, {
         method: config.method.toUpperCase(),
         headers: config.headers,
-        body: config.data as string,
+        body: requestBody,
         signal: config.signal
       })

297-323: Response extraction treats falsy values as missing data.

Line 313 uses (response as { data: ChatCompletionResponse }).data || response, which will incorrectly return the entire response object if data is any falsy value (null, 0, '', false). If the API returns a valid but falsy response, this breaks.

Use explicit undefined/null checks:

-        return (response as { data: ChatCompletionResponse }).data || response
+        const axiosResponse = response as { data?: ChatCompletionResponse }
+        return axiosResponse.data !== undefined ? axiosResponse.data : (response as ChatCompletionResponse)

385-395: Silent fallback from axios to fetch may confuse users.

When httpClientType: 'axios' is provided without axiosClient, the code at line 389 silently falls back to 'fetch' rather than throwing an error. The validation at lines 393-395 only catches cases where this.httpClientType was already 'axios' from a previous call, missing the initial misconfiguration.

Fail explicitly when axios is requested without a client:

     if (httpClientType === 'axios' && axiosClient) {
       this.httpClientType = 'axios'
       this.axiosClient = axiosClient
-    } else if (httpClientType) {
+    } else if (httpClientType === 'axios' && !axiosClient) {
+      throw new Error('axiosClient is required when httpClientType is axios')
+    } else if (httpClientType === 'fetch') {
       this.httpClientType = 'fetch'
     }
-
-    // 验证配置
-    if (this.httpClientType === 'axios' && !this.axiosClient) {
-      throw new Error('axiosClient is required when httpClientType is axios')
-    }
packages/plugins/robot/src/composables/core/useConfig.ts (3)

362-371: updateService now avoids wiping API keys; logic looks good.

The new pattern only re-encrypts the key when apiKey is explicitly present in updates, preserving the existing key otherwise. This resolves the earlier concern about accidentally clearing API keys.


385-415: getSelectedModelInfo now safely handles missing service/model.

Using currentService ?? ({} as Partial<ModelService>) avoids the previous non-null assertion issue while still returning a stable SelectedModelInfo shape. This aligns with prior review feedback.


417-441: getSelectedQuickModelInfo mirrors getSelectedModelInfo safety improvements.

Same pattern here: guarded destructuring and null service when not found makes the consumer side much more robust without throwing.

🧹 Nitpick comments (32)
packages/plugins/robot/src/components/chat/FooterButton.vue (1)

78-113: Remove unused CSS classes.

The .plugin-common and .plugin-active classes are not referenced in the template or mentioned in the component's usage context.

Apply this diff to remove dead code:

   .button {
     display: flex;
     align-items: center;
     justify-content: center;
     gap: 4px;
   }
-
-  .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;
-    }
-  }
 }
packages/plugins/robot/src/components/chat/RobotChat.vue (4)

87-108: Strengthen type safety for function props.

The promptClickHandler, beforeSubmit, and status props lack precise type constraints, which could lead to runtime errors if consumers pass incompatible values.

Apply this diff to add type safety:

+import type { ChatStatus } from '@opentiny/tiny-robot-kit'
+
 const { promptItems, status, promptClickHandler, allowFiles, bubbleRenderers, beforeSubmit } = defineProps({
   promptItems: {
     type: Array as PropType<PromptProps[]>,
     default: () => []
   },
   promptClickHandler: {
-    type: Function
+    type: Function as PropType<(item: PromptProps) => void>
   },
-  status: { type: String },
+  status: { 
+    type: String as PropType<ChatStatus>,
+    required: true
+  },
   allowFiles: {
     type: Boolean,
     default: false
   },
   bubbleRenderers: {
     type: Object as PropType<Record<string, Component>>,
     default: () => ({})
   },
   beforeSubmit: {
-    type: Function,
+    type: Function as PropType<(content: string) => boolean | Promise<boolean>>,
     default: () => true
   }
 })

276-282: Add guard for undefined description in prompt click fallback.

Line 280 passes item.description which may be undefined. While handleSendMessage validates empty content (Lines 219-221), it's clearer to check this explicitly before calling.

Apply this diff:

 const handlePromptItemClick = (ev: unknown, item: { description?: string }) => {
   if (promptClickHandler && typeof promptClickHandler === 'function') {
     promptClickHandler(item)
   } else {
-    handleSendMessage(item.description)
+    if (item.description) {
+      handleSendMessage(item.description)
+    }
   }
 }

190-195: Clarify renderer override behavior.

The spread operator on Line 194 allows bubbleRenderers prop to override built-in renderers (markdown, loading, img). While this may be intentional for customization, it could lead to unexpected behavior if consumers accidentally use the same keys.

Consider either:

  1. Documenting this override behavior in component docs/comments, or
  2. Reversing the spread order to prioritize built-ins:
 const contentRenderers = computed(() => ({
+  ...bubbleRenderers,
   markdown: MarkdownRenderer,
   loading: LoadingRenderer,
-  img: ImgRenderer,
-  ...bubbleRenderers
+  img: ImgRenderer
 }))

Choose based on whether you want consumers to be able to override built-in renderers.


285-483: Consider reducing reliance on deep selectors.

The styles contain many :deep() selectors (Lines 296, 311, 323, 326, 331, 335, 373, 379, 396, 442, 449, 454, 463, 467, 471) which tightly couple this component to the internal DOM structure of child components. While functional, this makes the code fragile if child component internals change.

Consider working with child component maintainers to expose CSS custom properties or explicit style props instead of piercing encapsulation.

packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (3)

217-226: Potential runtime error when splitting empty string.

When state.modelSelection.defaultModel is empty (e.g., initial state or cleared), split('::') returns [''], causing getServiceById('') to be called. While this likely returns null safely, it's worth defensive handling.

 const selectedDefaultModelInfo = computed(() => {
-  const [serviceId] = state.modelSelection.defaultModel.split('::')
+  if (!state.modelSelection.defaultModel) return null
+  const [serviceId] = state.modelSelection.defaultModel.split('::')
   const service = getServiceById(serviceId)
   if (!service) return null

282-290: Type assertion bypasses type safety.

The as any cast on line 288 hides potential type mismatches between Partial<ModelService> and the expected parameter type for addCustomService.

Consider defining a proper type for new service creation (e.g., NewModelService) that matches the required fields for addCustomService, rather than using as any.


273-276: Consider using structuredClone for deep cloning.

JSON.parse(JSON.stringify(...)) works but loses undefined values, Date objects, and other non-JSON-serializable types. If the ModelService type contains such values, this could cause subtle bugs.

 const editService = (service: ModelService) => {
-  state.editingService = JSON.parse(JSON.stringify(service))
+  state.editingService = structuredClone(service)
   state.showServiceDialog = true
 }
packages/plugins/robot/src/components/header-extension/History.vue (1)

79-79: Ensure type-safe date comparison in sort.

Direct subtraction b.createdAt - a.createdAt assumes createdAt is a numeric timestamp. If createdAt could be a Date object or ISO string, this would yield NaN.

-      items: group.items.sort((a, b) => b.createdAt - a.createdAt)
+      items: group.items.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
packages/plugins/robot/src/Main.vue (1)

267-272: Consider documenting or extracting magic timeout values.

The setTimeout with 1000ms delay is used without explanation. If this is to avoid layout thrashing or race conditions, consider adding a comment or extracting to a named constant.

+// Delay teleport mounting to avoid initial layout thrashing
+const TELEPORT_MOUNT_DELAY = 1000
+
 onMounted(async () => {
   initChatClient()
   setTimeout(() => {
     showTeleport.value = true
-  }, 1000)
+  }, TELEPORT_MOUNT_DELAY)
 })
packages/plugins/robot/src/services/agentServices.ts (2)

29-34: return inside forEach only skips current item, not the entire iteration.

The intent seems to be stopping when the limit is reached, but return inside forEach only skips to the next item. All remaining items will still be processed (though not appended).

Use for...of with break, or reduce, or some to stop early:

-    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-61: Type safety improvement for resource processing.

The chain uses any types extensively. Consider defining proper interfaces for the API response structure to improve maintainability and catch errors at compile time.

interface ResourceGroup {
  resources: Array<{
    resourceUrl: string
    description?: string
  }>
}

export const fetchAssets = async (appId: string): Promise<{ url: string; describe: string }[]> => {
  try {
    const res = (await apiService.getResourceList(appId)) as ResourceGroup[] || []
    return res
      .flatMap((group) => group.resources)
      .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/composables/modes/useMode.ts (1)

29-30: Module-level cache may cause issues with HMR or testing.

modeInstanceCache is a module-level object that persists across component instances. This could cause stale state during hot module replacement or make unit testing difficult. Consider providing a cache-clearing mechanism or making the cache instance-scoped if needed.

// Add a function to clear cache for testing/HMR scenarios
export const clearModeCache = () => {
  Object.keys(modeInstanceCache).forEach(key => delete modeInstanceCache[key])
}
packages/plugins/robot/src/services/api.ts (5)

19-55: Consider typing chatCompletions / agentChat responses explicitly.

Right now both methods rely on the HTTP client’s implicit return type. Defining concrete response types (or at least Promise<unknown> / Promise<any>) will make the public surface clearer and help catch mismatches if the backend shape changes.


39-46: Align agentChat with chatCompletions options behavior.

chatCompletions supports overriding url and merging headers from options, but agentChat hardcodes the URL and only merges headers. If callers may need to customize the endpoint (e.g., proxying / multi-env), it’s worth adding optional options.url handling here for symmetry.


57-75: Export resource-related types if they are part of the public surface.

Resource, ResourceGroup, and ResourceGroupList are currently file-local. If other modules consume resourceApi and rely on these shapes, exporting them will avoid duplicated type definitions and keep contracts in sync with the backend.


112-119: Narrow getHttpClient return type if possible.

Currently the return type is inferred from getMetaApi(META_SERVICE.Http)?.getHttp(), which is effectively any to most consumers. If the HTTP client has a stable interface (axios-like, fetch-like), consider declaring a small interface and using it as the return type for better IntelliSense and safer usage.


121-124: Clarify error contract for encryptKey.

encryptKey is assumed to always resolve to { token: string }. Downstream code (e.g., encryptServiceApiKey) should be robust to HTTP failures / unexpected payloads; if the HTTP layer can throw or return non-2xx, you may want to document that here or wrap with a safer helper.

packages/plugins/robot/src/composables/core/useConfig.ts (14)

39-45: Tighten getAIModelOptions typing to reduce any downstream.

getAIModelOptions currently returns any[] (inferred from mergeAIModelOptions(DEFAULT_LLM_MODELS, customAIModels)). Since DEFAULT_LLM_MODELS has a known provider/model shape, consider adding an explicit return type (e.g., ProviderConfig[]) so code using this helper doesn’t lose type safety when accessing provider, models, etc.


48-59: Avoid any in initBuiltInServices mapping if possible.

service: any is only used to access known fields (provider, label, baseUrl, allowEmptyApiKey, models). If you introduce a shared ProviderConfig type for DEFAULT_LLM_MODELS/custom providers, you can replace any here and let TS catch config mistakes at compile time.


62-80: Guard against empty builtInServices edge case.

initDefaultSettings assumes builtInServices[0] exists. In case DEFAULT_LLM_MODELS or merged options are empty/misconfigured, this will still behave (falling back to empty IDs) but the resulting settings won’t have any usable model. Consider explicitly handling the “no services” case (e.g., returning services: [], defaultModel/quickModel with empty IDs) and logging for easier debugging.


84-148: Consider making migrateOldSettings id generation more stable.

Using Date.now() for custom_${Date.now()} works but makes tests / snapshots harder to reason about. If old settings already imply a unique key (e.g., baseUrl + model), deriving IDs from those values or a small hash would be more deterministic while still unique enough for this context.


150-169: loadRobotSettingState silently discards invalid JSON; consider minimal logging.

On JSON parse errors the function just returns null. Adding a small console.warn (or shared logger) would help diagnose corrupted localStorage without impacting UX.


171-178: saveRobotSettingState reads from storage again; consider merging in-memory state instead.

saveRobotSettingState calls loadRobotSettingState() and initDefaultSettings() to build currentState, even though robotSettingState is already up-to-date when updateState is true. This can introduce subtle divergence if a caller mutates robotSettingState directly. Using robotSettingState as the base source of truth (and only falling back to defaults when it’s uninitialized) would simplify the mental model and avoid extra JSON parse.


194-258: mergeAIModelOptions works but is heavy on any; a shared config type would help.

The deep-merge logic and _remove handling look correct, but pervasive any (providers, models) weakens static guarantees and makes refactors risky. Introducing explicit interfaces (e.g., CompatibleProviderConfig, CompatibleModelConfig with an optional _remove flag) would let TS enforce field names and catch typos in provider/model configs early.


261-285: mergeServices drops all non-API fields from cached built-ins; verify that’s desired.

For built-in services with a cached entry, you only preserve apiKey and overwrite everything else with the latest built-ins (...builtIn, apiKey: cached.apiKey || ''). If future fields on ModelService become user-editable per built-in (e.g., custom label), this will discard them on init. If that’s not intended, consider merging from cached while letting built-in defaults win only for specific fields (e.g., models).


287-295: Confirm init should always re-save to localStorage on every call.

init calls saveRobotSettingState(settingState) even when it just loaded a valid state. If init is invoked frequently (e.g., on plugin mount), this will repeatedly write to localStorage. Not a big issue, but you might add a cheap equality check or only save when migration/merge actually changed something.


297-304: getModelCapabilities returns null or undefined; consider normalizing.

Currently this can return null (when ids are missing) or undefined (when service/model not found). Normalizing to null (or a consistent empty object) makes consumer code simpler and avoids double checks.


307-319: getAllAvailableModels shape might deserve an exported type.

The flattened model objects (serviceId, serviceName, modelName, modelLabel, capabilities, displayLabel, value) are a semi-public contract used by UI. Defining a named interface (e.g., AvailableModel) will help keep UI and core consistent as fields evolve.


326-334: Side-effectful setters look good; optional: debounce persistence.

updateThinkingState / updateChatModeState both immediately write to localStorage. If these toggles can change rapidly (e.g., user dragging a slider or rapidly switching modes), consider debouncing saves to avoid excessive writes; otherwise this is fine.


336-347: encryptServiceApiKey behavior is reasonable; consider surfacing failure to callers.

On failure it logs and returns the raw apiKey. For security-sensitive scenarios, callers might want to know encryption failed (e.g., to show a warning badge). Returning a tuple or throwing on failure would be more explicit; alternatively, document that “failure falls back to cleartext” so consumers can choose whether to accept it.


349-360: addCustomService ID generation and encryption look correct.

The function correctly encrypts service.apiKey, marks the service as non-built-in, and persists state once. Only optional improvement would be the same determinism comment about custom_${Date.now()} as in migration, if snapshot tests ever need stable IDs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55f90c3 and 3c329b0.

📒 Files selected for processing (22)
  • packages/plugins/robot/src/Main.vue (2 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/header-extension/History.vue (1 hunks)
  • packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1 hunks)
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue (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/useMessageStream.ts (1 hunks)
  • packages/plugins/robot/src/composables/features/useMcp.ts (2 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/prompts/data/examples.json (1 hunks)
  • packages/plugins/robot/src/constants/prompts/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/utils/schema.utils.ts (1 hunks)
  • tsconfig.app.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/plugins/robot/src/constants/prompts/data/examples.json
  • tsconfig.app.json
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue
  • packages/plugins/robot/src/composables/features/useMcp.ts
  • packages/plugins/robot/src/constants/prompts/index.ts
🧰 Additional context used
🧠 Learnings (5)
📚 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-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
  • packages/plugins/robot/src/components/chat/RobotChat.vue
📚 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
📚 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/robot/src/composables/modes/useChatMode.ts
🧬 Code graph analysis (9)
packages/plugins/robot/src/services/aiClient.ts (1)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (2)
  • ProviderConfig (42-47)
  • OpenAICompatibleProvider (49-414)
packages/plugins/robot/src/composables/modes/useMode.ts (4)
packages/plugins/robot/src/types/mode.types.ts (1)
  • ModeHooks (25-82)
packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)
  • useAgentMode (59-279)
packages/plugins/robot/src/composables/modes/useChatMode.ts (1)
  • useChatMode (53-179)
packages/plugins/robot/src/utils/meta.utils.ts (1)
  • getRobotServiceOptions (4-6)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
  • useMode (59-80)
packages/plugins/robot/src/composables/core/useMessageStream.ts (1)
  • createStreamDataHandler (76-106)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
  • ChatRequestData (35-40)
packages/plugins/robot/src/services/api.ts (1)
  • apiService (126-131)
packages/plugins/robot/src/composables/core/useConversation.ts (1)
  • useConversationAdapter (22-108)
packages/plugins/robot/src/composables/features/useToolCalls.ts (1)
  • createToolCallHandler (83-132)
packages/plugins/robot/src/composables/core/useConfig.ts (1)
  • robotSettingState (443-468)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • removeLoading (88-97)
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/agentServices.ts (1)
packages/plugins/robot/src/services/api.ts (1)
  • apiService (126-131)
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/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/utils/schema.utils.ts (1)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • serializeError (18-28)
packages/plugins/robot/src/composables/core/useConfig.ts (4)
packages/plugins/robot/src/types/setting.types.ts (4)
  • RobotSettings (52-59)
  • ModelService (30-39)
  • SelectedModelInfo (61-76)
  • ModelConfig (16-25)
packages/plugins/robot/src/utils/meta.utils.ts (1)
  • getRobotServiceOptions (4-6)
packages/plugins/robot/src/constants/model-config.ts (1)
  • DEFAULT_LLM_MODELS (11-94)
packages/plugins/robot/src/services/api.ts (1)
  • apiService (126-131)
⏰ 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 (13)
packages/plugins/robot/src/components/chat/FooterButton.vue (1)

116-125: @container queries have widespread browser support—clarify if legacy browser support is a project requirement.

@container queries are now baseline across all major browsers (Chrome 105+, Edge 105+, Safari 16+, Firefox 110+) and have been since 2022–2023. This support exceeds the browser matrix already accepted by your project (which supports :has() on Safari 15.4+ and Edge 105+). If your project targets only modern browsers, no action is needed. If legacy browser support is required, consider using a Container Query Polyfill or @supports feature detection.

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

272-274: Previous abort handling issue has been resolved.

The abort logic has been simplified to only emit the event, delegating the actual abort handling (including setting the aborted flag) to the parent component. This eliminates the bounds-check issue that was previously flagged.

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

1-142: Well-structured settings UI with good UX patterns.

The template uses appropriate form validation flow, capability tags for model selection, and confirmation dialogs for destructive actions. The tab-based organization and API key status indicators provide clear user feedback.

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

50-81: Clean refactored grouping logic.

The configuration-driven approach with groupConfigs is much cleaner than the previous if-else chain. The implementation correctly handles threshold-based grouping and filters empty groups.

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

256-264: Good error feedback implementation.

The file upload error handling now includes user notification via useNotify, addressing the previous review concern about silent failures.


199-215: API key validation flow is well-implemented.

The checkApiKey function properly validates both baseUrl and apiKey presence, respects allowEmptyApiKey configuration, provides user notification, and auto-opens settings panel. This is a good UX pattern.

packages/plugins/robot/src/composables/modes/useMode.ts (1)

59-79: Clean proxy pattern for runtime mode switching.

The delegation pattern allows seamless mode switching at runtime without requiring consumers to track mode changes. Each method call dynamically resolves to the current mode's implementation.

packages/plugins/robot/src/composables/core/pageUpdater.ts (1)

73-73: LGTM!

The throttled wrapper with a 200ms interval is appropriate for rate-limiting streaming schema updates, preventing excessive DOM updates while maintaining responsiveness.

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

75-87: LGTM! Well-structured initialization.

The initChatClient function properly initializes the provider configuration with all necessary parameters and uses a factory-provided axios client. The conditional service?.apiKey handles the case where no service is configured.

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

141-155: LGTM! Clean request preparation with proper hooks.

The prepareRequestData method correctly formats messages, merges model selection from multiple sources, and applies the beforeRequest hook at the appropriate time. The deep clone via JSON.parse(JSON.stringify(...)) ensures no mutation of the original request.

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

84-90: Verify backend really returns a full Resource on upload.

uploadFile is typed as Promise<Resource>, but many upload endpoints return only IDs or minimal metadata. If the actual API returns a different shape (e.g., { id: string }), this typing could be misleading and leak through to consumers.

packages/plugins/robot/src/composables/core/useConfig.ts (2)

373-379: deleteService correctly protects built-ins.

Guarding on !isBuiltIn before deletion is a good safety measure; behavior is clear and consistent with expectations for built-in providers.


443-468: Default export surface is cohesive and clear.

Exposing read-only state plus focused utilities (init is separate) gives consumers a clean API for reading config and managing services/models. No changes needed here.

@hexqi hexqi force-pushed the feat/ai-plugin-refactor branch from 3c329b0 to 53501ed Compare December 4, 2025 10:43
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: 6

♻️ Duplicate comments (13)
packages/plugins/robot/src/constants/prompts/data/examples.json (1)

75-75: Component ID still violates specification requirements.

The ID "f2525253" contains only lowercase letters and digits. Per the specification requirement mentioned in the previous review, component IDs must contain at least one uppercase letter, one lowercase letter, and one digit.

Replace with a compliant ID:

-                  "id": "f2525253",
+                  "id": "fL2m5x3R",

Ensure any references to this ID elsewhere in the file are also updated.

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

69-82: Missing circular reference protection in schemaAutoFix.

The recursive function traverses data.children without tracking visited nodes. If the schema contains circular references, this will cause infinite recursion and stack overflow.

This was flagged in a previous review. Consider adding a WeakSet to track visited nodes.

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

385-395: Silent fallback from axios to fetch when axiosClient is missing.

When httpClientType: 'axios' is passed without axiosClient, line 388-390 silently sets httpClientType to 'fetch'. The validation at lines 392-395 never triggers because this.httpClientType was already changed to 'fetch'.

This was flagged in a previous review. Consider failing explicitly when axios is requested but client is not provided.


306-318: Response extraction may fail for falsy but valid responses.

Line 313 uses || response which would return the entire axios response object if data is a valid but falsy value. While unlikely for chat completions, consider using nullish coalescing for correctness.

This was noted in a previous review. Consider using ?? response instead of || response.

packages/plugins/robot/src/composables/modes/useMode.ts (1)

49-52: Non-null assertion on config may throw at runtime.

Line 51 uses config!.chatMode which assumes config is always defined. If getSelectedModelInfo() returns an object with undefined config, this will cause a runtime error.

Apply this diff to add a fallback:

 const getCurrentMode = (): ModeHooks => {
   const { getSelectedModelInfo } = useModelConfig()
-  return getModeInstance(getSelectedModelInfo().config!.chatMode ?? ChatMode.Agent)
+  const modelInfo = getSelectedModelInfo()
+  const chatMode = modelInfo.config?.chatMode ?? ChatMode.Agent
+  return getModeInstance(chatMode)
 }
packages/plugins/robot/src/composables/modes/useChatMode.ts (2)

19-43: Non-null assertions on tool.function may cause runtime errors.

Lines 26, 33, 36 use non-null assertions (tool.function!) when accessing tool.function.name and tool.function.arguments. If malformed tool calls arrive from the LLM, these assertions will throw runtime errors.

Apply optional chaining with fallbacks:

-    currentToolCallContent.content.params = tool.parsedArgs || tool.function!.arguments || {}
+    currentToolCallContent.content.params = tool.parsedArgs || tool.function?.arguments || {}
 ...
-      name: tool.name || tool.function!.name,
+      name: tool.name || tool.function?.name || 'unknown',
 ...
-        params: tool.parsedArgs || tool.function!.arguments || {},
+        params: tool.parsedArgs || tool.function?.arguments || {},

81-103: Missing error handling for async getLLMTools() call.

Line 82 awaits useMcpServer().getLLMTools() without a try-catch block. If the MCP server is unavailable, this will throw an unhandled exception that aborts request preparation.

 const onBeforeRequest = async (requestParams: any) => {
-  const tools = await useMcpServer().getLLMTools()
+  let tools
+  try {
+    tools = await useMcpServer().getLLMTools()
+  } catch (error) {
+    console.warn('Failed to load MCP tools:', error)
+    tools = []
+  }
   const { model, baseUrl, config, capabilities } = getSelectedModelInfo()
packages/plugins/robot/src/composables/modes/useAgentMode.ts (2)

139-141: Non-null assertion on pageSchema may cause runtime error.

Line 140 uses pageSchema! assuming it's always defined during streaming. However, if onStreamData is called before onMessageSent (which initializes pageSchema at line 89), this will throw.

 const onStreamData = (data: object, content: string | object, _messages: any[]) => {
+  if (!pageSchema) {
+    console.warn('pageSchema not initialized, skipping stream update')
+    return
+  }
-  updatePageSchema(content, pageSchema!)
+  updatePageSchema(content, pageSchema)
 }

239-246: Awaiting throttled function may return undefined.

updatePageSchema is created using useThrottleFn which typically doesn't preserve the return value. The await at line 240 expects a result with .schema property, but the throttled wrapper may return void/undefined.

Verify whether updatePageSchema returns a Promise with the expected shape or consider using the non-throttled version for final updates:

#!/bin/bash
# Check the implementation of updatePageSchema in pageUpdater.ts
ast-grep --pattern $'export const updatePageSchema = $_'
rg -n "useThrottleFn" --type=ts -A 3 packages/plugins/robot/src/composables/core/pageUpdater.ts
packages/plugins/robot/src/Main.vue (1)

172-177: Potential null reference when aborting.

The non-null assertion on line 175 (messages.value.at(-1)!) could throw if the check on line 174 passes but the array is modified concurrently (unlikely but possible). Using a local variable is safer.

 const handleAbortRequest = () => {
   abortRequest()
-  if (messages.value.at(-1)) {
-    messages.value.at(-1)!.aborted = true
-  }
+  const lastMessage = messages.value.at(-1)
+  if (lastMessage) {
+    lastMessage.aborted = true
+  }
 }
packages/plugins/robot/src/components/chat/RobotChat.vue (1)

128-170: Add bounds checks to prevent runtime errors.

The retry path (Line 131) and the updateAttachment callback (Lines 158, 163) access selectedAttachments.value[0] without verifying the array has elements. If the user removes the attachment before retry completes or before the upload callback fires, this will throw a runtime error.

Apply this diff:

 const handleSingleFilesSelected = (files: File[] | null, retry = false) => {
   if (!files?.length) return
   if (retry) {
+    if (selectedAttachments.value.length === 0) {
+      useNotify({
+        type: 'error',
+        message: '附件已被移除,无法重试'
+      })
+      return
+    }
     Object.assign(selectedAttachments.value[0], {
       status: 'uploading'
     })
   } else {
     // ... existing code
   }

   // ... existing upload code

   const updateAttachment = (resourceUrl: string) => {
+    if (selectedAttachments.value.length === 0) return
+
     if (resourceUrl) {
       Object.assign(selectedAttachments.value[0], {
         status: 'success',
         url: resourceUrl
       })
     } else {
       Object.assign(selectedAttachments.value[0], {
         status: 'error'
       })
     }
   }

   emit('fileSelected', formData, updateAttachment)
 }
packages/plugins/robot/src/composables/useChat.ts (2)

110-115: Add null check for last message in error handler.

Line 113 accesses messages.at(-1).content without verifying the array has elements. If an error occurs with an empty messages array, this will throw a runtime error.

Apply this diff:

 const handleRequestError = async (error: Error, messages: ChatMessage[], messageState: MessageState) => {
   chatStatus = CHAT_STATUS.FINISHED
   delete abortControllerMap.main
-  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
 }

96-99: Add null check for lastMessage before accessing properties.

messages.at(-1) at Line 96 can return undefined if the messages array is empty. Line 99 accesses lastMessage.content without a guard, which would throw a runtime error.

Apply this diff to add a null check:

 const handleFinishRequest = async (
   finishReason: string,
   messages: ChatMessage[],
   contextMessages: ChatMessage[],
   messageState: MessageState
 ) => {
   chatStatus = CHAT_STATUS.PROCESSING
   const lastMessage = messages.at(-1)
+  if (!lastMessage) {
+    chatStatus = CHAT_STATUS.FINISHED
+    delete abortControllerMap.main
+    return
+  }
 
   delete abortControllerMap.main
   await onRequestEnd(finishReason, lastMessage.content, messages) // 本次请求结束
🧹 Nitpick comments (12)
packages/plugins/robot/src/constants/prompts/index.ts (2)

45-51: Function name doesn't reflect conditional behavior.

The function name toPascalCase implies it always converts strings to PascalCase, but it only transforms strings starting with "tiny" and returns others unchanged. Consider renaming to reflect this conditional behavior.

Apply this diff:

-const toPascalCase = (str: string): string => {
+const convertTinyComponentName = (str: string): string => {
   if (!str.toLowerCase().startsWith('tiny')) return str

Then update the two usages at lines 93 and 138.


83-83: TODO: Address the TinyNumeric component error.

The Chinese comment indicates this component causes an error and is temporarily ignored. This technical debt should be tracked and resolved.

Would you like me to open a new issue to track resolving the TinyNumeric component error?

packages/plugins/robot/src/composables/features/useMcp.ts (1)

110-124: Redundant MCP client call in getLLMTools.

getLLMTools directly calls getMcpClient()?.listTools() instead of reusing the listTools function defined on lines 112-115. This duplicates logic and could lead to inconsistencies if listTools is modified.

 const getLLMTools = async () => {
-  const mcpTools = await getMetaApi(META_SERVICE.McpService)?.getMcpClient()?.listTools()
-  llmTools = convertMCPToOpenAITools(mcpTools?.tools || [])
+  const mcpTools = await listTools()
+  llmTools = convertMCPToOpenAITools(mcpTools)
   return llmTools
 }
packages/plugins/robot/src/utils/schema.utils.ts (1)

180-189: Inconsistent error handling in parseAndRepairJson.

When isFinial is true, the function skips jsonrepair and directly parses content. If parsing fails, the error object is returned raw in the error field. However, when isFinial is false and jsonrepair or JSON.parse fails, the same pattern is used. Consider using serializeError for consistency with isValidJsonPatchObjectString.

 export const parseAndRepairJson = (content: string, isFinial: boolean) => {
   try {
     let repairedContent = content
     if (!isFinial) {
       repairedContent = jsonrepair(content)
     }
     return { data: JSON.parse(repairedContent), isError: false }
   } catch (error) {
-    return { isError: true, error }
+    return { isError: true, error: serializeError(error) }
   }
 }
packages/plugins/robot/src/composables/core/useConfig.ts (1)

171-178: Potential re-entrancy during migration in saveRobotSettingState.

When loadRobotSettingState triggers migration (line 161), it recursively calls saveRobotSettingState. Then on line 175, saveRobotSettingState calls loadRobotSettingState again. While this doesn't cause infinite recursion (migration only happens once), the nested read-write pattern is confusing.

Consider extracting the localStorage write to a private helper to make the flow clearer:

+const writeToStorage = (state: RobotSettings) => {
+  localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(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 }
-  localStorage.setItem(SETTING_STORAGE_KEY, JSON.stringify(newState))
+  writeToStorage(newState)
 }
packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1)

282-290: Type assertion as any bypasses validation.

The addCustomService(serviceData as any) on line 288 bypasses TypeScript's type checking. Consider properly typing the ServiceEditDialog output to match the expected Omit<ModelService, 'id' | 'isBuiltIn'> signature.

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

25-32: Consider adding explicit return type for API methods.

The chatCompletions and agentChat methods return whatever getMetaApi(META_SERVICE.Http).post() returns, but the type is implicit. For better type safety and API documentation, consider adding explicit return types.

-  chatCompletions: (body: LLMRequestBody, options: RequestOptions = {}) => {
+  chatCompletions: (body: LLMRequestBody, options: RequestOptions = {}): Promise<any> => {
     return getMetaApi(META_SERVICE.Http).post(options?.url || '/app-center/api/chat/completions', body, {
packages/plugins/robot/src/composables/modes/useMode.ts (1)

59-80: Consider using a JavaScript Proxy for cleaner delegation.

The manual delegation of all methods works but requires maintenance when ModeHooks interface changes. A Proxy would auto-forward all method calls.

export default function useMode(): ModeHooks {
  return new Proxy({} as ModeHooks, {
    get(_target, prop) {
      const mode = getCurrentMode()
      const value = mode[prop as keyof ModeHooks]
      return typeof value === 'function' ? value.bind(mode) : value
    }
  })
}
packages/plugins/robot/src/Main.vue (1)

123-144: Consider extracting prompt items to a separate configuration.

The promptItems array is hardcoded in the component. For better maintainability and potential i18n support, consider extracting this to a configuration file or composable.

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

225-236: Clarify the conversation reuse logic.

The condition at Line 229 (usedConversationId === newConversationId) appears to check if a conversation was reused rather than created. However, the logic is unclear:

  1. createConversation typically returns a new ID
  2. If IDs match, why would we need to update metadata since it was just passed to createConversation?

Consider adding a comment explaining when this equality condition occurs, or refactor to make the intent explicit (e.g., separate reuseConversation vs createNewConversation paths).


42-44: Consider instance isolation for module-level state.

chatStatus and abortControllerMap are module-level variables shared across all consumers. If multiple chat instances are used simultaneously (e.g., different panels or test scenarios), they will interfere with each other.

If this is intentionally a singleton, consider adding a comment documenting this constraint. Otherwise, move these into the returned object or use a factory pattern.

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

111-111: Add type annotation for selectedAttachments.

The selectedAttachments ref is untyped, which reduces type safety and IDE support. Consider adding a proper type.

+interface Attachment {
+  size?: number
+  rawFile?: File
+  status?: 'uploading' | 'success' | 'error'
+  url?: string
+}
+
-const selectedAttachments = ref([])
+const selectedAttachments = ref<Attachment[]>([])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c329b0 and 53501ed.

📒 Files selected for processing (23)
  • packages/plugins/robot/src/Main.vue (2 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/header-extension/History.vue (1 hunks)
  • packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1 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/composables/core/pageUpdater.ts (1 hunks)
  • packages/plugins/robot/src/composables/core/useConfig.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/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/prompts/data/examples.json (1 hunks)
  • packages/plugins/robot/src/constants/prompts/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/utils/schema.utils.ts (1 hunks)
  • tsconfig.app.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/plugins/robot/src/composables/core/pageUpdater.ts
  • tsconfig.app.json
  • packages/plugins/robot/src/services/agentServices.ts
  • packages/plugins/robot/src/services/aiClient.ts
  • packages/plugins/robot/src/components/header-extension/History.vue
  • packages/plugins/robot/src/components/renderers/AgentRenderer.vue
  • packages/plugins/robot/src/components/chat/FooterButton.vue
  • packages/plugins/robot/src/components/renderers/ImgRenderer.vue
  • packages/plugins/robot/src/composables/core/useMessageStream.ts
🧰 Additional context used
🧠 Learnings (9)
📚 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
  • packages/plugins/robot/src/composables/modes/useChatMode.ts
  • 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
📚 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
  • packages/plugins/robot/src/components/chat/RobotChat.vue
📚 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/modes/useChatMode.ts
📚 Learning: 2025-01-14T08:44:09.485Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/canvas-function/controller.ts:1-7
Timestamp: 2025-01-14T08:44:09.485Z
Learning: Type safety improvements for the controller in `packages/canvas/render/src/canvas-function/controller.ts` should be deferred until the data structure is finalized.

Applied to files:

  • packages/plugins/robot/src/composables/modes/useChatMode.ts
📚 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/plugins/robot/src/composables/modes/useChatMode.ts
  • packages/plugins/robot/src/composables/modes/useAgentMode.ts
📚 Learning: 2025-01-14T07:11:58.019Z
Learnt from: rhlin
Repo: opentiny/tiny-engine PR: 1011
File: packages/canvas/render/src/page-block-function/accessor-map.ts:13-13
Timestamp: 2025-01-14T07:11:58.019Z
Learning: The user prefers to keep the `Function` type in `packages/canvas/render/src/page-block-function/accessor-map.ts` for now, despite static analysis warnings.

Applied to files:

  • packages/plugins/robot/src/composables/modes/useChatMode.ts
📚 Learning: 2024-09-25T11:18:00.771Z
Learnt from: chilingling
Repo: opentiny/tiny-engine PR: 817
File: packages/vue-generator/src/plugins/appendElePlusStylePlugin.js:46-50
Timestamp: 2024-09-25T11:18:00.771Z
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/modes/useChatMode.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
🧬 Code graph analysis (7)
packages/plugins/robot/src/composables/modes/useMode.ts (4)
packages/plugins/robot/src/types/mode.types.ts (1)
  • ModeHooks (25-82)
packages/plugins/robot/src/composables/modes/useAgentMode.ts (1)
  • useAgentMode (59-279)
packages/plugins/robot/src/composables/modes/useChatMode.ts (1)
  • useChatMode (53-179)
packages/plugins/robot/src/utils/meta.utils.ts (1)
  • getRobotServiceOptions (4-6)
packages/plugins/robot/src/constants/prompts/index.ts (1)
packages/canvas/render/src/material-function/material-getter.ts (1)
  • getComponent (116-118)
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/useChat.ts (7)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
  • useMode (59-80)
packages/plugins/robot/src/composables/core/useMessageStream.ts (1)
  • createStreamDataHandler (76-106)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
  • ChatRequestData (35-40)
packages/plugins/robot/src/services/api.ts (1)
  • apiService (126-131)
packages/plugins/robot/src/composables/core/useConversation.ts (1)
  • useConversationAdapter (22-108)
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/services/api.ts (1)
packages/plugins/robot/src/types/chat.types.ts (2)
  • LLMRequestBody (51-57)
  • RequestOptions (5-10)
packages/plugins/robot/src/composables/features/useMcp.ts (2)
packages/plugins/robot/src/types/chat.types.ts (1)
  • RequestTool (12-31)
packages/plugins/robot/src/types/mcp.types.ts (1)
  • McpTool (9-25)
packages/plugins/robot/src/composables/core/useConfig.ts (4)
packages/plugins/robot/src/types/setting.types.ts (4)
  • RobotSettings (52-59)
  • ModelService (30-39)
  • SelectedModelInfo (61-76)
  • ModelConfig (16-25)
packages/plugins/robot/src/utils/meta.utils.ts (1)
  • getRobotServiceOptions (4-6)
packages/plugins/robot/src/constants/model-config.ts (1)
  • DEFAULT_LLM_MODELS (11-94)
packages/plugins/robot/src/services/api.ts (1)
  • apiService (126-131)
⏰ 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 (25)
packages/plugins/robot/src/constants/prompts/index.ts (1)

167-167: Ensure imageAssets items have required properties.

The code assumes each item in imageAssets has describe and url properties. If these are missing, the markdown output will contain undefined values. Consider adding defensive checks:

-    .replace('{{IMAGE_ASSETS}}', imageAssets.map((item) => `- ![${item.describe}](${item.url})`).join('\n'))
+    .replace('{{IMAGE_ASSETS}}', imageAssets
+      .filter((item) => item?.describe && item?.url)
+      .map((item) => `- ![${item.describe}](${item.url})`)
+      .join('\n'))
packages/plugins/robot/src/composables/features/useMcp.ts (1)

36-51: LGTM!

The convertMCPToOpenAITools function correctly transforms MCP tools to OpenAI-compatible format with proper null handling via optional chaining.

packages/plugins/robot/src/utils/schema.utils.ts (2)

87-122: LGTM with a note on non-standard operation.

The validation logic is thorough. Note that _get on line 88 is not a standard RFC 6902 operation but is used by some libraries like fast-json-patch for internal purposes. This appears intentional for compatibility.


151-155: LGTM!

The regex extraction correctly handles markdown code blocks with optional json or schema language identifiers, with a sensible fallback to the raw content.

packages/plugins/robot/src/composables/core/useConfig.ts (3)

362-371: LGTM!

The updateService function now correctly preserves the existing API key when apiKey is not included in the updates object, addressing the previous review concern.


350-360: LGTM!

The addCustomService function correctly encrypts the API key before storing and generates a unique ID. The Date.now() approach is acceptable for client-side IDs in this context.


287-295: LGTM!

The init function properly handles both fresh initialization and merging existing cached services with built-in services, preserving user-configured API keys.

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

161-221: LGTM!

The fetch adapter correctly uses config.data from the axios config rather than a closed-over value, addressing the previous review concern about axios interceptors being bypassed.


71-115: LGTM!

The error handling now uses a status code map for cleaner HTTP error classification, as suggested in a previous review. The keyword-based fallback for network/timeout errors is a reasonable approach given browser variations.


11-23: LGTM!

The minimal interface definitions for AxiosRequestConfig and AxiosInstance appropriately avoid a hard dependency on axios types while providing sufficient type safety for the provider's needs.

packages/plugins/robot/src/services/api.ts (2)

57-74: LGTM! Well-defined resource types.

The Resource, ResourceGroup, and ResourceGroupList types are properly defined with appropriate fields for the resource management API.


121-124: Verify API key encryption endpoint security.

The encryptKey function sends the raw API key to the server for encryption via the HTTP service. Confirm that:

  • The endpoint enforces HTTPS to prevent key exposure during transit
  • Proper authentication/authorization is implemented to restrict access to this endpoint
  • The HTTP client is configured with secure defaults (certificate validation, etc.)
packages/plugins/robot/src/composables/modes/useMode.ts (1)

24-27: LGTM! Clean registry pattern for mode management.

The mode registry pattern allows for easy extension of new modes while keeping the mapping centralized and type-safe.

packages/plugins/robot/src/composables/modes/useChatMode.ts (2)

113-123: LGTM! Error handling for request end properly implemented.

The onRequestEnd handler correctly uses optional chaining for extraData?.error and properly handles abort/error scenarios with loading removal.


93-100: LGTM! Guard for extraBody assignment properly implemented.

The code now properly checks if extraBody is truthy before calling Object.assign, preventing potential TypeError when the value is null.

packages/plugins/robot/src/composables/modes/useAgentMode.ts (2)

123-130: LGTM! Guard for extraBody assignment properly implemented.

The code correctly checks if extraBody is truthy before calling Object.assign, addressing the previous review concern about passing null to Object.assign.


201-218: JSON fix mechanism is well-designed.

The JSON repair workflow properly creates a new AbortController, uses a beforeRequest hook to configure the fix request, and handles both success and failure cases appropriately. The per-request URL approach via the apiUrl option avoids the global config mutation issue from the previous review.

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

79-101: LGTM! Clean composition API setup.

The script setup block properly imports dependencies and defines props with appropriate defaults. The migration from Options API to Composition API is well-executed.


199-215: Well-implemented API key validation with user feedback.

The checkApiKey function properly validates provider settings and provides clear user feedback via notification, followed by opening the settings panel. This is a good UX pattern.


249-265: LGTM! Error handling for file upload properly implemented.

The handleFileSelected function includes proper try-catch with user notification via useNotify, addressing the previous review concern about silent failures.


267-272: Verify necessity of setTimeout for showTeleport.

The 1-second delay before showing teleport content may cause a noticeable UI delay. Consider whether this delay is necessary or if a smaller value or nextTick would suffice.

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

199-214: LGTM!

The message sending flow correctly:

  • Awaits nextTick() before proceeding (addressing the previous race condition)
  • Sets up abort controller before starting
  • Adds loading indicator
  • Handles errors by removing loading and calling error handler
  • Auto-sets title after completion

216-223: LGTM!

The abort flow properly cleans up all abort controllers and uses optional chaining for safe access to the last message content.

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

264-266: LGTM!

The abort handler correctly delegates to the parent component via emit, following good component composition patterns.


268-274: LGTM!

The prompt click handler properly delegates to either a custom handler or the default send mechanism. The undefined description case is safely handled by handleSendMessage's validation.

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: 0

♻️ Duplicate comments (2)
packages/plugins/robot/src/components/chat/RobotChat.vue (1)

128-170: Add bounds checks in retry path and upload callback.

Both the retry path (line 131) and the updateAttachment callback (lines 158, 163) access selectedAttachments.value[0] without verifying the array has elements. If a user removes the attachment before retry completes or before the upload finishes, this will cause a runtime error.

Apply this diff to add bounds checks:

 const handleSingleFilesSelected = (files: File[] | null, retry = false) => {
   if (!files?.length) return
   if (retry) {
+    if (selectedAttachments.value.length === 0) {
+      useNotify({
+        type: 'error',
+        message: '附件已被移除,无法重试'
+      })
+      return
+    }
     Object.assign(selectedAttachments.value[0], {
       status: 'uploading'
     })
   } else {
     if (files.length > 1) {
       useNotify({
         type: 'error',
         message: '当前仅支持上传一张图片'
       })
       return
     }

     // 将选中的文件转换为 Attachment 格式并添加到附件列表
     const newAttachments = Array.from(files).map((file) => ({
       size: file.size,
       rawFile: file
     }))
     selectedAttachments.value.push(...newAttachments)
   }

   // 开始上传
   const formData = new FormData()
   const fileData = files[0]
   formData.append('file', fileData)

   const updateAttachment = (resourceUrl: string) => {
+    if (selectedAttachments.value.length === 0) return
+    
     if (resourceUrl) {
       Object.assign(selectedAttachments.value[0], {
         status: 'success',
         url: resourceUrl
       })
     } else {
       Object.assign(selectedAttachments.value[0], {
         status: 'error'
       })
     }
   }

   emit('fileSelected', formData, updateAttachment)
 }

Based on learnings, use useNotify for error messages rather than console.

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

3-11: Make back/close SVG icons keyboard-accessible and screen-reader friendly.

These icons are clickable but not focusable via keyboard and lack ARIA metadata, which blocks keyboard-only and screen-reader users from closing the panel.

Consider updating them like this:

-      <div class="header-left">
-        <svg-icon v-if="!props.fullscreen" name="back" class="back-icon" @click="handleBack"></svg-icon>
-      </div>
+      <div class="header-left">
+        <svg-icon
+          v-if="!props.fullscreen"
+          name="back"
+          class="back-icon"
+          tabindex="0"
+          role="button"
+          aria-label="返回"
+          @click="handleBack"
+          @keydown.enter="handleBack"
+          @keydown.space.prevent="handleBack"
+        ></svg-icon>
+      </div>
@@
-      <div class="header-right">
-        <svg-icon v-if="props.fullscreen" name="cross" class="close-icon" @click="handleBack"></svg-icon>
-      </div>
+      <div class="header-right">
+        <svg-icon
+          v-if="props.fullscreen"
+          name="cross"
+          class="close-icon"
+          tabindex="0"
+          role="button"
+          aria-label="关闭"
+          @click="handleBack"
+          @keydown.enter="handleBack"
+          @keydown.space.prevent="handleBack"
+        ></svg-icon>
+      </div>
🧹 Nitpick comments (1)
packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1)

185-187: Centralize serviceId::modelName parsing (and nice fix on quick model clearing).

The getModelValue / split('::') pattern is used in several places (default model, quick model, and selectedDefaultModelInfo). The new handleCompactModelChange correctly normalizes cleared selections to empty strings, which avoids undefined leaking into persisted state—nice improvement.

To reduce duplication and harden parsing (including the default model case), you could introduce a small helper:

 const getModelValue = (serviceId: string, modelName: string) => {
   return serviceId && modelName ? `${serviceId}::${modelName}` : ''
 }
+
+const parseModelValue = (value?: string) => {
+  const [serviceId = '', modelName = ''] = (value || '').split('::')
+  return { serviceId, modelName }
+}
@@
-const selectedDefaultModelInfo = computed(() => {
-  const [serviceId] = state.modelSelection.defaultModel.split('::')
+const selectedDefaultModelInfo = computed(() => {
+  const { serviceId } = parseModelValue(state.modelSelection.defaultModel)
   const service = getServiceById(serviceId)
   if (!service) return null
@@
 const handleModelChange = () => {
-  const [defaultServiceId, defaultModelName] = state.modelSelection.defaultModel.split('::')
+  const { serviceId: defaultServiceId, modelName: defaultModelName } = parseModelValue(
+    state.modelSelection.defaultModel
+  )
+  if (!defaultServiceId || !defaultModelName) {
+    return
+  }
@@
 const handleCompactModelChange = () => {
-  const [quickServiceId = '', quickModelName = ''] = (state.modelSelection.quickModel || '').split('::')
+  const { serviceId: quickServiceId, modelName: quickModelName } = parseModelValue(
+    state.modelSelection.quickModel
+  )

This keeps encoding/decoding logic in one place and makes behavior consistent if the stored value is ever empty or malformed.

Also applies to: 199-206, 217-226, 233-266

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53501ed and eaf795b.

📒 Files selected for processing (2)
  • packages/plugins/robot/src/components/chat/RobotChat.vue (1 hunks)
  • packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 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/components/chat/RobotChat.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 (4)
packages/plugins/robot/src/components/chat/RobotChat.vue (4)

1-66: Template structure is well-organized.

The conditional rendering logic correctly shows welcome/prompts when no non-system messages exist, and the file upload constraint (selectedAttachments.length < 1) properly enforces the single-attachment limitation.


209-262: Message sending logic is well-implemented.

The beforeSubmit async handling is correct, the file attachment flow properly filters by status === 'success' (matching the upload callback), and state cleanup is properly sequenced.


264-266: Abort handling is correctly delegated.

Emitting the abort event to the parent is the right approach, avoiding direct manipulation of the messages array and the bounds-check issues flagged in previous reviews.


277-475: Styles are well-structured.

Good use of container queries for responsive design, CSS custom properties for theming, and scoped styles to avoid conflicts.

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 (1)
packages/plugins/robot/src/composables/useChat.ts (1)

89-114: Add null guards for lastMessage in finish/error handlers.

Both handleFinishRequest and handleRequestError assume messages.at(-1) is always present. If the array is empty in any edge case, lastMessage.content will throw a runtime error.

You can guard and also relax the error type to match actual usage:

 const handleFinishRequest = async (
   finishReason: string,
   messages: ChatMessage[],
   contextMessages: ChatMessage[],
   messageState: MessageState
 ) => {
-  const lastMessage = messages.at(-1)
-
-  delete abortControllerMap.main
-  await onRequestEnd(finishReason, lastMessage.content, messages) // 本次请求结束
+  const lastMessage = messages.at(-1)
+
+  delete abortControllerMap.main
+  await onRequestEnd(finishReason, lastMessage?.content ?? '', messages) // 本次请求结束
 
   if (finishReason === 'tool_calls' && lastMessage.tool_calls?.length) {
     await handleToolCall(lastMessage.tool_calls, messages, contextMessages) // eslint-disable-line
   }
@@
-const handleRequestError = async (error: Error, messages: ChatMessage[], messageState: MessageState) => {
+const handleRequestError = async (error: unknown, messages: ChatMessage[], messageState: MessageState) => {
   chatStatus = CHAT_STATUS.FINISHED
   delete abortControllerMap.main
-  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
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eaf795b and a0b1f11.

📒 Files selected for processing (3)
  • packages/plugins/robot/src/composables/core/useConversation.ts (1 hunks)
  • packages/plugins/robot/src/composables/useChat.ts (1 hunks)
  • packages/plugins/robot/src/utils/schema.utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/plugins/robot/src/utils/schema.utils.ts
🧰 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: 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 (1)
packages/plugins/robot/src/composables/useChat.ts (8)
packages/plugins/robot/src/composables/modes/useMode.ts (1)
  • useMode (59-80)
packages/plugins/robot/src/composables/core/useMessageStream.ts (1)
  • createStreamDataHandler (76-106)
packages/plugins/robot/src/services/OpenAICompatibleProvider.ts (1)
  • ChatRequestData (35-40)
packages/plugins/robot/src/services/api.ts (1)
  • apiService (126-131)
packages/plugins/robot/src/composables/core/useConversation.ts (1)
  • useConversationAdapter (26-118)
packages/plugins/robot/src/composables/features/useToolCalls.ts (1)
  • createToolCallHandler (83-132)
packages/plugins/robot/src/composables/core/useConfig.ts (1)
  • robotSettingState (443-468)
packages/plugins/robot/src/utils/chat.utils.ts (1)
  • removeLoading (88-97)
⏰ 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 (6)
packages/plugins/robot/src/composables/core/useConversation.ts (1)

26-50: Adapter wiring looks clean and well‑factored.

The useConversationAdapter nicely decouples tiny-robot-kit events from business hooks and keeps the integration surface tight (stream, finish, message-processed, status). No issues spotted here.

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

191-214: sendUserMessage pipeline looks robust after awaiting nextTick.

Awaiting nextTick before adding the loading assistant message and sending ensures correct ordering and avoids the earlier race where send() could run before the loading message was appended. Abort controller setup and loading insertion are also cleanly separated.


226-233: Confirm onRequestEnd('aborted', ...) is not invoked twice for the same abort.

abortRequest manually calls onRequestEnd('aborted', ...), while the underlying client/adapter is also likely to trigger onFinish with finishReason === 'aborted', which then calls handleFinishRequestonRequestEnd('aborted', ...) again.

If the mode’s onRequestEnd has side effects (logging, metrics, UI transitions), double invocation might be undesirable. Consider either:

  • Treating the manual call in abortRequest as the single source of truth and skipping onRequestEnd on adapter finish when finishReason === 'aborted', or
  • Removing the explicit onRequestEnd here and letting the adapter finish path handle it.

Please verify which behavior you want and adjust accordingly.


235-246: changeChatMode logic is clear and aligns with metadata‑driven modes.

The flow of creating/rehydrating a conversation, updating metadata when reusing the same conversation, and then syncing state (updateChatModeState) and provider config (updateConfig({ apiUrl: getApiUrl() })) is coherent and keeps mode/config in sync.


167-183: Avoid non‑null assertions on conversationState.currentId in lifecycle wrappers.

The conversationState.currentId! assertions suggest the value can be undefined, yet the code passes it directly to onConversationEnd without guards. If no active conversation exists (first load or corrupted state), this becomes undefined and may cause unexpected behavior.

Add simple guards to call onConversationEnd only when an ID exists:

  • In createConversation: Check currentConversationId before calling onConversationEnd
  • In switchConversation: Guard onConversationEnd(conversationState.currentId) with an if check

The proposed diff removes non-null assertions and wraps calls safely, preserving behavior when valid IDs exist.


35-43: statusManager for useConversationAdapter may not reliably suppress duplicate onFinish calls.

onFinish in the adapter expects statusManager.isProcessing() to be false for the first finish and true thereafter. Reusing chatStatus (PROCESSING/STREAMING/FINISHED) for this flag and resetting chatStatus = CHAT_STATUS.FINISHED inside onMessageProcessed means a second finish event will again see isProcessing() as false, re-running onFinishRequest and onMessageProcessed.

Introduce a dedicated hasHandledFinish flag decoupled from chatStatus, reset only in sendUserMessage():

+let hasHandledFinish = false
 
 statusManager: {
-  isProcessing: () => chatStatus === CHAT_STATUS.PROCESSING,
-  setProcessing: () => {
-    chatStatus = CHAT_STATUS.PROCESSING
-  }
+  isProcessing: () => hasHandledFinish,
+  setProcessing: () => {
+    hasHandledFinish = true
+  }
 }
 
 const sendUserMessage = async () => {
+  hasHandledFinish = false
   onMessageSent()

This keeps chatStatus for UI/streaming state while using hasHandledFinish purely to gate onFinish (lines 35-43, 138-143).

@chilingling chilingling merged commit a214cba into opentiny:develop Dec 5, 2025
2 checks passed
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.

4 participants