|
14 | 14 | import { useState, useRef, useEffect } from 'react' |
15 | 15 | import useCanvasStore from '../../stores/useCanvasStore' |
16 | 16 | import { parseFile } from '../../utils/fileParser' |
| 17 | +import { PROVIDER_PRESETS, getAiConfig } from '../../services/aiConfig' |
17 | 18 |
|
18 | 19 | const MODES = [ |
19 | 20 | { |
@@ -232,6 +233,8 @@ function BottomAIBar({ showLeftPanel = true, showRightPanel = true, rightPanelWi |
232 | 233 | {submitting ? '提交中' : '就绪'} |
233 | 234 | </span> |
234 | 235 | </div> |
| 236 | + {/* 当前活跃 LLM provider 徽章 — 点击直接打开 AI 设置 */} |
| 237 | + <ActiveProviderBadge /> |
235 | 238 | </div> |
236 | 239 |
|
237 | 240 | {/* === 已选附件预览 (多文件) === */} |
@@ -392,3 +395,61 @@ function BottomAIBar({ showLeftPanel = true, showRightPanel = true, rightPanelWi |
392 | 395 | } |
393 | 396 |
|
394 | 397 | 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 | +} |
0 commit comments