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
@@ -13,7 +13,6 @@ import type {
|
||||
ToolUsed,
|
||||
WsStatus,
|
||||
} from "../lib/types";
|
||||
import { resolveSessionPersona } from "../lib/persona";
|
||||
import { now, uid } from "../lib/util";
|
||||
|
||||
interface ChatContext {
|
||||
@@ -37,7 +36,8 @@ export interface AppStore {
|
||||
* persona-scoped and carries no `sessionId`, so each role gets its own bucket
|
||||
* and the UI shows the bucket of the *active session's* role — role X's mood
|
||||
* must not sit above role Y's conversation. `""` holds events that arrived
|
||||
* without a persona (legacy/unscoped frames). */
|
||||
* without a persona (legacy/unscoped frames). Seeded from the role catalog's
|
||||
* role-level mood on load (`seedMoods` + `personaMoodSeed`). */
|
||||
moods: Record<string, string>;
|
||||
context: ChatContext;
|
||||
agents: AgentInfo[];
|
||||
@@ -72,6 +72,12 @@ export interface AppStore {
|
||||
/** Record one role's new mood. `personaId` is the role the event named; "" keeps
|
||||
* an unscoped/legacy frame in its own bucket. A null mood drops the bucket. */
|
||||
setMood: (mood: string | null, personaId?: string) => void;
|
||||
/** Merge role-level moods read from `GET /personas` into the buckets (design/01
|
||||
* §2: mood follows the role, so the catalog is an authoritative seed). Values
|
||||
* already known are overwritten by the server's truth; buckets the seed does
|
||||
* not mention are left alone, so a WS frame that arrived first is never
|
||||
* cleared. Build the seed with `personaMoodSeed`. */
|
||||
seedMoods: (seed: Record<string, string>) => void;
|
||||
|
||||
/** Replace the session metadata list. */
|
||||
setSessions: (list: SessionMeta[]) => void;
|
||||
@@ -166,6 +172,8 @@ export const useAppStore = create<AppStore>()(
|
||||
else delete moods[personaId];
|
||||
return { moods };
|
||||
}),
|
||||
seedMoods: (seed) =>
|
||||
set((s) => (Object.keys(seed).length ? { moods: { ...s.moods, ...seed } } : s)),
|
||||
|
||||
setSessions: (list) =>
|
||||
set({
|
||||
@@ -450,26 +458,6 @@ export const useAppStore = create<AppStore>()(
|
||||
)
|
||||
);
|
||||
|
||||
/** The role the UI renders (design/22 §4.6): the active session's bound role,
|
||||
* else the global default. THE single resolution point — the avatar, name,
|
||||
* wallpaper, mood bucket and message labels all read it, so none of them can
|
||||
* disagree about "who is speaking" (design/22 §5 risk row 1). */
|
||||
export function selectCurrentPersona(s: AppStore): PersonaRecord | null {
|
||||
return resolveSessionPersona(s.activeSessionId, s.sessions, s.personas);
|
||||
}
|
||||
|
||||
/** Subscribe to the active session's role. */
|
||||
export function useCurrentPersona(): PersonaRecord | null {
|
||||
return useAppStore(selectCurrentPersona);
|
||||
}
|
||||
|
||||
/** Subscribe to the active role's mood. `mood` frames are role-scoped and carry
|
||||
* no sessionId, so a role's mood is only shown above its own sessions. */
|
||||
export function useCurrentMood(): string | null {
|
||||
const personaId = useAppStore((s) => selectCurrentPersona(s)?.id ?? "");
|
||||
return useAppStore((s) => (personaId ? s.moods[personaId] ?? null : null));
|
||||
}
|
||||
|
||||
/** Convenience selector for the last streaming assistant message id. */
|
||||
export function lastStreamingMessage(s: AppStore): ChatMessage | undefined {
|
||||
const last = s.messages[s.messages.length - 1];
|
||||
|
||||
Reference in new issue
Block a user