feat: 完善知识图谱交互与 Agent/线程持久化
- 知识图谱画布、侧边栏、详情面板交互优化,新增图谱相关 API 与 hooks - Agent 卡片与详情面板调整,对话页面重构 - 线程元数据持久化增强(base/memory/sql),knowledge 仓储与导出补充 - 网关路由 agents/knowledge/threads 同步更新 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ea78fe9726
commit
f107ebc46d
25 files changed
+929
-279
No files matched your search
@@ -12,7 +12,7 @@ import { getAPIClient } from "../api";
|
||||
import { apiFetch } from "../api";
|
||||
import { getStoredAuth } from "../auth";
|
||||
import { rememberAskTime } from "../chat-timestamps";
|
||||
import { getBackendBaseURL } from "../config";
|
||||
import { getBackendBaseURL, getLangGraphBaseURL } from "../config";
|
||||
import { useI18n } from "../i18n/hooks";
|
||||
import type { FileInMessage } from "../messages/utils";
|
||||
import type { LocalSettings } from "../settings";
|
||||
@@ -761,14 +761,86 @@ export function useThreads(
|
||||
offset += response.length;
|
||||
}
|
||||
|
||||
return threads.filter(
|
||||
(t) => (t.metadata as Record<string, unknown> | null)?.thread_type !== "notebook",
|
||||
);
|
||||
// Safety net on top of the backend's system-thread exclusion: drop
|
||||
// machine-created threads (scheduled tasks, roundtable workers,
|
||||
// notebooks) in case an older backend still returns them.
|
||||
return threads.filter((t) => {
|
||||
const metadata = t.metadata as Record<string, unknown> | null;
|
||||
if (metadata?.system) return false;
|
||||
const threadType = metadata?.thread_type;
|
||||
return (
|
||||
threadType !== "notebook" &&
|
||||
threadType !== "scheduler" &&
|
||||
threadType !== "roundtable"
|
||||
);
|
||||
});
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ThreadsPageResult {
|
||||
threads: AgentThread[];
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side paginated conversation list for the chats page. Unlike
|
||||
* `useThreads` (which loads a bounded recent window for the sidebar), this
|
||||
* fetches exactly one page per request so a long history is never pulled in
|
||||
* one go. `search` matches thread titles on the backend; one extra row is
|
||||
* over-fetched only to detect whether a next page exists. The query key stays
|
||||
* under ["threads", "search"] so existing invalidations refresh pages too.
|
||||
*/
|
||||
export function useThreadsPage(options: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
search?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}) {
|
||||
const { page, pageSize, search, metadata } = options;
|
||||
const normalizedSearch = search?.trim() ?? "";
|
||||
return useQuery<ThreadsPageResult>({
|
||||
queryKey: [
|
||||
"threads",
|
||||
"search",
|
||||
"paged",
|
||||
page,
|
||||
pageSize,
|
||||
normalizedSearch,
|
||||
metadata ?? null,
|
||||
],
|
||||
queryFn: async () => {
|
||||
// Direct fetch instead of the LangGraph SDK: the SDK's threads.search
|
||||
// drops fields it doesn't know about, and `query` (title search) is a
|
||||
// gateway extension.
|
||||
const response = await apiFetch(
|
||||
`${getLangGraphBaseURL()}/threads/search`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
limit: pageSize + 1,
|
||||
offset: (page - 1) * pageSize,
|
||||
metadata: metadata ?? {},
|
||||
query: normalizedSearch || undefined,
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to load conversations");
|
||||
}
|
||||
const rows = (await response.json()) as AgentThread[];
|
||||
return {
|
||||
threads: rows.slice(0, pageSize),
|
||||
hasMore: rows.length > pageSize,
|
||||
};
|
||||
},
|
||||
placeholderData: (previous) => previous,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteThread() {
|
||||
const queryClient = useQueryClient();
|
||||
const apiClient = getAPIClient();
|
||||
|
||||
Reference in new issue
Block a user