feat(mindspace): gate published page delivery

This commit is contained in:
john
2026-07-14 19:13:01 +08:00
parent ae9090948a
commit 27b11894f2
16 changed files with 448 additions and 39 deletions
+49 -17
View File
@@ -1,4 +1,5 @@
import { ChangeEvent, useCallback, useEffect, useLayoutEffect, useRef, useState, type ClipboardEvent } from 'react';
import { BrainCircuit, Database } from 'lucide-react';
import { useNetworkStatus } from '../hooks/useNetworkStatus';
import { openAvatarPicker } from '../utils/userAvatar';
import { CHAT_SKILL_OPTIONS, filterChatSkills } from '../utils/chatSkills';
@@ -184,6 +185,7 @@ export function ChatPanel({
options?: {
messageId?: string;
forceDeepReasoning?: boolean;
pgRequired?: boolean;
selectedChatSkill?: string;
fileAttachments?: ChatFileAttachment[];
},
@@ -216,6 +218,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 [chatControlOnboardingStep, setChatControlOnboardingStep] = useState<0 | 1 | 2 | null>(null);
const [pendingImages, setPendingImages] = useState<PendingChatImage[]>([]);
const [pendingFiles, setPendingFiles] = useState<PendingChatFile[]>([]);
const [uploadingImage, setUploadingImage] = useState(false);
@@ -293,6 +297,16 @@ export function ChatPanel({
nearBottomRef.current = true;
}, [session?.id]);
useEffect(() => {
setChatControlOnboardingStep(0);
const timers = [
window.setTimeout(() => setChatControlOnboardingStep(1), 2_200),
window.setTimeout(() => setChatControlOnboardingStep(2), 4_400),
window.setTimeout(() => setChatControlOnboardingStep(null), 6_600),
];
return () => timers.forEach((timer) => window.clearTimeout(timer));
}, [session?.id]);
useLayoutEffect(() => {
const container = mainRef.current;
if (!container) return;
@@ -730,6 +744,7 @@ export function ChatPanel({
await onSubmit(trimmed, imagesToSend, previewImagesToSend, {
messageId: outgoingMessageId,
forceDeepReasoning,
pgRequired,
selectedChatSkill,
fileAttachments: fileAttachmentsToSend,
});
@@ -758,6 +773,7 @@ export function ChatPanel({
suppressVoiceUpdateRef.current = false;
}, [
forceDeepReasoning,
pgRequired,
input,
onSubmit,
onUploadFile,
@@ -1189,6 +1205,7 @@ export function ChatPanel({
<ChatSkillPicker
skills={chatSkills}
disabled={voiceDisabled}
onboardingActive={chatControlOnboardingStep === 0}
onSelect={submitText}
onPrefill={(prompt, skillId) => {
pendingSkillRef.current = skillId ?? null;
@@ -1196,23 +1213,38 @@ export function ChatPanel({
}}
/>
)}
{!showHomeWelcome && (
<label
className={`chat-deep-reasoning-toggle${forceDeepReasoning ? ' is-active' : ''}`}
title="勾选后,本轮消息使用深度推理完成"
>
<input
type="checkbox"
checked={forceDeepReasoning}
disabled={voiceDisabled}
onChange={(event) => setForceDeepReasoning(event.target.checked)}
/>
<span className="chat-deep-reasoning-toggle-label" aria-hidden="true">
<span></span>
<span></span>
</span>
</label>
)}
<label
className={`chat-deep-reasoning-toggle${pgRequired ? ' is-active' : ''}${chatControlOnboardingStep === 1 ? ' chat-control-onboarding-active' : ''}`}
title="勾选后,本轮生成的可保存数据必须使用你的专属 PostgreSQL 数据空间"
>
<input
type="checkbox"
checked={pgRequired}
disabled={voiceDisabled}
aria-label="使用 PostgreSQL 数据空间"
onChange={(event) => setPgRequired(event.target.checked)}
/>
<Database className="chat-deep-reasoning-toggle-icon" aria-hidden="true" />
{chatControlOnboardingStep === 1 && (
<span className="chat-control-onboarding-tip" role="status">使</span>
)}
</label>
<label
className={`chat-deep-reasoning-toggle${forceDeepReasoning ? ' is-active' : ''}${chatControlOnboardingStep === 2 ? ' chat-control-onboarding-active' : ''}`}
title="勾选后,本轮消息使用深度推理完成"
>
<input
type="checkbox"
checked={forceDeepReasoning}
disabled={voiceDisabled}
aria-label="使用深度推理"
onChange={(event) => setForceDeepReasoning(event.target.checked)}
/>
<BrainCircuit className="chat-deep-reasoning-toggle-icon" aria-hidden="true" />
{chatControlOnboardingStep === 2 && (
<span className="chat-control-onboarding-tip" role="status"></span>
)}
</label>
{chatState === 'streaming' ? (
<button type="button" className="danger-btn chat-input-action" onClick={() => void onStop()}>
+6 -1
View File
@@ -5,11 +5,13 @@ import { ChatSkillIcon } from './ChatSkillIcons';
export function ChatSkillPicker({
skills,
disabled,
onboardingActive = false,
onSelect,
onPrefill,
}: {
skills: ChatSkillOption[];
disabled?: boolean;
onboardingActive?: boolean;
onSelect: (prompt: string, skillId?: string) => void | Promise<void>;
onPrefill?: (prompt: string, skillId?: string) => void;
}) {
@@ -50,7 +52,7 @@ export function ChatSkillPicker({
<div className={`chat-skill-picker${open ? ' open' : ''}`} ref={wrapRef}>
<button
type="button"
className="chat-skill-trigger"
className={`chat-skill-trigger${onboardingActive ? ' chat-control-onboarding-active' : ''}`}
disabled={disabled}
aria-expanded={open}
aria-haspopup="menu"
@@ -59,6 +61,9 @@ export function ChatSkillPicker({
>
<ChatSkillIcon id="spark" className="chat-skill-trigger-icon" />
<span className="chat-skill-trigger-label">skill</span>
{onboardingActive && (
<span className="chat-control-onboarding-tip" role="status"> Skill</span>
)}
</button>
{open && (
+1
View File
@@ -547,6 +547,7 @@ export function ChatView({
{
messageId: options?.messageId,
forceDeepReasoning: options?.forceDeepReasoning,
pgRequired: options?.pgRequired,
selectedChatSkill: options?.selectedChatSkill,
fileAttachments: options?.fileAttachments,
},
+2
View File
@@ -147,6 +147,7 @@ export function SpaceChatPanel({
...context,
messageId: options?.messageId,
forceDeepReasoning: options?.forceDeepReasoning,
pgRequired: options?.pgRequired,
fileAttachments: options?.fileAttachments,
},
imageUrls,
@@ -158,6 +159,7 @@ export function SpaceChatPanel({
mindspaceContext: context,
messageId: options?.messageId,
forceDeepReasoning: options?.forceDeepReasoning,
pgRequired: options?.pgRequired,
fileAttachments: options?.fileAttachments,
},
imageUrls,