Files
Agent-System-Frontend/src/components/BorrowPanels.tsx
T
weichen 36b92ede88 Add secret, injection, and message controls to the workbench
Why: Keys must be added without writing masks back, and a turn needs copy, exclude, and regenerate without deleting history.
Evidence: npx tsc -b. Session notes and candidate switches call the new conversation and message routes.
Impact: Settings gains key, lore, and slash-template panels. Excluded messages stay visible and are dimmed.
Refs: docs/records/2026-10-04-sillytavern-review.md
2026-10-04 18:20:10 +00:00

105 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from 'react'
import { Btn, SettingRow, SettingsGroup } from '@ui'
import { api } from '../api'
const services = [
['models.chat', '主模型'],
['models.lightweight', '轻量模型'],
['models.embedding', 'Embedding'],
['models.rerank', 'Reranker'],
['tools.tavily', 'Tavily'],
] as const
type Secret = { id: string; service: string; label: string; active: boolean; mask: string }
type Entry = { id: string; keys: string[]; content: string; constant: boolean; logic: string }
type Cmd = { id: string; name: string; title: string; template: string }
export function BorrowPanels() {
const [secrets, setSecrets] = useState<Secret[]>([])
const [entries, setEntries] = useState<Entry[]>([])
const [cmds, setCmds] = useState<Cmd[]>([])
const [service, setService] = useState<string>(services[0][0])
const [label, setLabel] = useState('')
const [value, setValue] = useState('')
const [keys, setKeys] = useState('')
const [content, setContent] = useState('')
const [constant, setConstant] = useState(false)
const [cmdName, setCmdName] = useState('/summary')
const [cmdTitle, setCmdTitle] = useState('总结')
const [cmdBody, setCmdBody] = useState('请总结以上内容。\n{{input}}')
const reload = () => {
api.listSecrets().then(setSecrets).catch(() => undefined)
api.listContextEntries().then(setEntries).catch(() => undefined)
api.listPromptCommands().then(setCmds).catch(() => undefined)
}
useEffect(() => { reload() }, [])
return (
<>
<SettingsGroup title="密钥">
<SettingRow label="新增密钥" desc="每个服务同时只有一把生效。列表只显示掩码。">
<select value={service} onChange={e => setService(e.target.value)}>
{services.map(([id, name]) => <option key={id} value={id}>{name}</option>)}
</select>
</SettingRow>
<SettingRow label="标签与密钥" desc="保存后立即生效,并停用同服务的其他密钥。">
<input className="search-input" value={label} placeholder="标签" onChange={e => setLabel(e.target.value)} />
<input className="search-input" type="password" autoComplete="new-password" value={value} placeholder="密钥" onChange={e => setValue(e.target.value)} />
<Btn variant="primary" onClick={async () => {
if (!value.trim()) return
await api.createSecret(service, label || '未命名', value.trim())
setValue(''); setLabel(''); reload()
}}>添加</Btn>
</SettingRow>
{secrets.map(s => (
<SettingRow key={s.id} label={s.label} desc={`${s.service} · ${s.mask}${s.active ? ' · 生效' : ''}`}>
{!s.active && <Btn onClick={async () => { await api.activateSecret(s.id); reload() }}>启用</Btn>}
<Btn onClick={async () => { await api.deleteSecret(s.id); reload() }}>删除</Btn>
</SettingRow>
))}
</SettingsGroup>
<SettingsGroup title="条件注入">
<SettingRow label="关键词" desc="逗号分隔。用 /正则/ 表示正则。勾选后变为常驻。">
<input className="search-input" value={keys} placeholder="关键词" onChange={e => setKeys(e.target.value)} />
<label><input type="checkbox" checked={constant} onChange={e => setConstant(e.target.checked)} /> 常驻</label>
</SettingRow>
<SettingRow label="注入正文" desc="命中后附在上下文末尾,不改系统前缀。">
<textarea rows={3} value={content} onChange={e => setContent(e.target.value)} />
<Btn variant="primary" onClick={async () => {
if (!content.trim()) return
await api.saveContextEntry({
keys: keys.split(/[,,]/).map(s => s.trim()).filter(Boolean),
content: content.trim(), constant, logic: 'and', enabled: true, scanDepth: 4, budgetChars: 2000,
})
setContent(''); setKeys(''); reload()
}}>添加</Btn>
</SettingRow>
{entries.map(e => (
<SettingRow key={e.id} label={e.constant ? '常驻' : (e.keys.join(' / ') || '未命名')} desc={e.content}>
<Btn onClick={async () => { await api.deleteContextEntry(e.id); reload() }}>删除</Btn>
</SettingRow>
))}
</SettingsGroup>
<SettingsGroup title="用户命令">
<SettingRow label="命令" desc="发送 /名称 时展开成提示词。不会自动触发。{{input}} 换成命令后面的文字。">
<input className="search-input" value={cmdName} onChange={e => setCmdName(e.target.value)} />
<input className="search-input" value={cmdTitle} onChange={e => setCmdTitle(e.target.value)} />
</SettingRow>
<SettingRow label="模板" desc="名称需为小写字母、数字或连字符,例如 /summary。">
<textarea rows={3} value={cmdBody} onChange={e => setCmdBody(e.target.value)} />
<Btn variant="primary" onClick={async () => {
await api.savePromptCommand({ name: cmdName, title: cmdTitle, template: cmdBody })
reload()
}}>添加</Btn>
</SettingRow>
{cmds.map(c => (
<SettingRow key={c.id} label={c.name} desc={c.title}>
<Btn onClick={async () => { await api.deletePromptCommand(c.id); reload() }}>删除</Btn>
</SettingRow>
))}
</SettingsGroup>
</>
)
}