feat(persona,web): 人格读侧(thinking 三态 + 角色级 mood)+ 面板表单 + mood 播种
按 design/04 §3.2(thinking 语义,showToUser 三态)+ design/01 §2.1 + 主人两条定论(心情跟角色不跟会话;mood 初值走口径 ①:由角色读端点暴露、前端加载播种)。 - 管理侧 GET /admin/personas 的 PersonaSummary 增 thinking(budgetTokens/effort/mode/showToUser)——**showToUser 保持三态**(未配置 ≠ false) - 用户侧 GET /personas 增**角色级 mood**(取自 emotion 层该角色当前状态,非从消息推断)→ 前端加载播种 currentMood,消除「刷新后心情徽标短暂为空」 - 前端 Personas 页加 thinking 四控件(预算/effort/模式/showToUser 三态):只做输入校验,**provider 是否真支持由后端 WARN 决定,前端不假装能力** - 先拆 useAppStore 选择器(原 494/500)到独立模块再做,避免越线 - Docs/api-client 版本号 +1(两处契约 + 三态与角色级 mood 语义) - 验收:go build/vet/test 全绿 + linux/darwin 交叉编译 + 前端 build 绿 + personaSession/messageSync 逻辑测试通过
This commit is contained in:
1 parent
6dc1f218d5
commit
2aa3233f84
20 files changed
+1042
-61
No files matched your search
@@ -0,0 +1,115 @@
|
||||
import type { ThinkingDraft } from "../../lib/personaThinking";
|
||||
import {
|
||||
THINKING_EFFORTS,
|
||||
THINKING_MODES,
|
||||
thinkingValidationError,
|
||||
} from "../../lib/personaThinking";
|
||||
import { Tag } from "../../components/primitives";
|
||||
|
||||
/** The panel's four `thinking` controls (design/01 §2.1). Split out of Personas.tsx
|
||||
* (which is near its size ceiling) and kept dumb: it renders + validates the form
|
||||
* draft, the request mapping lives in lib/personaThinking.ts.
|
||||
*
|
||||
* It deliberately does NOT claim a capability: `effort` is offered from the union
|
||||
* of the values the providers behind the API recognize, but whether the *active*
|
||||
* provider honors the value is decided by the backend (it validates against the
|
||||
* provider whitelist and WARNs when it drops one). The hint says so instead of
|
||||
* pretending the knob is on. */
|
||||
|
||||
const inputCls =
|
||||
"w-full rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]";
|
||||
|
||||
export function ThinkingFields({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: ThinkingDraft;
|
||||
onChange: (next: ThinkingDraft) => void;
|
||||
}): React.ReactNode {
|
||||
const set = <K extends keyof ThinkingDraft>(key: K, v: ThinkingDraft[K]) =>
|
||||
onChange({ ...value, [key]: v });
|
||||
const err = thinkingValidationError(value);
|
||||
|
||||
return (
|
||||
<div className="space-y-2 rounded-[var(--mu-radius)] bg-[var(--mu-surface-3)]/40 px-3 py-3">
|
||||
<div className="flex flex-wrap items-baseline gap-2">
|
||||
<span className="text-sm font-semibold">思考配置 thinking</span>
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">
|
||||
只描述呈现/资源,不构成能力边界(design/01 §2.1);留空 = 不配置
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">预算 budgetTokens(非负整数,留空 = 不配置)</span>
|
||||
<input
|
||||
value={value.budgetTokens}
|
||||
inputMode="numeric"
|
||||
placeholder="4096"
|
||||
onChange={(e) => set("budgetTokens", e.target.value)}
|
||||
className={inputCls}
|
||||
/>
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">强度 effort(可留空)</span>
|
||||
<select
|
||||
value={value.effort}
|
||||
onChange={(e) => set("effort", e.target.value)}
|
||||
className={inputCls}
|
||||
>
|
||||
<option value="">(不配置)</option>
|
||||
{THINKING_EFFORTS.map((e) => (
|
||||
<option key={e} value={e}>
|
||||
{e}
|
||||
</option>
|
||||
))}
|
||||
{/* A stored value outside the list is shown as-is rather than being
|
||||
* silently displayed as "unconfigured" (Docs/api-client.md §1.1: an
|
||||
* unknown value is never dropped or replaced by a placeholder). The
|
||||
* validation error then asks the user to pick a recognized one. */}
|
||||
{value.effort && !(THINKING_EFFORTS as readonly string[]).includes(value.effort) ? (
|
||||
<option value={value.effort}>{value.effort}(未知取值,后端会 WARN 并忽略)</option>
|
||||
) : null}
|
||||
</select>
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">
|
||||
回显 showToUser(未配置 = 保持现行为:思考帧照常推给客户端)
|
||||
</span>
|
||||
<select
|
||||
value={value.showToUser}
|
||||
onChange={(e) => set("showToUser", e.target.value as ThinkingDraft["showToUser"])}
|
||||
className={inputCls}
|
||||
>
|
||||
<option value="unset">未配置(保持现行为)</option>
|
||||
<option value="on">开(推帧)</option>
|
||||
<option value="off">关(只落库不回显)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs text-[var(--mu-text-lo)]">开关 mode(硬开关,推理)</span>
|
||||
<select
|
||||
value={value.mode}
|
||||
onChange={(e) => set("mode", e.target.value as ThinkingDraft["mode"])}
|
||||
className={inputCls}
|
||||
>
|
||||
<option value="">(未配置)</option>
|
||||
{THINKING_MODES.map((m) => (
|
||||
<option key={m} value={m}>
|
||||
{m}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-xs text-[var(--mu-text-lo)]">
|
||||
effort 的有效取值随当前生效 provider 而定(OpenAI 兼容系
|
||||
none/minimal/low/medium/high/xhigh/max;DeepSeek none/low/high/max,别名
|
||||
minimal→low、medium→high、xhigh→high、ultra→max;未知 provider 不支持)。面板只做前端枚举校验;
|
||||
该值在上游是否真生效,由后端按 provider 校验并记 WARN 决定,此处不代为承诺。
|
||||
</p>
|
||||
{err ? (
|
||||
<Tag tone="err">{err}</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user