feat: add include_domains search filter and UI display

This commit is contained in:
JOJO 2026-03-07 17:50:35 +08:00
parent 877bcc2fad
commit c067df4e1b
9 changed files with 78 additions and 8 deletions

View File

@ -1647,6 +1647,13 @@ class MainTerminal:
"country": { "country": {
"type": "string", "type": "string",
"description": "国家过滤,仅 topic=general 可用,使用英文小写国名" "description": "国家过滤,仅 topic=general 可用,使用英文小写国名"
},
"include_domains": {
"type": "array",
"description": "仅包含这些域名可选最多300个",
"items": {
"type": "string"
}
} }
}), }),
"required": ["query"] "required": ["query"]
@ -2209,7 +2216,8 @@ class MainTerminal:
days=arguments.get("days"), days=arguments.get("days"),
start_date=arguments.get("start_date"), start_date=arguments.get("start_date"),
end_date=arguments.get("end_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"]: if search_response["success"]:

View File

@ -424,6 +424,9 @@ class WebTerminal(MainTerminal):
filters.append(f"{arguments.get('start_date')}~{arguments.get('end_date')}") filters.append(f"{arguments.get('start_date')}~{arguments.get('end_date')}")
if arguments.get("country"): if arguments.get("country"):
filters.append(f"country={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) filter_text = " | ".join(filter_item for filter_item in filters if filter_item)
self.broadcast('tool_status', { self.broadcast('tool_status', {
'tool': tool_name, 'tool': tool_name,

View File

@ -2,7 +2,7 @@
import httpx import httpx
import json import json
from typing import Dict, Optional, Any from typing import Dict, Optional, Any, List
from datetime import datetime from datetime import datetime
import re import re
try: try:
@ -42,7 +42,8 @@ class SearchEngine:
days: Optional[int] = None, days: Optional[int] = None,
start_date: Optional[str] = None, start_date: Optional[str] = None,
end_date: Optional[str] = None, end_date: Optional[str] = None,
country: Optional[str] = None country: Optional[str] = None,
include_domains: Optional[List[str]] = None
) -> Dict: ) -> Dict:
""" """
执行网络搜索 执行网络搜索
@ -56,6 +57,7 @@ class SearchEngine:
start_date: 起始日期格式YYYY-MM-DD start_date: 起始日期格式YYYY-MM-DD
end_date: 结束日期格式YYYY-MM-DD end_date: 结束日期格式YYYY-MM-DD
country: 国家过滤仅topic=general可用 country: 国家过滤仅topic=general可用
include_domains: 仅包含这些域名最多300个
Returns: Returns:
搜索结果字典 搜索结果字典
@ -75,7 +77,8 @@ class SearchEngine:
days=days, days=days,
start_date=start_date, start_date=start_date,
end_date=end_date, end_date=end_date,
country=country country=country,
include_domains=include_domains
) )
if not validation["success"]: if not validation["success"]:
@ -165,7 +168,8 @@ class SearchEngine:
days: Optional[int] = None, days: Optional[int] = None,
start_date: Optional[str] = None, start_date: Optional[str] = None,
end_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]: ) -> Dict[str, Any]:
""" """
搜索并返回格式化的摘要 搜索并返回格式化的摘要
@ -185,7 +189,8 @@ class SearchEngine:
days=days, days=days,
start_date=start_date, start_date=start_date,
end_date=end_date, end_date=end_date,
country=country country=country,
include_domains=include_domains
) )
if not results["success"]: if not results["success"]:
@ -326,7 +331,8 @@ class SearchEngine:
days: Optional[int], days: Optional[int],
start_date: Optional[str], start_date: Optional[str],
end_date: Optional[str], end_date: Optional[str],
country: Optional[str] country: Optional[str],
include_domains: Optional[List[str]]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""验证并构建 Tavily 请求参数""" """验证并构建 Tavily 请求参数"""
payload: Dict[str, Any] = { payload: Dict[str, Any] = {
@ -459,6 +465,35 @@ class SearchEngine:
payload["country"] = normalized_country payload["country"] = normalized_country
filters["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 { return {
"success": True, "success": True,
"payload": payload, "payload": payload,
@ -485,6 +520,8 @@ class SearchEngine:
if "country" in filters: if "country" in filters:
parts.append(f"Country: {filters['country']}") parts.append(f"Country: {filters['country']}")
if "include_domains" in filters:
parts.append(f"Domains: {len(filters['include_domains'])}")
if not parts: if not parts:
return "" return ""

View File

@ -196,6 +196,7 @@
:get-tool-description="getToolDescription" :get-tool-description="getToolDescription"
:format-search-topic="formatSearchTopic" :format-search-topic="formatSearchTopic"
:format-search-time="formatSearchTime" :format-search-time="formatSearchTime"
:format-search-domains="formatSearchDomains"
/> />
<VirtualMonitorSurface v-show="chatDisplayMode === 'monitor'" /> <VirtualMonitorSurface v-show="chatDisplayMode === 'monitor'" />

View File

@ -55,6 +55,7 @@ import {
buildToolLabel, buildToolLabel,
formatSearchTopic, formatSearchTopic,
formatSearchTime, formatSearchTime,
formatSearchDomains,
getLanguageClass getLanguageClass
} from './utils/chatDisplay'; } from './utils/chatDisplay';
import { import {
@ -4012,6 +4013,7 @@ const appOptions = {
buildToolLabel, buildToolLabel,
formatSearchTopic, formatSearchTopic,
formatSearchTime, formatSearchTime,
formatSearchDomains,
getLanguageClass, getLanguageClass,
scrollToBottom() { scrollToBottom() {

View File

@ -60,6 +60,7 @@
:get-tool-description="getToolDescription" :get-tool-description="getToolDescription"
:format-search-topic="formatSearchTopic" :format-search-topic="formatSearchTopic"
:format-search-time="formatSearchTime" :format-search-time="formatSearchTime"
:format-search-domains="formatSearchDomains"
/> />
<div <div
v-else v-else
@ -183,9 +184,10 @@
:get-tool-animation-class="getToolAnimationClass" :get-tool-animation-class="getToolAnimationClass"
:get-tool-icon="getToolIcon" :get-tool-icon="getToolIcon"
:get-tool-status-text="getToolStatusText" :get-tool-status-text="getToolStatusText"
:get-tool-description="getToolDescription" :get-tool-description="getToolDescription"
:format-search-topic="formatSearchTopic" :format-search-topic="formatSearchTopic"
:format-search-time="formatSearchTime" :format-search-time="formatSearchTime"
:format-search-domains="formatSearchDomains"
:streaming-message="streamingMessage" :streaming-message="streamingMessage"
:register-collapse-content="registerCollapseContent" :register-collapse-content="registerCollapseContent"
:collapse-key="group.action.blockId || `${index}-tool-${group.actionIndex}`" :collapse-key="group.action.blockId || `${index}-tool-${group.actionIndex}`"
@ -320,6 +322,7 @@
:get-tool-description="getToolDescription" :get-tool-description="getToolDescription"
:format-search-topic="formatSearchTopic" :format-search-topic="formatSearchTopic"
:format-search-time="formatSearchTime" :format-search-time="formatSearchTime"
:format-search-domains="formatSearchDomains"
:streaming-message="streamingMessage" :streaming-message="streamingMessage"
:register-collapse-content="registerCollapseContent" :register-collapse-content="registerCollapseContent"
:collapse-key="action.blockId || `${index}-tool-${actionIndex}`" :collapse-key="action.blockId || `${index}-tool-${actionIndex}`"
@ -372,6 +375,7 @@ const props = defineProps<{
getToolDescription: (tool: any) => string; getToolDescription: (tool: any) => string;
formatSearchTopic: (filters: Record<string, any>) => string; formatSearchTopic: (filters: Record<string, any>) => string;
formatSearchTime: (filters: Record<string, any>) => string; formatSearchTime: (filters: Record<string, any>) => string;
formatSearchDomains: (filters: Record<string, any>) => string;
}>(); }>();
const personalization = usePersonalizationStore(); const personalization = usePersonalizationStore();

View File

@ -86,6 +86,7 @@
<div><strong>搜索内容</strong>{{ action.tool.result.query || action.tool.arguments?.query }}</div> <div><strong>搜索内容</strong>{{ action.tool.result.query || action.tool.arguments?.query }}</div>
<div><strong>主题</strong>{{ formatSearchTopic(action.tool.result.filters || {}) }}</div> <div><strong>主题</strong>{{ formatSearchTopic(action.tool.result.filters || {}) }}</div>
<div><strong>时间范围</strong>{{ formatSearchTime(action.tool.result.filters || {}) }}</div> <div><strong>时间范围</strong>{{ formatSearchTime(action.tool.result.filters || {}) }}</div>
<div><strong>限定网站</strong>{{ formatSearchDomains(action.tool.result.filters || {}) }}</div>
<div><strong>结果数量</strong>{{ action.tool.result.total_results }}</div> <div><strong>结果数量</strong>{{ action.tool.result.total_results }}</div>
</div> </div>
<div v-if="action.tool.result.results && action.tool.result.results.length" class="search-result-list"> <div v-if="action.tool.result.results && action.tool.result.results.length" class="search-result-list">
@ -139,6 +140,7 @@ const props = defineProps<{
getToolDescription: (tool: any) => string; getToolDescription: (tool: any) => string;
formatSearchTopic: (filters: Record<string, any>) => string; formatSearchTopic: (filters: Record<string, any>) => string;
formatSearchTime: (filters: Record<string, any>) => string; formatSearchTime: (filters: Record<string, any>) => string;
formatSearchDomains: (filters: Record<string, any>) => string;
}>(); }>();
const VISIBLE_LIMIT = 6; const VISIBLE_LIMIT = 6;

View File

@ -30,6 +30,7 @@
<div><strong>搜索内容</strong>{{ action.tool.result.query || action.tool.arguments.query }}</div> <div><strong>搜索内容</strong>{{ action.tool.result.query || action.tool.arguments.query }}</div>
<div><strong>主题</strong>{{ formatSearchTopic(action.tool.result.filters || {}) }}</div> <div><strong>主题</strong>{{ formatSearchTopic(action.tool.result.filters || {}) }}</div>
<div><strong>时间范围</strong>{{ formatSearchTime(action.tool.result.filters || {}) }}</div> <div><strong>时间范围</strong>{{ formatSearchTime(action.tool.result.filters || {}) }}</div>
<div><strong>限定网站</strong>{{ formatSearchDomains(action.tool.result.filters || {}) }}</div>
<div><strong>结果数量</strong>{{ action.tool.result.total_results }}</div> <div><strong>结果数量</strong>{{ action.tool.result.total_results }}</div>
</div> </div>
<div v-if="action.tool.result.results && action.tool.result.results.length" class="search-result-list"> <div v-if="action.tool.result.results && action.tool.result.results.length" class="search-result-list">
@ -77,6 +78,7 @@ defineProps<{
getToolDescription: (tool: any) => string; getToolDescription: (tool: any) => string;
formatSearchTopic: (filters: Record<string, any>) => string; formatSearchTopic: (filters: Record<string, any>) => string;
formatSearchTime: (filters: Record<string, any>) => string; formatSearchTime: (filters: Record<string, any>) => string;
formatSearchDomains: (filters: Record<string, any>) => string;
streamingMessage: boolean; streamingMessage: boolean;
registerCollapseContent?: (key: string, el: Element | null) => void; registerCollapseContent?: (key: string, el: Element | null) => void;
collapseKey?: string; collapseKey?: string;

View File

@ -284,6 +284,17 @@ export function formatSearchTime(filters: ToolPayload): string {
return '未限定时间'; 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 { export function getLanguageClass(path: string): string {
if (!path) { if (!path) {
return 'language-plain'; return 'language-plain';