Compare commits

..

No commits in common. "08e8de5e3e180718ecfa725862bc6e0950fba24f" and "1fd7a5d4f703aa47ecc588f8323df5399458245d" have entirely different histories.

8 changed files with 40 additions and 109 deletions

View File

@ -81,23 +81,16 @@ export default function ChatPage() {
() => isNewThread && !safeThreadId, () => isNewThread && !safeThreadId,
[isNewThread, safeThreadId], [isNewThread, safeThreadId],
); );
const [isThreadInitReady, setIsThreadInitReady] = useState(false);
const streamThreadId = useMemo(() => { const streamThreadId = useMemo(() => {
if (!safeThreadId) { if (isNewThread && createNewSession) {
return undefined;
}
// In /new flow, defer history loading until thread init is finished:
// delete -> create -> history.
if (isNewThread && !isThreadInitReady) {
return undefined; return undefined;
} }
return safeThreadId; return safeThreadId;
}, [isNewThread, isThreadInitReady, safeThreadId]); }, [createNewSession, isNewThread, safeThreadId]);
const apiClient = useMemo(() => getAPIClient(isMock), [isMock]); const apiClient = useMemo(() => getAPIClient(isMock), [isMock]);
const warnedMissingThreadIdRef = useRef(false); const warnedMissingThreadIdRef = useRef(false);
const initializedThreadRef = useRef<string | null>(null); const initializedThreadRef = useRef<string | null>(null);
const threadInitPromiseRef = useRef<Promise<void> | null>(null);
const { showNotification } = useNotification(); const { showNotification } = useNotification();
const currentSlogan = motivationSlogans[ const currentSlogan = motivationSlogans[
@ -137,7 +130,6 @@ export default function ChatPage() {
useEffect(() => { useEffect(() => {
if (!isNewThread) { if (!isNewThread) {
warnedMissingThreadIdRef.current = false; warnedMissingThreadIdRef.current = false;
setIsThreadInitReady(true);
return; return;
} }
if (!safeThreadId) { if (!safeThreadId) {
@ -145,38 +137,29 @@ export default function ChatPage() {
warnedMissingThreadIdRef.current = true; warnedMissingThreadIdRef.current = true;
toast.error(t.chatPage.missingThreadIdForCreate); toast.error(t.chatPage.missingThreadIdForCreate);
} }
setIsThreadInitReady(false);
return; return;
} }
warnedMissingThreadIdRef.current = false; warnedMissingThreadIdRef.current = false;
if (initializedThreadRef.current === safeThreadId) return; if (initializedThreadRef.current === safeThreadId) return;
initializedThreadRef.current = safeThreadId; initializedThreadRef.current = safeThreadId;
setIsThreadInitReady(false); void apiClient.threads
// TODO: 先注释先删除再创建的逻辑
const initPromise = apiClient.threads // .delete(safeThreadId)
.delete(safeThreadId) // .catch(() => undefined)
.catch(() => undefined) // .then(() =>
.then(() => // apiClient.threads.create({
apiClient.threads.create({ // threadId: safeThreadId,
threadId: safeThreadId, // ifExists: "raise",
ifExists: "do_nothing", // }),
}), // )
) .create({
.then(() => { threadId: safeThreadId,
setIsThreadInitReady(true); ifExists: "do_nothing",
}) })
.catch(() => { .catch(() => {
initializedThreadRef.current = null; initializedThreadRef.current = null;
setIsThreadInitReady(false);
toast.error(t.chatPage.createSessionFailed); toast.error(t.chatPage.createSessionFailed);
}); });
threadInitPromiseRef.current = initPromise;
void initPromise.finally(() => {
if (threadInitPromiseRef.current === initPromise) {
threadInitPromiseRef.current = null;
}
});
}, [ }, [
apiClient, apiClient,
isNewThread, isNewThread,
@ -308,7 +291,7 @@ export default function ChatPage() {
const [showExitDialog, setShowExitDialog] = useState(false); const [showExitDialog, setShowExitDialog] = useState(false);
const isStreaming = isUploading || thread.isLoading; const isStreaming = isUploading || thread.isLoading;
const handleSubmit = useCallback( const handleSubmit = useCallback(
async (message: Parameters<typeof sendMessage>[1]) => { (message: Parameters<typeof sendMessage>[1]) => {
if (isSelectedSkillBootstrapping) { if (isSelectedSkillBootstrapping) {
return; return;
} }
@ -316,12 +299,6 @@ export default function ChatPage() {
toast.error(t.chatPage.missingThreadIdForSend); toast.error(t.chatPage.missingThreadIdForSend);
return; return;
} }
if (isNewThread && safeThreadId) {
await threadInitPromiseRef.current;
}
if (isNewThread && safeThreadId && !isThreadInitReady) {
return;
}
setHasSubmitted(true); setHasSubmitted(true);
if (safeThreadId && (isNewThread || showWelcomeStyle)) { if (safeThreadId && (isNewThread || showWelcomeStyle)) {
router.replace(`/workspace/chats/${safeThreadId}?is_chatting=true`); router.replace(`/workspace/chats/${safeThreadId}?is_chatting=true`);
@ -330,7 +307,6 @@ export default function ChatPage() {
}, },
[ [
isNewThread, isNewThread,
isThreadInitReady,
isSelectedSkillBootstrapping, isSelectedSkillBootstrapping,
router, router,
safeThreadId, safeThreadId,
@ -657,14 +633,14 @@ export default function ChatPage() {
type: POST_MESSAGE_TYPES.IS_CHATTING, type: POST_MESSAGE_TYPES.IS_CHATTING,
isChatting: false, isChatting: false,
}); });
resetNewSessionState();
// 始终复用 query 中的 thread_id。 // 始终复用 query 中的 thread_id。
const nextQuery = new URLSearchParams(); const nextQuery = new URLSearchParams();
if (threadId && threadId !== "new") { if (threadId && threadId !== "new") {
nextQuery.set("thread_id", threadId); nextQuery.set("thread_id", threadId);
} }
// /workspace/chats/${threadId}?is_chatting=false
router.replace( router.replace(
`/workspace/chats/new?thread_id=${threadId}`, `/workspace/chats/${threadId}?is_chatting=false`,
); );
}} }}
> >

View File

@ -970,14 +970,6 @@ export function InputBox({
/> />
</div> </div>
)} )}
{!showWelcomeStyle && (
<div className="shrink-0 h-full">
<ExitChattingButton
router={router}
threadId={threadIdFromProps}
/>
</div>
)}
<div ref={attachmentsButtonTourRef} className="shrink-0 h-full"> <div ref={attachmentsButtonTourRef} className="shrink-0 h-full">
<AddAttachmentsButton /> <AddAttachmentsButton />
</div> </div>
@ -1300,53 +1292,6 @@ function HistoryButton({
</Tooltip> </Tooltip>
); );
} }
function ExitChattingButton({
className,
router,
threadId,
}: {
className?: string;
router: AppRouterInstance;
threadId: string;
}) {
const { t } = useI18n();
return (
<Tooltip content={t.inputBox.welcome}>
<WorkspaceToolButton
className={cn(
"text-ws-base-1 hover:text-ws-interactive-primary",
className,
)}
onClick={() =>
router.replace(`/workspace/chats/${threadId}?is_chatting=false`)
}
>
<svg
className="transition-[color] duration-200"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
className="stroke-current transition-[stroke] duration-200"
cx="9"
cy="9"
r="8.5"
/>
<path
className="stroke-current transition-[stroke] duration-200"
d="M6 9H12"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</WorkspaceToolButton>
</Tooltip>
);
}
// 启动iframeSkillDialog // 启动iframeSkillDialog
function IframeSkillDialogButton({ function IframeSkillDialogButton({
className, className,

View File

@ -39,6 +39,7 @@ import {
} from "@/core/messages/utils"; } from "@/core/messages/utils";
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype"; import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
import { materializeSkillYaml } from "@/core/skills"; import { materializeSkillYaml } from "@/core/skills";
import { humanMessagePlugins } from "@/core/streamdown";
import { dispatchMentionReference } from "@/core/threads/reference-events"; import { dispatchMentionReference } from "@/core/threads/reference-events";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -224,9 +225,13 @@ function MessageContent_({
if (isHuman) { if (isHuman) {
const shouldRenderSummaryCollapse = isSummaryMessage && summaryBody; const shouldRenderSummaryCollapse = isSummaryMessage && summaryBody;
const messageResponse = contentToDisplay ? ( const messageResponse = contentToDisplay ? (
<div className="whitespace-break-spaces break-words"> <AIElementMessageResponse
remarkPlugins={humanMessagePlugins.remarkPlugins}
rehypePlugins={humanMessagePlugins.rehypePlugins}
components={components}
>
{contentToDisplay} {contentToDisplay}
</div> </AIElementMessageResponse>
) : null; ) : null;
return ( return (
<div className={cn("ml-auto flex flex-col gap-2", className)}> <div className={cn("ml-auto flex flex-col gap-2", className)}>
@ -245,9 +250,13 @@ function MessageContent_({
: t.toolCalls.expandContent} : t.toolCalls.expandContent}
</summary> </summary>
<AIElementMessageContent className="w-fit border-t"> <AIElementMessageContent className="w-fit border-t">
<div className="whitespace-break-spaces break-words"> <AIElementMessageResponse
remarkPlugins={humanMessagePlugins.remarkPlugins}
rehypePlugins={humanMessagePlugins.rehypePlugins}
components={components}
>
{summaryBody} {summaryBody}
</div> </AIElementMessageResponse>
</AIElementMessageContent> </AIElementMessageContent>
</details> </details>
)} )}

View File

@ -32,6 +32,7 @@ export function useReferenceFiles(threadId: string | undefined) {
queryKey: ["references", "list", threadId], queryKey: ["references", "list", threadId],
queryFn: () => listReferenceFiles(threadId ?? ""), queryFn: () => listReferenceFiles(threadId ?? ""),
enabled: Boolean(threadId), enabled: Boolean(threadId),
refetchOnWindowFocus: false, refetchInterval: 5000,
refetchOnWindowFocus: true,
}); });
} }

View File

@ -86,7 +86,6 @@ export const enUS: Translations = {
"Please note, this feature will consume tokens. Ensure your account balance is greater than 200 credits.", "Please note, this feature will consume tokens. Ensure your account balance is greater than 200 credits.",
addAttachments: "Add attachments", addAttachments: "Add attachments",
history: "History", history: "History",
welcome:"Welcome",
selectSkill: "Select Skill", selectSkill: "Select Skill",
mode: "Mode", mode: "Mode",
flashMode: "Flash", flashMode: "Flash",

View File

@ -75,7 +75,6 @@ export interface Translations {
createSkillPrompt: string; createSkillPrompt: string;
addAttachments: string; addAttachments: string;
history: string; history: string;
welcome:string;
selectSkill: string; selectSkill: string;
mode: string; mode: string;
flashMode: string; flashMode: string;

View File

@ -87,7 +87,6 @@ export const zhCN: Translations = {
"请注意此功能将消耗token请保证账户余额大于200可学豆。", "请注意此功能将消耗token请保证账户余额大于200可学豆。",
addAttachments: "添加附件", addAttachments: "添加附件",
history: "历史记录", history: "历史记录",
welcome:"欢迎页",
selectSkill: "选择Skill", selectSkill: "选择Skill",
mode: "模式", mode: "模式",
flashMode: "闪速", flashMode: "闪速",
@ -263,7 +262,7 @@ export const zhCN: Translations = {
noArtifactSelectedDescription: "请选择一个生成文件以查看详情", noArtifactSelectedDescription: "请选择一个生成文件以查看详情",
exitDialogTitle: "提示", exitDialogTitle: "提示",
exitDialogDescription: exitDialogDescription:
"每七天自动删除。现在将返回欢迎页且清空聊天消息,是否继续?", "历史记录每七天自动删除,现在将返回欢迎页,是否继续?",
exitDialogConfirm: "确定", exitDialogConfirm: "确定",
selectedSkillLoadFailed: "技能加载失败", selectedSkillLoadFailed: "技能加载失败",
unknownErrorRetry: "发生了未知错误,请稍后重试。", unknownErrorRetry: "发生了未知错误,请稍后重试。",

View File

@ -414,9 +414,12 @@ export function stripPriorityHintSuffix(content: string): string {
* - Split Chinese-numbered items (e.g. "1...") into separate paragraphs. * - Split Chinese-numbered items (e.g. "1...") into separate paragraphs.
*/ */
export function normalizeHumanMessageDisplayText(content: string): string { export function normalizeHumanMessageDisplayText(content: string): string {
// Preserve human input as-is for display; only decode escaped newlines return content
// and normalize CRLF/CR to LF so line breaks render consistently. .replace(/\\n/g, "\n")
return content.replace(/\\n/g, "\n").replace(/\r\n?/g, "\n"); .replace(/\r\n?/g, "\n")
.replace(/\n(?=\d+[)]\s*)/g, "\n\n")
.replace(/\n{3,}/g, "\n\n")
.trim();
} }
export function parseUploadedFiles(content: string): FileInMessage[] { export function parseUploadedFiles(content: string): FileInMessage[] {