Skip to content

Conversation

@Soulter
Copy link
Member

@Soulter Soulter commented Jan 16, 2026

Modifications / 改动点

image
  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

实现端到端的网页搜索引用功能,在聊天界面进行可视化展示,并在后端完成引用信息的抽取与存储。

New Features:

  • 在聊天界面中,通过内联小标签(chips)和独立侧边栏展示机器人消息的网页搜索引用来源。
  • 支持在消息上点击引用操作,打开侧边栏,列出被引用的网页搜索结果,包括网站图标(favicon)、标题、URL 和内容摘要。
  • 在 markdown 消息中渲染自定义 <ref> 标签,使用专门的引用节点组件,并与网页搜索结果的元数据关联。

Enhancements:

  • 在服务端抽取并持久化 web_search_tavily 引用元数据,包括将被引用的索引映射到相应的搜索结果详情及其网站图标(favicons)。
  • 增强 Tavily 网页搜索集成能力,使其返回包含索引结果、网站图标以及更高默认 max_results 值的结构化 JSON,并通过系统提示引导 LLM 插入 <ref>index</ref> 引用标记。
  • 在 shared preferences 中引入临时内存缓存,用于存储网页搜索网站图标(favicons),并支持定期自动清理。
  • 优化聊天布局样式,包括工具栏和会话侧边栏的边框样式以及悬停/过渡效果调整。
Original summary in English

Summary by Sourcery

Implement end-to-end web search citation references with UI visualization in chat and backend extraction/storage.

New Features:

  • Display web search reference sources for bot messages via inline chips and a dedicated sidebar in the chat UI.
  • Support clickable reference actions on messages to open a sidebar listing cited web results with favicons, titles, URLs, and snippets.
  • Render custom tags in markdown messages using a dedicated reference node component tied to web search results metadata.

Enhancements:

  • Extract and persist web_search_tavily citation metadata on the server side, including mapping cited indices to search result details and favicons.
  • Augment Tavily web search integration to return structured JSON with indexed results, favicons, and a higher default max_results value, and guide the LLM via system prompt to insert index citations.
  • Introduce a temporary in-memory cache in shared preferences for storing web search favicons with periodic automatic cleanup.
  • Refine chat layout styling including border and hover/transition tweaks for tool and conversation sidebars.

@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. area:webui The bug / feature is about webui(dashboard) of astrbot. feature:chatui The bug / feature is about astrbot's chatui, webchat labels Jan 16, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我发现了 3 个问题,并给出了一些高层次的反馈:

  • MessageList.vue 中的 extractWebSearchResults 方法在每次 updated 钩子时都会运行,每次都会遍历并 JSON 解析整个 messages 树;建议在每条消息上做结果缓存,或者在 messages 变更时(例如通过 watcher)做 diff,从而避免在每次渲染时都完整重新计算。
  • MessageList.vue 中,provide() 暴露的是 isDark: this.isDark,这不会对主题变更做出响应;如果 isDark 在运行时可能变化,并且需要更新 RefNode 之类的子组件,请提供一个 getter/computed,或者使用 toRef/toRefs,以保证注入的使用方能够保持同步。
  • chat.py 中新加的 _extract_web_search_refs helper 依赖 re,但在这个 diff 中没有看到相应的导入;请确认该模块中存在 import re,以避免运行时出现 NameError
给 AI 代理的提示
请根据以下代码审查意见进行修改:

## 整体评论
- `MessageList.vue` 中的 `extractWebSearchResults` 方法在每次 `updated` 钩子时都会运行,每次都会遍历并 JSON 解析整个 `messages` 树;建议在每条消息上做结果缓存,或者在 `messages` 变更时(例如通过 `watcher`)做 diff,从而避免在每次渲染时都完整重新计算。
-`MessageList.vue` 中,`provide()` 暴露的是 `isDark: this.isDark`,这不会对主题变更做出响应;如果 `isDark` 在运行时可能变化,并且需要更新 `RefNode` 之类的子组件,请提供一个 getter/computed,或者使用 `toRef`/`toRefs`,以保证注入的使用方能够保持同步。
- `chat.py` 中新加的 `_extract_web_search_refs` helper 依赖 `re`,但在这个 diff 中没有看到相应的导入;请确认该模块中存在 `import re`,以避免运行时出现 `NameError`## 逐条评论

