Skip to content

Commit 3cec67c

Browse files
nieaoclaude
andcommitted
feat(ai-settings): BottomAIBar 显示当前活跃 LLM 徽章 — 让用户看到设置生效
用户反馈: AI 设置面板里有 11 个 provider, 但画布看不到当前用的是哪个 — 设置似乎"没启动"用法. 实现: - BottomAIBar 模式切换条加 ActiveProviderBadge ("LLM · Claude CLI 桥 · claude-sonnet-4-5") - 点击徽章 = window.dispatchEvent('open-ai-settings'), 直达设置面板 - aiConfig.setActiveProvider/setProviderConfig 触发 'ai-provider-changed' 事件, 徽章 + 任何订阅者实时刷新 - 同时监听 'storage' event, 跨标签页改 provider 也能同步 效果: 用户切换 provider 后, 底部徽章立刻变, 一眼看到所有 AI 调用走的是哪个引擎. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c9bd4fb commit 3cec67c

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

src/pages/panels/BottomAIBar.jsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import { useState, useRef, useEffect } from 'react'
1515
import useCanvasStore from '../../stores/useCanvasStore'
1616
import { parseFile } from '../../utils/fileParser'
17+
import { PROVIDER_PRESETS, getAiConfig } from '../../services/aiConfig'
1718

1819
const MODES = [
1920
{
@@ -232,6 +233,8 @@ function BottomAIBar({ showLeftPanel = true, showRightPanel = true, rightPanelWi
232233
{submitting ? '提交中' : '就绪'}
233234
</span>
234235
</div>
236+
{/* 当前活跃 LLM provider 徽章 — 点击直接打开 AI 设置 */}
237+
<ActiveProviderBadge />
235238
</div>
236239

237240
{/* === 已选附件预览 (多文件) === */}
@@ -392,3 +395,61 @@ function BottomAIBar({ showLeftPanel = true, showRightPanel = true, rightPanelWi
392395
}
393396

394397
export default BottomAIBar
398+
399+
// === 当前 LLM provider 徽章 ===
400+
// 显示用户当前选中的 AI provider + model, 点击打开 AI 设置面板
401+
// 让用户随时知道 "现在所有 AI 调用走的是哪个引擎"
402+
function ActiveProviderBadge() {
403+
const [info, setInfo] = useState(() => readActiveInfo())
404+
405+
useEffect(() => {
406+
const refresh = () => setInfo(readActiveInfo())
407+
const onStorage = (e) => {
408+
if (!e.key || e.key === 'know_canvas_ai_config_v1') refresh()
409+
}
410+
const onCustom = () => refresh()
411+
window.addEventListener('storage', onStorage)
412+
window.addEventListener('ai-provider-changed', onCustom)
413+
return () => {
414+
window.removeEventListener('storage', onStorage)
415+
window.removeEventListener('ai-provider-changed', onCustom)
416+
}
417+
}, [])
418+
419+
if (!info) return null
420+
const { label, model } = info
421+
return (
422+
<button
423+
onClick={() => window.dispatchEvent(new CustomEvent('open-ai-settings'))}
424+
title={`当前 LLM: ${label}\n模型: ${model}\n点击切换 / 配置`}
425+
className="flex items-center gap-1.5 px-2.5 py-0.5 rounded-full transition-colors"
426+
style={{
427+
background: 'var(--white, #fafafa)',
428+
border: '1px solid var(--gray-100, #e8e8e8)',
429+
cursor: 'pointer',
430+
}}
431+
onMouseEnter={(e) => { e.currentTarget.style.borderColor = '#c8a882' }}
432+
onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--gray-100, #e8e8e8)' }}
433+
>
434+
<span style={{ fontSize: 10, color: '#888', letterSpacing: '0.1em' }}>LLM</span>
435+
<span style={{ fontSize: 10, color: '#c8a882', fontWeight: 500 }}>{label}</span>
436+
<span style={{ fontSize: 9, color: '#bbb' }}>·</span>
437+
<span style={{ fontSize: 10, color: '#555', fontFamily: 'monospace' }}>{model}</span>
438+
</button>
439+
)
440+
}
441+
442+
function readActiveInfo() {
443+
try {
444+
const cfg = getAiConfig()
445+
const preset = PROVIDER_PRESETS.find((p) => p.id === cfg.activeProviderId) || PROVIDER_PRESETS[0]
446+
const merged = cfg.providers?.[preset.id] || preset.config
447+
return {
448+
id: preset.id,
449+
label: preset.label.replace(/.*?|\(.*?\)/g, '').trim(),
450+
model: merged.model || preset.config.model || '—',
451+
}
452+
} catch {
453+
return null
454+
}
455+
}

src/services/aiConfig.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,19 @@ export function setActiveProvider(id) {
154154
const cfg = getAiConfig()
155155
cfg.activeProviderId = id
156156
setAiConfig(cfg)
157+
// 通知 BottomAIBar 等订阅者刷新徽章
158+
if (typeof window !== 'undefined') {
159+
window.dispatchEvent(new CustomEvent('ai-provider-changed'))
160+
}
157161
}
158162

159163
export function setProviderConfig(id, partial) {
160164
const cfg = getAiConfig()
161165
cfg.providers[id] = { ...(cfg.providers[id] || {}), ...partial }
162166
setAiConfig(cfg)
167+
if (typeof window !== 'undefined') {
168+
window.dispatchEvent(new CustomEvent('ai-provider-changed'))
169+
}
163170
}
164171

165172
/** 取当前激活 provider 的完整描述(含 type + config) */

0 commit comments

Comments
 (0)