fix(router): route casual chat to DeepSeek and reserve Cursor for page tasks

Default unmatched conversation to direct_chat, stop blanket Agent session continuation and generic Agent→Cursor promotion; fix WeChat page retry userPublishDir scope and simplify ChatPanel controls.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-26 21:09:44 +08:00
parent 1ec971515b
commit 9ac9f96167
9 changed files with 853 additions and 215 deletions
+25 -103
View File
@@ -1,5 +1,5 @@
import { ChangeEvent, useCallback, useEffect, useLayoutEffect, useRef, useState, type ClipboardEvent, type MutableRefObject } from 'react';
import { BrainCircuit, Database, Image, ImageOff, ImagePlus } from 'lucide-react';
import { BrainCircuit } from 'lucide-react';
import { useNetworkStatus } from '../hooks/useNetworkStatus';
import { openAvatarPicker } from '../utils/userAvatar';
import {
@@ -11,11 +11,7 @@ import {
} from '../utils/chatSkills';
import { getMessageSaveActions } from '../utils/messageSave';
import { getDisplayText } from '../utils/message';
import {
IMAGE_GENERATION_MODE_LABELS,
nextImageGenerationMode,
type ImageGenerationMode,
} from '../utils/imageGeneration';
import type { ImageGenerationMode } from '../utils/imageGeneration';
import {
downloadChatMessageDocx,
} from '../api/client';
@@ -39,6 +35,7 @@ import { ChatSkillPicker } from './ChatSkillPicker';
import { ChatSkillIcon } from './ChatSkillIcons';
import { PageTemplateShopModal } from './PageTemplateShopModal';
import { ChatLoadingSpinner } from './ChatLoadingSpinner';
import { useTaskLoadingTip } from '../hooks/useTaskLoadingTip';
import { ChatSharePreviewModal } from './ChatSharePreviewModal';
import { ChatPlazaPublishModal } from './ChatPlazaPublishModal';
import { MessageList } from './MessageList';
@@ -183,6 +180,7 @@ export function ChatPanel({
onBalanceUpdate,
onGrantedSkillsUpdate,
onOpenRecharge,
taskLoadingTip = null,
}: {
variant: 'full' | 'compact';
user?: PortalUser | null;
@@ -231,6 +229,7 @@ export function ChatPanel({
onBalanceUpdate?: (balanceCents: number) => void;
onGrantedSkillsUpdate?: (skills: string[]) => void;
onOpenRecharge?: () => void;
taskLoadingTip?: string | null;
}) {
const online = useNetworkStatus();
const [input, setInput] = useState('');
@@ -240,12 +239,8 @@ export function ChatPanel({
const [plazaPublishSource, setPlazaPublishSource] = useState<Message | null>(null);
const [voiceNotice, setVoiceNotice] = useState<string | null>(null);
const [forceDeepReasoning, setForceDeepReasoning] = useState(false);
const [pgRequired, setPgRequired] = useState(false);
const [imageGenerationMode, setImageGenerationMode] = useState<ImageGenerationMode>('auto');
const [imageGenerationTipVisible, setImageGenerationTipVisible] = useState(false);
const [pgTipVisible, setPgTipVisible] = useState(false);
const [deepReasoningTipVisible, setDeepReasoningTipVisible] = useState(false);
const [chatControlOnboardingStep, setChatControlOnboardingStep] = useState<0 | 1 | 2 | null>(null);
const [chatControlOnboardingStep, setChatControlOnboardingStep] = useState<0 | 1 | null>(null);
const [templateShopOpen, setTemplateShopOpen] = useState(false);
const [activeTemplatePrefill, setActiveTemplatePrefill] = useState<{
skillId: string;
@@ -261,8 +256,6 @@ export function ChatPanel({
const [voiceStopSignal, setVoiceStopSignal] = useState(0);
const pendingSkillRef = useRef<string | null>(null);
const chatInputRef = useRef<HTMLTextAreaElement>(null);
const imageGenerationTipTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const pgTipTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const deepReasoningTipTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [randomPrompt] = useState(
() => CHAT_PLACEHOLDER_PROMPTS[Math.floor(Math.random() * CHAT_PLACEHOLDER_PROMPTS.length)],
@@ -282,6 +275,7 @@ export function ChatPanel({
}>(null);
const prevMessageCountRef = useRef(messages.length);
const showAssistantTyping = chatState === 'waiting' || chatState === 'streaming';
const displayLoadingTip = useTaskLoadingTip(showAssistantTyping, taskLoadingTip);
inputRef.current = input;
pendingImagesRef.current = pendingImages;
@@ -337,19 +331,12 @@ export function ChatPanel({
setChatControlOnboardingStep(0);
const timers = [
window.setTimeout(() => setChatControlOnboardingStep(1), 2_200),
window.setTimeout(() => setChatControlOnboardingStep(2), 4_400),
window.setTimeout(() => setChatControlOnboardingStep(null), 6_600),
window.setTimeout(() => setChatControlOnboardingStep(null), 4_400),
];
return () => timers.forEach((timer) => window.clearTimeout(timer));
}, [session?.id]);
useEffect(() => () => {
if (imageGenerationTipTimerRef.current) {
clearTimeout(imageGenerationTipTimerRef.current);
}
if (pgTipTimerRef.current) {
clearTimeout(pgTipTimerRef.current);
}
if (deepReasoningTipTimerRef.current) {
clearTimeout(deepReasoningTipTimerRef.current);
}
@@ -372,26 +359,6 @@ export function ChatPanel({
[],
);
const showImageGenerationSlowTip = useCallback(() => {
showTransientControlTip(setImageGenerationTipVisible, imageGenerationTipTimerRef);
}, [showTransientControlTip]);
const handlePgToggle = useCallback(() => {
setPgRequired((current) => {
const next = !current;
if (next) {
showTransientControlTip(setPgTipVisible, pgTipTimerRef);
} else {
setPgTipVisible(false);
if (pgTipTimerRef.current) {
clearTimeout(pgTipTimerRef.current);
pgTipTimerRef.current = null;
}
}
return next;
});
}, [showTransientControlTip]);
const handleDeepReasoningToggle = useCallback(() => {
setForceDeepReasoning((current) => {
const next = !current;
@@ -408,11 +375,6 @@ export function ChatPanel({
});
}, [showTransientControlTip]);
const handleImageGenerationClick = useCallback(() => {
setImageGenerationMode((current) => nextImageGenerationMode(current));
showImageGenerationSlowTip();
}, [showImageGenerationSlowTip]);
useLayoutEffect(() => {
const container = mainRef.current;
if (!container) return;
@@ -876,8 +838,8 @@ export function ChatPanel({
await onSubmit(trimmed, imagesToSend, previewImagesToSend, {
messageId: outgoingMessageId,
forceDeepReasoning,
pgRequired,
imageGenerationMode,
pgRequired: false,
imageGenerationMode: 'auto',
selectedChatSkill,
fileAttachments: fileAttachmentsToSend,
});
@@ -907,8 +869,6 @@ export function ChatPanel({
}, [
activeTemplatePrefill,
forceDeepReasoning,
pgRequired,
imageGenerationMode,
input,
onSubmit,
onUploadFile,
@@ -1118,6 +1078,7 @@ export function ChatPanel({
<MessageList
messages={messages}
streaming={showAssistantTyping}
typingTip={displayLoadingTip}
onAvatarClick={compact ? undefined : openAvatarPicker}
onSaveAsPage={openSaveActions}
onShareToPlaza={openPlazaPublish}
@@ -1323,14 +1284,19 @@ export function ChatPanel({
className={`input chat-input-field chat-input-task-status${uploadFieldClass}`}
role="status"
aria-live="polite"
aria-label={taskInputStatusText}
aria-label={[taskInputStatusText, displayLoadingTip].filter(Boolean).join(' ')}
>
<span className="chat-input-task-status-text">{taskInputStatusText}</span>
<span className="typing-dots chat-input-task-status-dots" aria-hidden="true">
<span />
<span />
<span />
</span>
<div className="chat-input-task-status-main">
<span className="chat-input-task-status-text">{taskInputStatusText}</span>
<span className="typing-dots chat-input-task-status-dots" aria-hidden="true">
<span />
<span />
<span />
</span>
</div>
{displayLoadingTip ? (
<span className="chat-input-task-status-tip">{displayLoadingTip}</span>
) : null}
</div>
) : (
<textarea
@@ -1396,51 +1362,7 @@ export function ChatPanel({
)}
<button
type="button"
className={`chat-deep-reasoning-toggle chat-icon-toggle${pgRequired ? ' is-active' : ''}${pgTipVisible ? ' chat-icon-toggle-tip-active' : ''}${chatControlOnboardingStep === 1 ? ' chat-control-onboarding-active' : ''}`}
disabled={voiceDisabled}
role="checkbox"
aria-checked={pgRequired}
aria-label="使用 PostgreSQL 数据空间"
title="点击开启后,本轮生成的可保存数据必须使用你的专属 PostgreSQL 数据空间"
onClick={handlePgToggle}
>
<Database className="chat-deep-reasoning-toggle-icon" aria-hidden="true" />
<span className="chat-icon-toggle-label">db</span>
{pgTipVisible && (
<span className="chat-control-onboarding-tip chat-icon-toggle-tip" role="status">
PostgreSQL
</span>
)}
{chatControlOnboardingStep === 1 && (
<span className="chat-control-onboarding-tip" role="status">使</span>
)}
</button>
<button
type="button"
className={`chat-deep-reasoning-toggle chat-icon-toggle chat-image-generation-toggle is-${imageGenerationMode}${imageGenerationMode === 'required' ? ' is-active' : ''}${imageGenerationTipVisible ? ' chat-icon-toggle-tip-active' : ''}`}
disabled={voiceDisabled}
role="checkbox"
aria-checked={imageGenerationMode === 'auto' ? 'mixed' : imageGenerationMode === 'required'}
aria-label={`AI 配图:${IMAGE_GENERATION_MODE_LABELS[imageGenerationMode]}`}
title={`AI 配图:${IMAGE_GENERATION_MODE_LABELS[imageGenerationMode]}。启用后会加载智能生图 skill,自动补全场景、构图、光线和负面约束。点击切换为${IMAGE_GENERATION_MODE_LABELS[nextImageGenerationMode(imageGenerationMode)]}`}
onClick={handleImageGenerationClick}
>
{imageGenerationMode === 'required' ? (
<ImagePlus className="chat-deep-reasoning-toggle-icon" aria-hidden="true" />
) : imageGenerationMode === 'disabled' ? (
<ImageOff className="chat-deep-reasoning-toggle-icon" aria-hidden="true" />
) : (
<Image className="chat-deep-reasoning-toggle-icon" aria-hidden="true" />
)}
{imageGenerationTipVisible && (
<span className="chat-control-onboarding-tip chat-icon-toggle-tip" role="status">
</span>
)}
</button>
<button
type="button"
className={`chat-deep-reasoning-toggle chat-icon-toggle${forceDeepReasoning ? ' is-active' : ''}${deepReasoningTipVisible ? ' chat-icon-toggle-tip-active' : ''}${chatControlOnboardingStep === 2 ? ' chat-control-onboarding-active' : ''}`}
className={`chat-deep-reasoning-toggle chat-icon-toggle${forceDeepReasoning ? ' is-active' : ''}${deepReasoningTipVisible ? ' chat-icon-toggle-tip-active' : ''}${chatControlOnboardingStep === 1 ? ' chat-control-onboarding-active' : ''}`}
disabled={voiceDisabled}
role="checkbox"
aria-checked={forceDeepReasoning}
@@ -1455,7 +1377,7 @@ export function ChatPanel({
</span>
)}
{chatControlOnboardingStep === 2 && (
{chatControlOnboardingStep === 1 && (
<span className="chat-control-onboarding-tip" role="status"></span>
)}
</button>