diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 88276bc..5283909 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -22,6 +22,8 @@ export const API = { expertsSelect: '/api/experts/select', sessionSelectModel: '/api/session/select-model', sessionSelectedModel: '/api/session/selected-model', + topicSelectModel: '/api/topic/select-model', + topicSelectedModel: '/api/topic/selected-model', } as const; const TOKEN_KEY = 'picobot-gateway-token'; diff --git a/web/src/api/experts.ts b/web/src/api/experts.ts index 246dc37..fe91c3e 100644 --- a/web/src/api/experts.ts +++ b/web/src/api/experts.ts @@ -106,3 +106,35 @@ export async function getSelectedModel( if (!resp.ok) return { provider: null, model: null }; return resp.json(); } + +/** + * 设置(或清除)话题级模型选择。后端双写:topics 行 + session store(新话题默认值)。 + * provider/model 均为 null 时清除话题级选择(恢复继承 session 级)。 + */ +export async function selectTopicModel( + sessionId: string, + topicId: string, + provider: string | null, + model: string | null, +): Promise<{ success: boolean; error?: string }> { + const resp = await authedFetch(API.topicSelectModel, { + method: 'POST', + body: { session_id: sessionId, topic_id: topicId, provider, model }, + }); + const data = await resp.json().catch(() => ({})); + if (!resp.ok || !data.success) return { success: false, error: data.error || '切换模型失败' }; + return { success: true }; +} + +/** + * 读取话题生效的用户模型选择(topic 级优先,miss 回退 session 级)。 + * 与 session 端点同语义:只反映用户选择,不解析 expert/config 默认。 + */ +export async function getSelectedTopicModel( + topicId: string, +): Promise<{ provider: string | null; model: string | null }> { + const params = new URLSearchParams({ topic_id: topicId }); + const resp = await authedFetch(`${API.topicSelectedModel}?${params}`); + if (!resp.ok) return { provider: null, model: null }; + return resp.json(); +} diff --git a/web/src/components/Chat/ChatContainer.tsx b/web/src/components/Chat/ChatContainer.tsx index fbb9d4b..b6e5c91 100644 --- a/web/src/components/Chat/ChatContainer.tsx +++ b/web/src/components/Chat/ChatContainer.tsx @@ -49,6 +49,11 @@ export function ChatContainer({ name: string; description: string; } | null>(null); + // 当前生效模型(供 Task 卡片差异显示:子代理模型 ≠ 主代理模型时提示) + const [effectiveModel, setEffectiveModel] = useState<{ + provider: string; + model: string; + } | null>(null); const selectors = (