新增智能体收藏、定时任务必须生成MD校验、问答时间显示及账号绑定的常用提示词

- 智能体支持从广场收藏到「我的」(新增 agent_favorites 表与收藏/取消收藏接口)
- 定时任务新增「必须生成Markdown文件」开关:注入提示词并校验生成的 .md 文件且正文超过100字,否则记为失败(存于 execution_context,无需迁移)
- 聊天问答展示时间(年月日 时分):问题在右下角、回答在左下角
- 常用提示词改为与账号绑定(新增 user_custom_prompts 表与 /api/user-prompts 接口),仅在首页输入框上方以居中标签 + 轮播展示,齿轮置于最右侧管理

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.7 committed 2026-05-28 17:16:15 +08:00
1 parent 4410acaee8
commit 10e35055e0
45 files changed
+1653 -145

No files matched your search

+48 -1
View File
@@ -11,6 +11,7 @@ import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
import { getAPIClient } from "../api";
import { apiFetch } from "../api";
import { getStoredAuth } from "../auth";
import { rememberAskTime } from "../chat-timestamps";
import { getBackendBaseURL } from "../config";
import { useI18n } from "../i18n/hooks";
import type { FileInMessage } from "../messages/utils";
@@ -378,6 +379,13 @@ export function useThreadStream({
const [optimisticMessages, setOptimisticMessages] = useState<Message[]>([]);
const [isUploading, setIsUploading] = useState(false);
const sendInFlightRef = useRef(false);
// Carries the ask time of an in-flight send until the server-assigned human
// message id shows up, so we can bind the timestamp to the real id (see the
// reconciliation effect below). ``known`` is the set of human ids that
// already existed at send time, used to spot the newly-arrived one.
const pendingAskTimeRef = useRef<{ ts: string; known: Set<string> } | null>(
null,
);
// Track message count before sending so we know when server has responded
const prevMsgCountRef = useRef(thread.messages.length);
@@ -429,6 +437,26 @@ export function useThreadStream({
}
}, [thread.messages.length, optimisticMessages.length]);
// Bind a just-sent question's ask time to the server-assigned human message
// id once it appears. Persists to localStorage so the time shows on every
// question and survives reloads, independent of the backend echoing it back.
useEffect(() => {
const pending = pendingAskTimeRef.current;
if (!pending) return;
for (const message of thread.messages) {
if (
message.type === "human" &&
message.id &&
!message.id.startsWith("opt-") &&
!pending.known.has(message.id)
) {
rememberAskTime(message.id, pending.ts);
pendingAskTimeRef.current = null;
break;
}
}
}, [thread.messages]);
const sendMessage = useCallback(
async (
threadId: string,
@@ -442,6 +470,20 @@ export function useThreadStream({
sendInFlightRef.current = true;
const text = message.text.trim();
// Wall-clock time the question was asked. Stored on the human message so
// the chat can show "几点提问" and have it survive a reload (it round-trips
// through the checkpoint inside additional_kwargs, same as `files`).
const clientTs = new Date().toISOString();
// Remember which human messages already exist so the reconciliation
// effect can bind ``clientTs`` to the new one once the server returns it.
pendingAskTimeRef.current = {
ts: clientTs,
known: new Set(
thread.messages
.filter((m) => m.type === "human" && m.id)
.map((m) => m.id as string),
),
};
// Capture current count before showing optimistic messages
prevMsgCountRef.current = thread.messages.length;
@@ -458,6 +500,7 @@ export function useThreadStream({
const hideFromUI = options?.additionalKwargs?.hide_from_ui === true;
const optimisticAdditionalKwargs = {
...options?.additionalKwargs,
client_ts: clientTs,
...(optimisticFiles.length > 0 ? { files: optimisticFiles } : {}),
};
@@ -535,7 +578,10 @@ export function useThreadStream({
return [
{
...humanMessage,
additional_kwargs: { files: uploadedFiles },
additional_kwargs: {
...humanMessage.additional_kwargs,
files: uploadedFiles,
},
},
...messages.slice(1),
];
@@ -585,6 +631,7 @@ export function useThreadStream({
],
additional_kwargs: {
...options?.additionalKwargs,
client_ts: clientTs,
...(filesForSubmit.length > 0
? { files: filesForSubmit }
: {}),