### 评论 1
<location> `dashboard/src/components/chat/message_list_comps/RefNode.vue:22-25` </location>
<code_context>
+    }
+})
+
+console.log('RefNode node:', props.node);
+
+// 从父组件注入的暗黑模式状态和搜索结果
</code_context>

<issue_to_address>
**suggestion:** 请从生产环境组件中移除调试用的 `console.log`。

该日志会在 ref 节点的每次渲染时输出,导致浏览器控制台被大量日志淹没。请将其移除,或用一个调试开关包裹,使其在生产环境中不会执行。

```suggestion
/*
// Debug log for development; uncomment if you need to inspect RefNode props
// console.log('RefNode node:', props.node);
*/

// 从父组件注入的暗黑模式状态和搜索结果
const isDark = inject('isDark', false)
```
</issue_to_address>

### 评论 2
<location> `dashboard/src/components/chat/MessageList.vue:378-383` </location>
<code_context>
+                        
+                        try {
+                            // 解析工具调用结果
+                            console.log('Parsing web search result:', toolCall.result);
+                            const resultData = typeof toolCall.result === 'string' 
+                                ? JSON.parse(toolCall.result) 
</code_context>

<issue_to_address>
**suggestion (performance):** 避免在消息渲染的热点路径中使用过于冗长的 `console.log`。

由于这段代码会在 `mounted` 以及每次 `updated` 时,对所有消息和 tool 调用执行,因此该日志会被非常频繁地触发,可能导致控制台被刷屏,影响页面响应速度和日志可用性。请将其移除,或用一个调试开关进行保护。

```suggestion
                        try {
                            // 解析工具调用结果
                            const resultData = typeof toolCall.result === 'string' 
                                ? JSON.parse(toolCall.result) 
                                : toolCall.result;
```
</issue_to_address>

### 评论 3
<location> `dashboard/src/components/chat/MessageList.vue:347` </location>
<code_context>
         this.addScrollListener();
         this.scrollToBottom();
         this.startElapsedTimeTimer();
+        this.extractWebSearchResults();
     },
     updated() {
</code_context>

<issue_to_address>
**suggestion (performance):** 在每次更新时重新计算网页搜索结果可能比实际需要的开销更大。

`extractWebSearchResults()` 会在每次响应式更新时运行,即使消息和工具调用并没有发生变化,也会遍历所有消息和工具调用。为避免重复工作,可以考虑只在 `messages` 发生变化时触发(例如通过对 `messages` 使用 `watch`),或者按消息 ID 缓存结果。

建议的实现方式:

```
    async mounted() {
        this.addScrollListener();
        this.scrollToBottom();
        this.startElapsedTimeTimer();
    },

```

```
    updated() {
        this.initCodeCopyButtons();
        if (this.isUserNearBottom) {
            this.scrollToBottom();
        }
    },
    watch: {
        // Recompute web search results only when messages change
        messages: {
            handler() {
                this.extractWebSearchResults();
            },
            deep: true,
            immediate: true
        }
    },
    methods: {

```
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你觉得我们的审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English

Hey - I've found 3 issues, and left some high level feedback:

  • The extractWebSearchResults method in MessageList.vue runs on every updated hook and walks/JSON-parses the entire messages tree each time; consider caching per-message results or diffing on messages changes (e.g., via a watcher) to avoid repeated full recomputation on every render.
  • In MessageList.vue, provide() exposes isDark: this.isDark, which will not react to theme changes; if isDark can change at runtime and should update child components like RefNode, provide a getter/computed or use toRef/toRefs so injected consumers stay in sync.
  • The new _extract_web_search_refs helper in chat.py relies on re but the import is not shown in this diff; please ensure import re exists in that module to avoid a runtime NameError.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `extractWebSearchResults` method in `MessageList.vue` runs on every `updated` hook and walks/JSON-parses the entire `messages` tree each time; consider caching per-message results or diffing on `messages` changes (e.g., via a watcher) to avoid repeated full recomputation on every render.
- In `MessageList.vue`, `provide()` exposes `isDark: this.isDark`, which will not react to theme changes; if `isDark` can change at runtime and should update child components like `RefNode`, provide a getter/computed or use `toRef`/`toRefs` so injected consumers stay in sync.
- The new `_extract_web_search_refs` helper in `chat.py` relies on `re` but the import is not shown in this diff; please ensure `import re` exists in that module to avoid a runtime `NameError`.

## Individual Comments

### Comment 1
<location> `dashboard/src/components/chat/message_list_comps/RefNode.vue:22-25` </location>
<code_context>
+    }
+})
+
+console.log('RefNode node:', props.node);
+
+// 从父组件注入的暗黑模式状态和搜索结果
</code_context>

