feat(页面): 添加秘塔AI配置界面和聊天页面增强

设置页面:
- 新增秘塔AI配置区块
- 支持秘塔API Key的配置和清除
- 添加秘塔平台链接和工具使用说明

聊天页面:
- 从数据库加载搜索图片和使用工具数据
- 将数据传递给 MessageBubble 组件展示
This commit is contained in:
gaoziman 2025-12-22 12:38:06 +08:00
parent 615a59567d
commit 8bfe420676
2 changed files with 125 additions and 0 deletions

View File

@ -89,10 +89,14 @@ export default function ChatPage({ params }: PageProps) {
outputTokens: msg.outputTokens || undefined,
// 从数据库加载图片数据(代码执行产生的)
images: (msg.images as string[]) || undefined,
// 从数据库加载搜索到的图片(图片搜索工具结果)
searchImages: (msg.searchImages as { title: string; imageUrl: string; width: number; height: number; score: string; position: number; sourceUrl?: string }[]) || undefined,
// 从数据库加载用户上传的图片
uploadedImages: (msg.uploadedImages as string[]) || undefined,
// 从数据库加载用户上传的文档
uploadedDocuments: (msg.uploadedDocuments as { name: string; size: number; type: string; content: string }[]) || undefined,
// 从数据库加载使用的工具列表
usedTools: (msg.usedTools as string[]) || undefined,
}));
setInitialMessages(historyMessages);
}
@ -563,8 +567,10 @@ export default function ChatPage({ params }: PageProps) {
isStreaming={message.status === 'streaming'}
error={message.error}
images={message.images}
searchImages={message.searchImages}
uploadedImages={message.uploadedImages}
uploadedDocuments={message.uploadedDocuments}
usedTools={message.usedTools}
pyodideStatus={message.pyodideStatus}
onRegenerate={message.role === 'assistant' && !isStreaming ? handleRegenerate : undefined}
onSaveToNote={message.role === 'assistant' && !isStreaming ? handleSaveToNote : undefined}

View File

@ -56,6 +56,11 @@ export default function SettingsPage() {
const [showApiKey, setShowApiKey] = useState(false);
const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
// 秘塔AI 配置状态
const [metasoApiKey, setMetasoApiKey] = useState('');
const [showMetasoApiKey, setShowMetasoApiKey] = useState(false);
const [metasoSaveStatus, setMetasoSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
// AI 行为设置状态
const [systemPrompt, setSystemPrompt] = useState('');
const [temperature, setTemperature] = useState('0.7');
@ -107,6 +112,31 @@ export default function SettingsPage() {
}
};
// 保存秘塔AI API Key
const handleSaveMetasoConfig = async () => {
setMetasoSaveStatus('saving');
try {
if (metasoApiKey) {
await updateSettings({ metasoApiKey });
}
setMetasoSaveStatus('saved');
setMetasoApiKey(''); // 清除输入的 API Key
setTimeout(() => setMetasoSaveStatus('idle'), 2000);
} catch {
setMetasoSaveStatus('error');
setTimeout(() => setMetasoSaveStatus('idle'), 2000);
}
};
// 清除秘塔 API Key
const handleClearMetasoApiKey = async () => {
try {
await updateSettings({ metasoApiKey: '' });
} catch (error) {
console.error('Failed to clear Metaso API key:', error);
}
};
// 更新默认模型
const handleModelChange = async (modelId: string) => {
try {
@ -476,6 +506,95 @@ export default function SettingsPage() {
</div>
</SettingsSection>
{/* 秘塔AI 配置 */}
<SettingsSection
title="秘塔AI 配置"
description="配置秘塔AI搜索和读取工具可选"
>
<SettingsItem
label="秘塔 API Key"
description={
settings?.metasoApiKeyConfigured ? (
'已配置秘塔 API Key'
) : (
<span>
使 Metaso Search Metaso Reader
<a
href="https://metaso.cn/"
target="_blank"
rel="noopener noreferrer"
className="text-[var(--color-primary)] hover:underline ml-1"
>
API Key
</a>
</span>
)
}
>
<div className="flex items-center gap-2">
{settings?.metasoApiKeyConfigured ? (
<>
<span className="inline-flex items-center gap-1 text-sm text-green-600">
<Check size={14} />
</span>
<button
onClick={handleClearMetasoApiKey}
className="btn-ghost text-red-600 hover:text-red-700 text-sm"
disabled={saving}
>
</button>
</>
) : (
<div className="relative">
<input
type={showMetasoApiKey ? 'text' : 'password'}
className="settings-input pr-10 w-80"
value={metasoApiKey}
onChange={(e) => setMetasoApiKey(e.target.value)}
placeholder="输入秘塔 API Key"
/>
<button
type="button"
className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]"
onClick={() => setShowMetasoApiKey(!showMetasoApiKey)}
>
{showMetasoApiKey ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
)}
</div>
</SettingsItem>
{!settings?.metasoApiKeyConfigured && (
<div className="px-5 py-4 border-t border-[var(--color-border-light)]">
<button
onClick={handleSaveMetasoConfig}
disabled={metasoSaveStatus === 'saving' || !metasoApiKey}
className="btn-primary inline-flex items-center gap-2"
>
{metasoSaveStatus === 'saving' ? (
<Loader2 size={16} className="animate-spin" />
) : metasoSaveStatus === 'saved' ? (
<Check size={16} />
) : null}
{metasoSaveStatus === 'saving' ? '保存中...' : metasoSaveStatus === 'saved' ? '已保存' : '保存秘塔配置'}
</button>
{metasoSaveStatus === 'error' && (
<span className="ml-3 text-sm text-red-600"></span>
)}
</div>
)}
<div className="px-5 py-3 bg-[var(--color-bg-tertiary)] border-t border-[var(--color-border-light)]">
<p className="text-xs text-[var(--color-text-tertiary)]">
💡 Metaso Search Metaso Reader Markdown
API Key 使
</p>
</div>
</SettingsSection>
{/* 模型和工具设置 */}
<SettingsSection
title="AI 配置"