+
void;
@@ -70,9 +70,11 @@ export default function CapabilitySettings({
-
- {currentName}
-
+ {currentName && (
+
+ {currentName}
+
+ )}
{agent?.description || '补充基础资料后,这里会更像一个完整可运营的产品角色。'}
@@ -229,9 +231,20 @@ export default function CapabilitySettings({
{item.originalName}
{item.size ? `${(item.size / 1024).toFixed(1)} KB` : ''} ·{' '}
-
- {STATUS_TAG[(item.status || 'ready')].text}
-
+ {item.status === 'indexing' ? (
+
+
+ 索引中…
+
+
+ ) : (
+
+ {STATUS_TAG[(item.status || 'ready')].text}
+
+ )}
diff --git a/src/pages/AgentEditor/components/Header.tsx b/src/pages/AgentEditor/components/Header.tsx
index 906af88..5ec3128 100644
--- a/src/pages/AgentEditor/components/Header.tsx
+++ b/src/pages/AgentEditor/components/Header.tsx
@@ -3,7 +3,7 @@ import { ArrowLeftOutlined, FileTextOutlined, SaveOutlined, RocketOutlined } fro
interface HeaderProps {
isNew: boolean;
- currentName: string;
+ currentName?: string;
navigate: any;
autoSaveStatus: 'saved' | 'dirty' | 'saving' | 'error';
saving: boolean;
@@ -25,7 +25,7 @@ export default function Header({ isNew, currentName, navigate, autoSaveStatus, s
- {isNew ? '创建新智能体' : currentName}
+ {isNew ? '创建新智能体' : currentName || ''}
diff --git a/src/pages/AgentEditor/hooks/useAgentEditor.ts b/src/pages/AgentEditor/hooks/useAgentEditor.ts
index d330da5..4be33c5 100644
--- a/src/pages/AgentEditor/hooks/useAgentEditor.ts
+++ b/src/pages/AgentEditor/hooks/useAgentEditor.ts
@@ -26,8 +26,59 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
const [editingSkillId, setEditingSkillId] = useState
(null);
const pollTimer = useRef(null);
+ const pollCount = useRef(0);
const hydratingRef = useRef(false);
+ const MAX_POLL_COUNT = 30;
+
+ const stopPolling = () => {
+ if (pollTimer.current) {
+ window.clearInterval(pollTimer.current);
+ pollTimer.current = null;
+ }
+ pollCount.current = 0;
+ };
+
+ const startPolling = () => {
+ pollCount.current = 0;
+ if (pollTimer.current) {
+ window.clearInterval(pollTimer.current);
+ }
+ pollTimer.current = window.setInterval(pollKnowledgeStatus, 4000);
+ pollKnowledgeStatus();
+ };
+
+ const pollKnowledgeStatus = async () => {
+ if (!id) return;
+ try {
+ const result = await AgentAPI.getKnowledgeStatus(id);
+ const files = result.files || [];
+
+ setAgent((prev) => {
+ if (!prev) return prev;
+ return { ...prev, knowledge: files };
+ });
+
+ const indexing = files.some((k) => k.status === 'pending' || k.status === 'indexing');
+ pollCount.current++;
+
+ if (!indexing || pollCount.current >= MAX_POLL_COUNT) {
+ stopPolling();
+ if (!indexing) {
+ refresh(false);
+ } else {
+ message.warning('部分文件处理超时,请稍后刷新页面查看结果');
+ }
+ }
+ } catch (e) {
+ pollCount.current++;
+ if (pollCount.current >= MAX_POLL_COUNT) {
+ stopPolling();
+ message.warning('知识库状态查询超时,请稍后刷新页面查看结果');
+ }
+ }
+ };
+
const refresh = async (force = false) => {
if (!id) return;
const data = await AgentAPI.detail(id);
@@ -45,10 +96,7 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
const indexing = data.knowledge?.some((k) => k.status === 'pending' || k.status === 'indexing');
if (indexing && !pollTimer.current) {
- pollTimer.current = window.setInterval(refresh, 2000);
- } else if (!indexing && pollTimer.current) {
- window.clearInterval(pollTimer.current);
- pollTimer.current = null;
+ startPolling();
}
};
@@ -111,7 +159,20 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
if (!silent) setSaving(true);
setAutoSaveStatus('saving');
try {
- const updatedAgent = await AgentAPI.update(id!, values);
+ const changedFields: Record = {};
+ Object.keys(values).forEach((key) => {
+ const formValue = (values as any)[key];
+ const originalValue = (agent as any)?.[key];
+ if (formValue !== originalValue) {
+ changedFields[key] = formValue;
+ }
+ });
+ if (Object.keys(changedFields).length === 0) {
+ if (!silent) message.info('无变化');
+ setAutoSaveStatus('saved');
+ return;
+ }
+ const updatedAgent = await AgentAPI.update(id!, changedFields);
setAgent(updatedAgent);
form.setFieldsValue(updatedAgent);
if (!silent) message.success('已保存');
@@ -171,6 +232,7 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
knowledge: [...uniqueNewFiles, ...(prev.knowledge || [])],
};
});
+ startPolling();
message.success(`${file.name} 已上传,正在建索引…`);
} catch (e: any) {
message.error('上传失败:' + (e?.message ?? e));
@@ -192,8 +254,12 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
const handleAvatarSelect = async (avatarUrl: string) => {
if (!id) return;
try {
- const updatedAgent = await AgentAPI.updateAvatar(id, avatarUrl);
- setAgent(updatedAgent);
+ await AgentAPI.updateAvatar(id, avatarUrl);
+ setAgent((prev) => {
+ if (!prev) return prev;
+ return { ...prev, avatar: avatarUrl };
+ });
+ setSelectedAvatar(avatarUrl);
message.success('头像已更新');
setAvatarSelectorOpen(false);
} catch (e: any) {
@@ -205,8 +271,12 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
if (!id) return false;
try {
const url = await uploadAvatar(file as File);
- const updatedAgent = await AgentAPI.updateAvatar(id, url);
- setAgent(updatedAgent);
+ await AgentAPI.updateAvatar(id, url);
+ setAgent((prev) => {
+ if (!prev) return prev;
+ return { ...prev, avatar: url };
+ });
+ setSelectedAvatar(url);
message.success('头像已更新');
setAvatarSelectorOpen(false);
} catch (e: any) {
@@ -216,7 +286,7 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
};
const liveAgent = { ...(agent ?? {}), ...(form.getFieldsValue() as Agent) } as Agent;
- const currentName = liveAgent?.name || agentName || '未命名智能体';
+ const currentName = agent?.name || liveAgent?.name || agentName || undefined;
const markDirty = () => {
if (hydratingRef.current) return;
diff --git a/src/pages/AgentEditor/index.tsx b/src/pages/AgentEditor/index.tsx
index 18394e8..7c4c833 100644
--- a/src/pages/AgentEditor/index.tsx
+++ b/src/pages/AgentEditor/index.tsx
@@ -17,7 +17,6 @@ export default function AgentEditor() {
const navigate = useNavigate();
const { message } = AntApp.useApp();
const [form] = Form.useForm();
- const pollTimer = useRef(null);
const {
agent,
@@ -51,15 +50,6 @@ export default function AgentEditor() {
markDirty,
} = useAgentEditor({ id, isNew, form, message, navigate });
- useEffect(() => {
- return () => {
- if (pollTimer.current) {
- window.clearInterval(pollTimer.current);
- pollTimer.current = null;
- }
- };
- }, []);
-
return (
<>
diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx
index 4e38f61..ec052e8 100644
--- a/src/pages/ChatPage.tsx
+++ b/src/pages/ChatPage.tsx
@@ -75,7 +75,7 @@ export default function ChatPage() {
const autoScrollRef = useRef(true);
const initialScrollDoneRef = useRef(false);
const scrollRafRef = useRef
(null);
- const scrollProgrammaticRef = useRef(false);
+ const scrollProgrammaticAtRef = useRef(0);
const lastScrollTopRef = useRef(0);
const userScrollLockRef = useRef(false);
@@ -108,7 +108,7 @@ export default function ChatPage() {
scrollRafRef.current = null;
const el = bodyRef.current;
if (!el) return;
- scrollProgrammaticRef.current = true;
+ scrollProgrammaticAtRef.current = Date.now();
el.scrollTop = el.scrollHeight;
});
};
@@ -117,19 +117,25 @@ export default function ChatPage() {
const el = bodyRef.current;
if (!el) return;
const onScroll = () => {
- if (scrollProgrammaticRef.current) {
- scrollProgrammaticRef.current = false;
- lastScrollTopRef.current = el.scrollTop;
- } else {
- const nextTop = el.scrollTop;
- const lastTop = lastScrollTopRef.current;
- if (nextTop < lastTop - 2) {
+ const now = Date.now();
+ const isProgrammatic = now - scrollProgrammaticAtRef.current < 50;
+ const nextTop = el.scrollTop;
+ const lastTop = lastScrollTopRef.current;
+ const scrollHeight = el.scrollHeight;
+ const clientHeight = el.clientHeight;
+ const distance = scrollHeight - nextTop - clientHeight;
+
+ if (!isProgrammatic) {
+ const scrollDelta = nextTop - lastTop;
+ if (scrollDelta < -5) {
userScrollLockRef.current = true;
}
- lastScrollTopRef.current = nextTop;
}
- const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
- if (distance < 8) userScrollLockRef.current = false;
+ lastScrollTopRef.current = nextTop;
+
+ if (distance < 8) {
+ userScrollLockRef.current = false;
+ }
autoScrollRef.current = distance < 32 && !userScrollLockRef.current;
if ((!autoScrollRef.current || userScrollLockRef.current) && scrollRafRef.current) {
cancelAnimationFrame(scrollRafRef.current);
@@ -137,7 +143,7 @@ export default function ChatPage() {
}
};
el.addEventListener('scroll', onScroll, { passive: true });
- onScroll();
+ setTimeout(onScroll, 100);
return () => el.removeEventListener('scroll', onScroll);
}, []);
@@ -810,7 +816,7 @@ export default function ChatPage() {
{(streaming.retrieved.length > 0 || streaming.toolCalls.length > 0) && (
{streaming.retrieved.length > 0 && (
-
+
)}
{streaming.toolCalls.length > 0 && (
@@ -1324,12 +1330,11 @@ function ReasoningView({ reasoning }: { reasoning: string }) {
);
}
-function RetrievedView({ retrieved, liveStyle }: { retrieved: RetrievedSnippet[]; liveStyle?: boolean }) {
+function RetrievedView({ retrieved }: { retrieved: RetrievedSnippet[] }) {
return (