diff --git a/core/main_terminal.py b/core/main_terminal.py index 787ac7c..92115e6 100644 --- a/core/main_terminal.py +++ b/core/main_terminal.py @@ -1647,6 +1647,13 @@ class MainTerminal: "country": { "type": "string", "description": "国家过滤,仅 topic=general 可用,使用英文小写国名" + }, + "include_domains": { + "type": "array", + "description": "仅包含这些域名(可选,最多300个)", + "items": { + "type": "string" + } } }), "required": ["query"] @@ -2209,7 +2216,8 @@ class MainTerminal: days=arguments.get("days"), start_date=arguments.get("start_date"), end_date=arguments.get("end_date"), - country=arguments.get("country") + country=arguments.get("country"), + include_domains=arguments.get("include_domains") ) if search_response["success"]: diff --git a/core/web_terminal.py b/core/web_terminal.py index d2d1b04..76daf3a 100644 --- a/core/web_terminal.py +++ b/core/web_terminal.py @@ -424,6 +424,9 @@ class WebTerminal(MainTerminal): filters.append(f"{arguments.get('start_date')}~{arguments.get('end_date')}") if arguments.get("country"): filters.append(f"country={arguments.get('country')}") + include_domains = arguments.get("include_domains") + if isinstance(include_domains, list) and include_domains: + filters.append(f"include_domains={len(include_domains)}") filter_text = " | ".join(filter_item for filter_item in filters if filter_item) self.broadcast('tool_status', { 'tool': tool_name, diff --git a/modules/search_engine.py b/modules/search_engine.py index 85800f2..356ed9a 100644 --- a/modules/search_engine.py +++ b/modules/search_engine.py @@ -2,7 +2,7 @@ import httpx import json -from typing import Dict, Optional, Any +from typing import Dict, Optional, Any, List from datetime import datetime import re try: @@ -42,7 +42,8 @@ class SearchEngine: days: Optional[int] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, - country: Optional[str] = None + country: Optional[str] = None, + include_domains: Optional[List[str]] = None ) -> Dict: """ 执行网络搜索 @@ -56,6 +57,7 @@ class SearchEngine: start_date: 起始日期,格式YYYY-MM-DD end_date: 结束日期,格式YYYY-MM-DD country: 国家过滤,仅topic=general可用 + include_domains: 仅包含这些域名(最多300个) Returns: 搜索结果字典 @@ -75,7 +77,8 @@ class SearchEngine: days=days, start_date=start_date, end_date=end_date, - country=country + country=country, + include_domains=include_domains ) if not validation["success"]: @@ -165,7 +168,8 @@ class SearchEngine: days: Optional[int] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, - country: Optional[str] = None + country: Optional[str] = None, + include_domains: Optional[List[str]] = None ) -> Dict[str, Any]: """ 搜索并返回格式化的摘要 @@ -185,7 +189,8 @@ class SearchEngine: days=days, start_date=start_date, end_date=end_date, - country=country + country=country, + include_domains=include_domains ) if not results["success"]: @@ -326,7 +331,8 @@ class SearchEngine: days: Optional[int], start_date: Optional[str], end_date: Optional[str], - country: Optional[str] + country: Optional[str], + include_domains: Optional[List[str]] ) -> Dict[str, Any]: """验证并构建 Tavily 请求参数""" payload: Dict[str, Any] = { @@ -458,6 +464,35 @@ class SearchEngine: } payload["country"] = normalized_country filters["country"] = normalized_country + + # 域名白名单 + if include_domains is not None: + if not isinstance(include_domains, list): + return { + "success": False, + "error": "include_domains 必须是字符串数组", + "results": [] + } + cleaned_domains = [] + for item in include_domains: + if not isinstance(item, str): + return { + "success": False, + "error": "include_domains 中每一项都必须是字符串", + "results": [] + } + domain = item.strip().lower() + if domain: + cleaned_domains.append(domain) + if len(cleaned_domains) > 300: + return { + "success": False, + "error": f"include_domains 最多支持300个域名,当前: {len(cleaned_domains)}", + "results": [] + } + if cleaned_domains: + payload["include_domains"] = cleaned_domains + filters["include_domains"] = cleaned_domains return { "success": True, @@ -485,6 +520,8 @@ class SearchEngine: if "country" in filters: parts.append(f"Country: {filters['country']}") + if "include_domains" in filters: + parts.append(f"Domains: {len(filters['include_domains'])}") if not parts: return "" diff --git a/static/src/App.vue b/static/src/App.vue index 4d60a66..34da366 100644 --- a/static/src/App.vue +++ b/static/src/App.vue @@ -196,6 +196,7 @@ :get-tool-description="getToolDescription" :format-search-topic="formatSearchTopic" :format-search-time="formatSearchTime" + :format-search-domains="formatSearchDomains" /> diff --git a/static/src/app.ts b/static/src/app.ts index 4b6baa2..5941ae9 100644 --- a/static/src/app.ts +++ b/static/src/app.ts @@ -55,6 +55,7 @@ import { buildToolLabel, formatSearchTopic, formatSearchTime, + formatSearchDomains, getLanguageClass } from './utils/chatDisplay'; import { @@ -4012,6 +4013,7 @@ const appOptions = { buildToolLabel, formatSearchTopic, formatSearchTime, + formatSearchDomains, getLanguageClass, scrollToBottom() { diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue index c260e09..9aa9395 100644 --- a/static/src/components/chat/ChatArea.vue +++ b/static/src/components/chat/ChatArea.vue @@ -60,6 +60,7 @@ :get-tool-description="getToolDescription" :format-search-topic="formatSearchTopic" :format-search-time="formatSearchTime" + :format-search-domains="formatSearchDomains" />
string; formatSearchTopic: (filters: Record) => string; formatSearchTime: (filters: Record) => string; + formatSearchDomains: (filters: Record) => string; }>(); const personalization = usePersonalizationStore(); diff --git a/static/src/components/chat/StackedBlocks.vue b/static/src/components/chat/StackedBlocks.vue index a309bce..79bd0c1 100644 --- a/static/src/components/chat/StackedBlocks.vue +++ b/static/src/components/chat/StackedBlocks.vue @@ -86,6 +86,7 @@
搜索内容:{{ action.tool.result.query || action.tool.arguments?.query }}
主题:{{ formatSearchTopic(action.tool.result.filters || {}) }}
时间范围:{{ formatSearchTime(action.tool.result.filters || {}) }}
+
限定网站:{{ formatSearchDomains(action.tool.result.filters || {}) }}
结果数量:{{ action.tool.result.total_results }}
@@ -139,6 +140,7 @@ const props = defineProps<{ getToolDescription: (tool: any) => string; formatSearchTopic: (filters: Record) => string; formatSearchTime: (filters: Record) => string; + formatSearchDomains: (filters: Record) => string; }>(); const VISIBLE_LIMIT = 6; diff --git a/static/src/components/chat/actions/ToolAction.vue b/static/src/components/chat/actions/ToolAction.vue index 3d8c574..991d409 100644 --- a/static/src/components/chat/actions/ToolAction.vue +++ b/static/src/components/chat/actions/ToolAction.vue @@ -30,6 +30,7 @@
搜索内容:{{ action.tool.result.query || action.tool.arguments.query }}
主题:{{ formatSearchTopic(action.tool.result.filters || {}) }}
时间范围:{{ formatSearchTime(action.tool.result.filters || {}) }}
+
限定网站:{{ formatSearchDomains(action.tool.result.filters || {}) }}
结果数量:{{ action.tool.result.total_results }}
@@ -77,6 +78,7 @@ defineProps<{ getToolDescription: (tool: any) => string; formatSearchTopic: (filters: Record) => string; formatSearchTime: (filters: Record) => string; + formatSearchDomains: (filters: Record) => string; streamingMessage: boolean; registerCollapseContent?: (key: string, el: Element | null) => void; collapseKey?: string; diff --git a/static/src/utils/chatDisplay.ts b/static/src/utils/chatDisplay.ts index e417add..0073142 100644 --- a/static/src/utils/chatDisplay.ts +++ b/static/src/utils/chatDisplay.ts @@ -284,6 +284,17 @@ export function formatSearchTime(filters: ToolPayload): string { return '未限定时间'; } +export function formatSearchDomains(filters: ToolPayload): string { + const domains = filters?.include_domains; + if (!Array.isArray(domains) || domains.length === 0) { + return '未限定网站'; + } + const normalized = domains + .map(item => String(item || '').trim()) + .filter(Boolean); + return normalized.length ? normalized.join(', ') : '未限定网站'; +} + export function getLanguageClass(path: string): string { if (!path) { return 'language-plain';