feat: add guarded image generation controls

This commit is contained in:
john
2026-07-20 07:37:43 +08:00
parent 72ed4dde83
commit 105a72c1f1
19 changed files with 482 additions and 23 deletions
+31 -1
View File
@@ -1,10 +1,15 @@
import { ChangeEvent, useCallback, useEffect, useLayoutEffect, useRef, useState, type ClipboardEvent } from 'react';
import { BrainCircuit, Database } from 'lucide-react';
import { BrainCircuit, Database, Image, ImageOff, ImagePlus } from 'lucide-react';
import { useNetworkStatus } from '../hooks/useNetworkStatus';
import { openAvatarPicker } from '../utils/userAvatar';
import { CHAT_SKILL_OPTIONS, filterChatSkills } 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 {
downloadChatMessageDocx,
} from '../api/client';
@@ -186,6 +191,7 @@ export function ChatPanel({
messageId?: string;
forceDeepReasoning?: boolean;
pgRequired?: boolean;
imageGenerationMode?: ImageGenerationMode;
selectedChatSkill?: string;
fileAttachments?: ChatFileAttachment[];
},
@@ -219,6 +225,7 @@ export function ChatPanel({
const [voiceNotice, setVoiceNotice] = useState<string | null>(null);
const [forceDeepReasoning, setForceDeepReasoning] = useState(false);
const [pgRequired, setPgRequired] = useState(false);
const [imageGenerationMode, setImageGenerationMode] = useState<ImageGenerationMode>('auto');
const [chatControlOnboardingStep, setChatControlOnboardingStep] = useState<0 | 1 | 2 | null>(null);
const [pendingImages, setPendingImages] = useState<PendingChatImage[]>([]);
const [pendingFiles, setPendingFiles] = useState<PendingChatFile[]>([]);
@@ -745,6 +752,7 @@ export function ChatPanel({
messageId: outgoingMessageId,
forceDeepReasoning,
pgRequired,
imageGenerationMode,
selectedChatSkill,
fileAttachments: fileAttachmentsToSend,
});
@@ -774,6 +782,7 @@ export function ChatPanel({
}, [
forceDeepReasoning,
pgRequired,
imageGenerationMode,
input,
onSubmit,
onUploadFile,
@@ -1229,6 +1238,27 @@ export function ChatPanel({
<span className="chat-control-onboarding-tip" role="status">使</span>
)}
</label>
<button
type="button"
className={`chat-deep-reasoning-toggle chat-image-generation-toggle is-${imageGenerationMode}${imageGenerationMode === 'required' ? ' is-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]}。点击切换为${IMAGE_GENERATION_MODE_LABELS[nextImageGenerationMode(imageGenerationMode)]}`}
onClick={() => setImageGenerationMode((current) => nextImageGenerationMode(current))}
>
{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" />
)}
<span className="chat-image-generation-state" aria-hidden="true">
{imageGenerationMode === 'auto' ? 'A' : imageGenerationMode === 'required' ? '✓' : '×'}
</span>
</button>
<label
className={`chat-deep-reasoning-toggle${forceDeepReasoning ? ' is-active' : ''}${chatControlOnboardingStep === 2 ? ' chat-control-onboarding-active' : ''}`}
title="勾选后,本轮消息使用深度推理完成"
+1
View File
@@ -548,6 +548,7 @@ export function ChatView({
messageId: options?.messageId,
forceDeepReasoning: options?.forceDeepReasoning,
pgRequired: options?.pgRequired,
imageGenerationMode: options?.imageGenerationMode,
selectedChatSkill: options?.selectedChatSkill,
fileAttachments: options?.fileAttachments,
},
+2
View File
@@ -148,6 +148,7 @@ export function SpaceChatPanel({
messageId: options?.messageId,
forceDeepReasoning: options?.forceDeepReasoning,
pgRequired: options?.pgRequired,
imageGenerationMode: options?.imageGenerationMode,
fileAttachments: options?.fileAttachments,
},
imageUrls,
@@ -160,6 +161,7 @@ export function SpaceChatPanel({
messageId: options?.messageId,
forceDeepReasoning: options?.forceDeepReasoning,
pgRequired: options?.pgRequired,
imageGenerationMode: options?.imageGenerationMode,
fileAttachments: options?.fileAttachments,
},
imageUrls,