Hide admin memory details and fix memory config
This commit is contained in:
+15
-36
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user