Hide admin memory details and fix memory config

This commit is contained in:
wuyang6
2026-05-13 14:29:06 +08:00
parent af07bc77ed
commit 7282a18116
2 changed files with 57 additions and 196 deletions
+15 -36
View File
@@ -522,7 +522,7 @@ class AgentState:
) )
self.memory_manager = PersonalMemoryManager( self.memory_manager = PersonalMemoryManager(
self.session_directory.parent / 'accounts', self.session_directory.parent / 'accounts',
self.config_for, self.model_config_for,
) )
self.memory_manager.start() self.memory_manager.start()
@@ -571,6 +571,20 @@ class AgentState:
with self._lock: with self._lock:
return self._config_for(account_id) return self._config_for(account_id)
def model_config_for(self, account_id: str | None) -> ModelConfig:
"""给后台辅助任务使用的模型配置。
AgentInstanceConfig 还包含 cwd、权限等运行态字段;调用模型时需要的是
OpenAICompatClient 支持的 ModelConfig,否则后台记忆整理会缺少 temperature 等字段。
"""
config = self.config_for(account_id)
return ModelConfig(
model=config.model,
base_url=config.base_url,
api_key=config.api_key,
timeout_seconds=config.timeout_seconds,
)
def _set_config_for(self, account_id: str | None, config: AgentInstanceConfig) -> None: def _set_config_for(self, account_id: str | None, config: AgentInstanceConfig) -> None:
if not account_id: if not account_id:
self._default_config = config self._default_config = config
@@ -980,7 +994,6 @@ class AdminAccountCreateRequest(BaseModel):
class MemoryUpdateRequest(BaseModel): class MemoryUpdateRequest(BaseModel):
account_id: str = Field(min_length=1) account_id: str = Field(min_length=1)
content: str content: str
skill: str | None = None
def _append_runtime_context(current: str | None, addition: str) -> str: def _append_runtime_context(current: str | None, addition: str) -> str:
@@ -1309,40 +1322,6 @@ def create_app(state: AgentState) -> FastAPI:
), ),
} }
@app.get('/api/admin/memory/user')
async def admin_user_memory(token: str, account_id: str) -> dict[str, Any]:
_require_admin_token(token)
return state.memory_manager.list_user_memory(_safe_account_id(account_id))
@app.put('/api/admin/memory/user')
async def admin_update_user_memory(
token: str,
payload: MemoryUpdateRequest,
) -> dict[str, Any]:
_require_admin_token(token)
return state.memory_manager.update_user_memory(
_safe_account_id(payload.account_id),
payload.content,
)
@app.get('/api/admin/memory/skills')
async def admin_skill_memories(token: str, account_id: str) -> list[dict[str, Any]]:
_require_admin_token(token)
return state.memory_manager.list_skill_memories(_safe_account_id(account_id))
@app.put('/api/admin/memory/skills/{skill_name}')
async def admin_update_skill_memory(
skill_name: str,
token: str,
payload: MemoryUpdateRequest,
) -> dict[str, Any]:
_require_admin_token(token)
return state.memory_manager.update_skill_memory(
_safe_account_id(payload.account_id),
skill_name,
payload.content,
)
@app.get('/api/models') @app.get('/api/models')
async def list_models_get(account_id: str | None = None) -> dict[str, Any]: async def list_models_get(account_id: str | None = None) -> dict[str, Any]:
config = state.config_for(account_id) config = state.config_for(account_id)
+42 -160
View File
@@ -4,7 +4,6 @@ import {
ActivityIcon, ActivityIcon,
DatabaseIcon, DatabaseIcon,
RefreshCwIcon, RefreshCwIcon,
SaveIcon,
Trash2Icon, Trash2Icon,
UserPlusIcon, UserPlusIcon,
UsersIcon, UsersIcon,
@@ -68,19 +67,6 @@ type MemoryEvent = {
updated_at: string; updated_at: string;
}; };
type UserMemory = {
content: string;
line_count: number;
updated_at?: string | null;
};
type SkillMemory = {
skill: string;
content: string;
line_count: number;
updated_at?: string | null;
};
const TOKEN_KEY = "zk-admin-token"; const TOKEN_KEY = "zk-admin-token";
export default function AdminPage() { export default function AdminPage() {
@@ -168,10 +154,6 @@ function AdminDashboard({
const [accounts, setAccounts] = useState<AccountRow[]>([]); const [accounts, setAccounts] = useState<AccountRow[]>([]);
const [selectedAccountId, setSelectedAccountId] = useState(""); const [selectedAccountId, setSelectedAccountId] = useState("");
const [events, setEvents] = useState<MemoryEvent[]>([]); const [events, setEvents] = useState<MemoryEvent[]>([]);
const [userMemory, setUserMemory] = useState<UserMemory | null>(null);
const [skillMemories, setSkillMemories] = useState<SkillMemory[]>([]);
const [selectedSkill, setSelectedSkill] = useState("");
const [memoryDraft, setMemoryDraft] = useState("");
const [newAccountId, setNewAccountId] = useState(""); const [newAccountId, setNewAccountId] = useState("");
const [notice, setNotice] = useState(""); const [notice, setNotice] = useState("");
@@ -179,10 +161,6 @@ function AdminDashboard({
() => accounts.find((item) => item.account_id === selectedAccountId), () => accounts.find((item) => item.account_id === selectedAccountId),
[accounts, selectedAccountId], [accounts, selectedAccountId],
); );
const selectedSkillMemory = useMemo(
() => skillMemories.find((item) => item.skill === selectedSkill),
[skillMemories, selectedSkill],
);
// biome-ignore lint/correctness/useExhaustiveDependencies: 首次进入后台时加载一次即可,后续由刷新按钮触发。 // biome-ignore lint/correctness/useExhaustiveDependencies: 首次进入后台时加载一次即可,后续由刷新按钮触发。
useEffect(() => { useEffect(() => {
@@ -195,14 +173,6 @@ function AdminDashboard({
loadAccountMemory(selectedAccountId); loadAccountMemory(selectedAccountId);
}, [selectedAccountId]); }, [selectedAccountId]);
useEffect(() => {
if (selectedSkillMemory) {
setMemoryDraft(selectedSkillMemory.content);
} else if (userMemory) {
setMemoryDraft(userMemory.content);
}
}, [selectedSkillMemory, userMemory]);
async function refresh() { async function refresh() {
const [summaryPayload, accountPayload] = await Promise.all([ const [summaryPayload, accountPayload] = await Promise.all([
adminFetch<AdminSummary>(token, "/summary"), adminFetch<AdminSummary>(token, "/summary"),
@@ -217,25 +187,11 @@ function AdminDashboard({
} }
async function loadAccountMemory(accountId: string) { async function loadAccountMemory(accountId: string) {
const [memoryPayload, userPayload, skillPayload] = await Promise.all([ const memoryPayload = await adminFetch<{ events: MemoryEvent[] }>(
adminFetch<{ events: MemoryEvent[] }>( token,
token, `/memory?account_id=${encodeURIComponent(accountId)}`,
`/memory?account_id=${encodeURIComponent(accountId)}`, );
),
adminFetch<UserMemory>(
token,
`/memory/user?account_id=${encodeURIComponent(accountId)}`,
),
adminFetch<SkillMemory[]>(
token,
`/memory/skills?account_id=${encodeURIComponent(accountId)}`,
),
]);
setEvents(memoryPayload.events ?? []); setEvents(memoryPayload.events ?? []);
setUserMemory(userPayload);
setSkillMemories(skillPayload);
setSelectedSkill("");
setMemoryDraft(userPayload.content);
} }
async function createAccount() { async function createAccount() {
@@ -264,34 +220,6 @@ function AdminDashboard({
await refresh(); await refresh();
} }
async function saveMemory() {
if (!selectedAccountId) return;
if (selectedSkill) {
await adminFetch(
token,
`/memory/skills/${encodeURIComponent(selectedSkill)}`,
{
method: "PUT",
body: JSON.stringify({
account_id: selectedAccountId,
content: memoryDraft,
}),
},
);
setNotice(`已保存 ${selectedSkill} 记忆`);
} else {
await adminFetch(token, "/memory/user", {
method: "PUT",
body: JSON.stringify({
account_id: selectedAccountId,
content: memoryDraft,
}),
});
setNotice("已保存用户记忆");
}
await loadAccountMemory(selectedAccountId);
}
function logout() { function logout() {
localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(TOKEN_KEY);
onLogout(); onLogout();
@@ -403,54 +331,8 @@ function AdminDashboard({
/> />
</div> </div>
<div className="grid gap-4 xl:grid-cols-[1fr_360px]"> <div className="grid gap-4 xl:grid-cols-[360px_1fr]">
<div className="rounded-lg border bg-card"> <div className="grid gap-4 content-start">
<div className="flex items-center justify-between border-b p-3">
<div>
<div className="font-medium"></div>
<div className="text-muted-foreground text-xs">
Skill Skill
</div>
</div>
<Button size="sm" onClick={saveMemory}>
<SaveIcon className="size-4" />
</Button>
</div>
<div className="flex gap-2 border-b p-3">
<Button
variant={selectedSkill ? "outline" : "secondary"}
size="sm"
onClick={() => {
setSelectedSkill("");
setMemoryDraft(userMemory?.content ?? "");
}}
>
</Button>
<div className="flex min-w-0 flex-1 gap-2 overflow-x-auto">
{skillMemories.map((item) => (
<Button
key={item.skill}
variant={
selectedSkill === item.skill ? "secondary" : "outline"
}
size="sm"
onClick={() => setSelectedSkill(item.skill)}
>
{item.skill}
</Button>
))}
</div>
</div>
<textarea
className="min-h-[420px] w-full resize-y bg-transparent p-4 font-mono text-sm outline-none"
value={memoryDraft}
onChange={(event) => setMemoryDraft(event.target.value)}
/>
</div>
<div className="grid gap-4">
<div className="rounded-lg border bg-card"> <div className="rounded-lg border bg-card">
<div className="flex items-center gap-2 border-b p-3 font-medium"> <div className="flex items-center gap-2 border-b p-3 font-medium">
<ActivityIcon className="size-4" /> <ActivityIcon className="size-4" />
@@ -476,42 +358,6 @@ function AdminDashboard({
</div> </div>
</div> </div>
<div className="rounded-lg border bg-card">
<div className="border-b p-3 font-medium"></div>
<div className="max-h-[360px] overflow-y-auto p-2">
{events.length ? (
events.map((event) => (
<div
key={event.id}
className="mb-2 rounded-md border bg-muted/20 p-2 text-xs"
>
<div className="flex items-center justify-between gap-2">
<span className="font-medium">{event.status}</span>
<span className="text-muted-foreground">
P{event.priority}
</span>
</div>
<div className="mt-1 truncate text-muted-foreground">
{event.session_id}
</div>
<div className="mt-1">
{event.signals.join(" / ") || "无信号"}
</div>
{event.error ? (
<div className="mt-1 text-destructive">
{event.error}
</div>
) : null}
</div>
))
) : (
<p className="p-4 text-center text-muted-foreground text-sm">
</p>
)}
</div>
</div>
{selectedAccountId ? ( {selectedAccountId ? (
<Button <Button
variant="destructive" variant="destructive"
@@ -522,6 +368,42 @@ function AdminDashboard({
</Button> </Button>
) : null} ) : null}
</div> </div>
<div className="rounded-lg border bg-card">
<div className="border-b p-3 font-medium"></div>
<div className="max-h-[520px] overflow-y-auto p-2">
{events.length ? (
events.map((event) => (
<div
key={event.id}
className="mb-2 rounded-md border bg-muted/20 p-2 text-xs"
>
<div className="flex items-center justify-between gap-2">
<span className="font-medium">{event.status}</span>
<span className="text-muted-foreground">
P{event.priority}
</span>
</div>
<div className="mt-1 truncate text-muted-foreground">
{event.session_id}
</div>
<div className="mt-1">
{event.signals.join(" / ") || "无信号"}
</div>
{event.error ? (
<div className="mt-1 text-destructive">
{event.error}
</div>
) : null}
</div>
))
) : (
<p className="p-4 text-center text-muted-foreground text-sm">
</p>
)}
</div>
</div>
</div> </div>
</section> </section>
</div> </div>