<issue_to_address>
**suggestion:** Remove debug `console.log` from production component.

This will log on every render of the ref node and clutter the browser console. Please remove it or wrap it in a debug flag so it doesn’t run in production.

```suggestion
/*
// Debug log for development; uncomment if you need to inspect RefNode props
// console.log('RefNode node:', props.node);
*/

// 从父组件注入的暗黑模式状态和搜索结果
const isDark = inject('isDark', false)
```
</issue_to_address>

### Comment 2
<location> `dashboard/src/components/chat/MessageList.vue:378-383` </location>
<code_context>
+                        
+                        try {
+                            // 解析工具调用结果
+                            console.log('Parsing web search result:', toolCall.result);
+                            const resultData = typeof toolCall.result === 'string' 
+                                ? JSON.parse(toolCall.result) 
</code_context>

<issue_to_address>
**suggestion (performance):** Avoid verbose `console.log` in the hot path of message rendering.

Because this runs on `mounted` and every `updated` over all messages and tool calls, this log will be called very frequently and can flood the console, hurting responsiveness and log usability. Please remove it or guard it behind a debug flag.

```suggestion
                        try {
                            // 解析工具调用结果
                            const resultData = typeof toolCall.result === 'string' 
                                ? JSON.parse(toolCall.result) 
                                : toolCall.result;
```
</issue_to_address>

### Comment 3
<location> `dashboard/src/components/chat/MessageList.vue:347` </location>
<code_context>
         this.addScrollListener();
         this.scrollToBottom();
         this.startElapsedTimeTimer();
+        this.extractWebSearchResults();
     },
     updated() {
</code_context>

<issue_to_address>
**suggestion (performance):** Recomputing web search results on every update may be heavier than necessary.

`extractWebSearchResults()` runs on every reactive update, traversing all messages and tool calls even when they haven’t changed. To avoid this repeated work, consider triggering it only when `messages` change (e.g. via a `watch` on `messages`) or caching results by message id.

Suggested implementation:

```
    async mounted() {
        this.addScrollListener();
        this.scrollToBottom();
        this.startElapsedTimeTimer();
    },

```

```
    updated() {
        this.initCodeCopyButtons();
        if (this.isUserNearBottom) {
            this.scrollToBottom();
        }
    },
    watch: {
        // Recompute web search results only when messages change
        messages: {
            handler() {
                this.extractWebSearchResults();
            },
            deep: true,
            immediate: true
        }
    },
    methods: {

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +22 to +25
console.log('RefNode node:', props.node);
// 从父组件注入的暗黑模式状态和搜索结果
const isDark = inject('isDark', false)
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: 请从生产环境组件中移除调试用的 console.log

该日志会在 ref 节点的每次渲染时输出,导致浏览器控制台被大量日志淹没。请将其移除,或用一个调试开关包裹,使其在生产环境中不会执行。

Suggested change
console.log('RefNode node:', props.node);
// 从父组件注入的暗黑模式状态和搜索结果
const isDark = inject('isDark', false)
/*
// Debug log for development; uncomment if you need to inspect RefNode props
// console.log('RefNode node:', props.node);
*/
// 从父组件注入的暗黑模式状态和搜索结果
const isDark = inject('isDark', false)
Original comment in English

suggestion: Remove debug console.log from production component.

This will log on every render of the ref node and clutter the browser console. Please remove it or wrap it in a debug flag so it doesn’t run in production.

Suggested change
console.log('RefNode node:', props.node);
// 从父组件注入的暗黑模式状态和搜索结果
const isDark = inject('isDark', false)
/*
// Debug log for development; uncomment if you need to inspect RefNode props
// console.log('RefNode node:', props.node);
*/
// 从父组件注入的暗黑模式状态和搜索结果
const isDark = inject('isDark', false)

Comment on lines 378 to 383
try {
// 解析工具调用结果
console.log('Parsing web search result:', toolCall.result);
const resultData = typeof toolCall.result === 'string'
? JSON.parse(toolCall.result)
: toolCall.result;
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (performance): 避免在消息渲染的热点路径中使用过于冗长的 console.log

由于这段代码会在 mounted 以及每次 updated 时,对所有消息和 tool 调用执行,因此该日志会被非常频繁地触发,可能导致控制台被刷屏,影响页面响应速度和日志可用性。请将其移除,或用一个调试开关进行保护。

Suggested change
try {
// 解析工具调用结果
console.log('Parsing web search result:', toolCall.result);
const resultData = typeof toolCall.result === 'string'
? JSON.parse(toolCall.result)
: toolCall.result;
try {
// 解析工具调用结果
const resultData = typeof toolCall.result === 'string'
? JSON.parse(toolCall.result)
: toolCall.result;
Original comment in English

suggestion (performance): Avoid verbose console.log in the hot path of message rendering.

Because this runs on mounted and every updated over all messages and tool calls, this log will be called very frequently and can flood the console, hurting responsiveness and log usability. Please remove it or guard it behind a debug flag.

Suggested change
try {
// 解析工具调用结果
console.log('Parsing web search result:', toolCall.result);
const resultData = typeof toolCall.result === 'string'
? JSON.parse(toolCall.result)
: toolCall.result;
try {
// 解析工具调用结果
const resultData = typeof toolCall.result === 'string'
? JSON.parse(toolCall.result)
: toolCall.result;

this.addScrollListener();
this.scrollToBottom();
this.startElapsedTimeTimer();
this.extractWebSearchResults();
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (performance): 在每次更新时重新计算网页搜索结果可能比实际需要的开销更大。

extractWebSearchResults() 会在每次响应式更新时运行,即使消息和工具调用并没有发生变化,也会遍历所有消息和工具调用。为避免重复工作,可以考虑只在 messages 发生变化时触发(例如通过对 messages 使用 watch),或者按消息 ID 缓存结果。

建议的实现方式:

    async mounted() {
        this.addScrollListener();
        this.scrollToBottom();
        this.startElapsedTimeTimer();
    },

    updated() {
        this.initCodeCopyButtons();
        if (this.isUserNearBottom) {
            this.scrollToBottom();
        }
    },
    watch: {
        // Recompute web search results only when messages change
        messages: {
            handler() {
                this.extractWebSearchResults();
            },
            deep: true,
            immediate: true
        }
    },
    methods: {

Original comment in English

suggestion (performance): Recomputing web search results on every update may be heavier than necessary.

extractWebSearchResults() runs on every reactive update, traversing all messages and tool calls even when they haven’t changed. To avoid this repeated work, consider triggering it only when messages change (e.g. via a watch on messages) or caching results by message id.

Suggested implementation:

    async mounted() {
        this.addScrollListener();
        this.scrollToBottom();
        this.startElapsedTimeTimer();
    },

    updated() {
        this.initCodeCopyButtons();
        if (this.isUserNearBottom) {
            this.scrollToBottom();
        }
    },
    watch: {
        // Recompute web search results only when messages change
        messages: {
            handler() {
                this.extractWebSearchResults();
            },
            deep: true,
            immediate: true
        }
    },
    methods: {

@Soulter Soulter merged commit 82e2e0d into master Jan 16, 2026
5 checks passed
@Soulter Soulter deleted the feat/chatui-websearch-ref branch January 16, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:webui The bug / feature is about webui(dashboard) of astrbot. feature:chatui The bug / feature is about astrbot's chatui, webchat size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants