feat: MySQL 索引长度兼容 + 启动自动补列 + 未发布徽标与查询加载态
后端: - 索引/唯一键改用 mysql_length 前缀索引,适配 MySQL 767 字节限制(error 1071):knowledge_notes、knowledge_entities、knowledge_graph_blacklist、agent_skills、feedback - engine.py 新增 _ensure_orm_columns_sync:启动时对比 ORM 元数据,自动 ALTER TABLE 补齐缺失的可空/带默认值列及其索引 - 新增 scripts/audit_index_key_lengths.py 审计全部索引键长度 - 更新 backend/CLAUDE.md 迁移说明 前端: - 智能体卡片/技能列表:所有者与管理员可见未发布徽标 - 智能体广场:服务端查询进行中时灰显旧结果并悬浮加载提示 - 排行榜入口从 Header 移到欢迎语旁 - threads/hooks.ts:抽取 updateCachedThreadLists 统一处理数组与分页两种缓存形态 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
da677f87d5
commit
57a87d6322
15 files changed
+330
-102
No files matched your search
@@ -2,6 +2,7 @@ import type { AIMessage, Message } from "@langchain/langgraph-sdk";
|
||||
import type { ThreadsClient } from "@langchain/langgraph-sdk/client";
|
||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
@@ -298,25 +299,19 @@ export function useThreadStream({
|
||||
);
|
||||
for (const update of updates) {
|
||||
if (update && "title" in update && update.title) {
|
||||
void queryClient.setQueriesData(
|
||||
{
|
||||
queryKey: ["threads", "search"],
|
||||
exact: false,
|
||||
},
|
||||
(oldData: Array<AgentThread> | undefined) => {
|
||||
return oldData?.map((t) => {
|
||||
if (t.thread_id === threadIdRef.current) {
|
||||
return {
|
||||
const nextTitle = update.title;
|
||||
updateCachedThreadLists(queryClient, (threads) =>
|
||||
threads.map((t) =>
|
||||
t.thread_id === threadIdRef.current
|
||||
? {
|
||||
...t,
|
||||
values: {
|
||||
...t.values,
|
||||
title: update.title,
|
||||
title: nextTitle,
|
||||
},
|
||||
};
|
||||
}
|
||||
return t;
|
||||
});
|
||||
},
|
||||
}
|
||||
: t,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -815,6 +810,31 @@ export interface ThreadsPageResult {
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply ``transform`` to the thread array inside every cached
|
||||
* ``["threads", "search"]`` entry. The sidebar queries cache a plain
|
||||
* ``AgentThread[]`` while the paged chats list (`useThreadsPage`) caches a
|
||||
* ``ThreadsPageResult`` object under the same key prefix, so fuzzy
|
||||
* ``setQueriesData`` updaters must handle both shapes.
|
||||
*/
|
||||
function updateCachedThreadLists(
|
||||
queryClient: QueryClient,
|
||||
transform: (threads: AgentThread[]) => AgentThread[],
|
||||
) {
|
||||
queryClient.setQueriesData<AgentThread[] | ThreadsPageResult | undefined>(
|
||||
{ queryKey: ["threads", "search"], exact: false },
|
||||
(oldData) => {
|
||||
if (Array.isArray(oldData)) {
|
||||
return transform(oldData);
|
||||
}
|
||||
if (oldData && Array.isArray(oldData.threads)) {
|
||||
return { ...oldData, threads: transform(oldData.threads) };
|
||||
}
|
||||
return oldData;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side paginated conversation list for the chats page. Unlike
|
||||
* `useThreads` (which loads a bounded recent window for the sidebar), this
|
||||
@@ -935,17 +955,8 @@ export function useDeleteThread() {
|
||||
}
|
||||
},
|
||||
onSuccess(_, { threadId }) {
|
||||
queryClient.setQueriesData(
|
||||
{
|
||||
queryKey: ["threads", "search"],
|
||||
exact: false,
|
||||
},
|
||||
(oldData: Array<AgentThread> | undefined) => {
|
||||
if (oldData == null) {
|
||||
return oldData;
|
||||
}
|
||||
return oldData.filter((t) => t.thread_id !== threadId);
|
||||
},
|
||||
updateCachedThreadLists(queryClient, (threads) =>
|
||||
threads.filter((t) => t.thread_id !== threadId),
|
||||
);
|
||||
},
|
||||
onSettled() {
|
||||
@@ -1031,27 +1042,18 @@ export function useUpdateThreadMetadata() {
|
||||
}
|
||||
},
|
||||
onSuccess(_, { threadId, metadata }) {
|
||||
queryClient.setQueriesData(
|
||||
{
|
||||
queryKey: ["threads", "search"],
|
||||
exact: false,
|
||||
},
|
||||
(oldData: Array<AgentThread> | undefined) => {
|
||||
if (oldData == null) {
|
||||
return oldData;
|
||||
}
|
||||
return oldData.map((t) =>
|
||||
t.thread_id === threadId
|
||||
? {
|
||||
...t,
|
||||
metadata: {
|
||||
...(t.metadata as Record<string, unknown> | null),
|
||||
...metadata,
|
||||
},
|
||||
}
|
||||
: t,
|
||||
);
|
||||
},
|
||||
updateCachedThreadLists(queryClient, (threads) =>
|
||||
threads.map((t) =>
|
||||
t.thread_id === threadId
|
||||
? {
|
||||
...t,
|
||||
metadata: {
|
||||
...(t.metadata as Record<string, unknown> | null),
|
||||
...metadata,
|
||||
},
|
||||
}
|
||||
: t,
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1093,25 +1095,18 @@ export function useRenameThread() {
|
||||
});
|
||||
},
|
||||
onSuccess(_, { threadId, title }) {
|
||||
queryClient.setQueriesData(
|
||||
{
|
||||
queryKey: ["threads", "search"],
|
||||
exact: false,
|
||||
},
|
||||
(oldData: Array<AgentThread>) => {
|
||||
return oldData.map((t) => {
|
||||
if (t.thread_id === threadId) {
|
||||
return {
|
||||
updateCachedThreadLists(queryClient, (threads) =>
|
||||
threads.map((t) =>
|
||||
t.thread_id === threadId
|
||||
? {
|
||||
...t,
|
||||
values: {
|
||||
...t.values,
|
||||
title,
|
||||
},
|
||||
};
|
||||
}
|
||||
return t;
|
||||
});
|
||||
},
|
||||
}
|
||||
: t,
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user