Enhance DebugDrawer and DebugVoicePanel with unsaved changes handling
- Add `hasUnsavedChanges` prop to `DebugDrawer` to indicate unsaved changes in the AssistantPage. - Update `DebugVoicePanel` to display appropriate messages and disable actions when there are unsaved changes. - Refactor related components to ensure consistent handling of unsaved changes across the AssistantPage, improving user experience during interactions.
This commit is contained in:
@@ -1302,6 +1302,7 @@ export function AssistantPage(props: AssistantPageProps) {
|
|||||||
<DebugDrawer
|
<DebugDrawer
|
||||||
assistantId={editingId}
|
assistantId={editingId}
|
||||||
asSheet
|
asSheet
|
||||||
|
hasUnsavedChanges={dirty}
|
||||||
onNodeActive={setActiveNodeId}
|
onNodeActive={setActiveNodeId}
|
||||||
/>
|
/>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
@@ -1403,7 +1404,7 @@ export function AssistantPage(props: AssistantPageProps) {
|
|||||||
</SectionCard>
|
</SectionCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebugDrawer assistantId={editingId} />
|
<DebugDrawer assistantId={editingId} hasUnsavedChanges={dirty} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1517,7 +1518,7 @@ export function AssistantPage(props: AssistantPageProps) {
|
|||||||
</SectionCard>
|
</SectionCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebugDrawer assistantId={editingId} />
|
<DebugDrawer assistantId={editingId} hasUnsavedChanges={dirty} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1642,7 +1643,7 @@ export function AssistantPage(props: AssistantPageProps) {
|
|||||||
</SectionCard>
|
</SectionCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebugDrawer assistantId={editingId} />
|
<DebugDrawer assistantId={editingId} hasUnsavedChanges={dirty} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1859,7 +1860,11 @@ export function AssistantPage(props: AssistantPageProps) {
|
|||||||
</SectionCard>
|
</SectionCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebugDrawer assistantId={editingId} vision={visionEnabled} />
|
<DebugDrawer
|
||||||
|
assistantId={editingId}
|
||||||
|
hasUnsavedChanges={dirty}
|
||||||
|
vision={visionEnabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1934,11 +1939,13 @@ function SegmentedIconButton({
|
|||||||
function DebugDrawer({
|
function DebugDrawer({
|
||||||
assistantId,
|
assistantId,
|
||||||
asSheet = false,
|
asSheet = false,
|
||||||
|
hasUnsavedChanges = false,
|
||||||
onNodeActive,
|
onNodeActive,
|
||||||
vision = false,
|
vision = false,
|
||||||
}: {
|
}: {
|
||||||
assistantId: string | null;
|
assistantId: string | null;
|
||||||
asSheet?: boolean;
|
asSheet?: boolean;
|
||||||
|
hasUnsavedChanges?: boolean;
|
||||||
onNodeActive?: (nodeId: string | null) => void;
|
onNodeActive?: (nodeId: string | null) => void;
|
||||||
vision?: boolean;
|
vision?: boolean;
|
||||||
}) {
|
}) {
|
||||||
@@ -2050,6 +2057,7 @@ function DebugDrawer({
|
|||||||
assistantId={assistantId}
|
assistantId={assistantId}
|
||||||
preview={preview}
|
preview={preview}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
|
hasUnsavedChanges={hasUnsavedChanges}
|
||||||
vision={vision}
|
vision={vision}
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -2180,6 +2188,7 @@ function DebugVoicePanel({
|
|||||||
assistantId,
|
assistantId,
|
||||||
preview,
|
preview,
|
||||||
camera,
|
camera,
|
||||||
|
hasUnsavedChanges,
|
||||||
vision,
|
vision,
|
||||||
}: {
|
}: {
|
||||||
view: DebugView;
|
view: DebugView;
|
||||||
@@ -2188,6 +2197,7 @@ function DebugVoicePanel({
|
|||||||
assistantId: string | null;
|
assistantId: string | null;
|
||||||
preview: VoicePreview;
|
preview: VoicePreview;
|
||||||
camera: CameraPreview;
|
camera: CameraPreview;
|
||||||
|
hasUnsavedChanges: boolean;
|
||||||
vision: boolean;
|
vision: boolean;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
@@ -2210,6 +2220,12 @@ function DebugVoicePanel({
|
|||||||
const showIdleHub =
|
const showIdleHub =
|
||||||
idleOrFailed &&
|
idleOrFailed &&
|
||||||
(view === "video" || (messages.length === 0 && inChatView));
|
(view === "video" || (messages.length === 0 && inChatView));
|
||||||
|
const startBlockedMessage = !assistantId
|
||||||
|
? "请先保存助手,再开始对话。"
|
||||||
|
: hasUnsavedChanges
|
||||||
|
? "请先保存当前改动,再开始对话。"
|
||||||
|
: "";
|
||||||
|
const startDisabled = status === "connecting" || Boolean(startBlockedMessage);
|
||||||
|
|
||||||
function handleSendText() {
|
function handleSendText() {
|
||||||
if (sendText(textDraft)) {
|
if (sendText(textDraft)) {
|
||||||
@@ -2218,12 +2234,13 @@ function DebugVoicePanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const startConversation = useCallback(async () => {
|
const startConversation = useCallback(async () => {
|
||||||
|
if (!assistantId || hasUnsavedChanges) return;
|
||||||
const videoStream = vision ? await camera.start() : null;
|
const videoStream = vision ? await camera.start() : null;
|
||||||
await connect({
|
await connect({
|
||||||
visionEnabled: vision,
|
visionEnabled: vision,
|
||||||
videoStream,
|
videoStream,
|
||||||
});
|
});
|
||||||
}, [camera, connect, vision]);
|
}, [assistantId, camera, connect, hasUnsavedChanges, vision]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-0 flex-1 flex-col">
|
<div className="flex min-h-0 flex-1 flex-col">
|
||||||
@@ -2234,7 +2251,7 @@ function DebugVoicePanel({
|
|||||||
<DebugIdleHub
|
<DebugIdleHub
|
||||||
status={status}
|
status={status}
|
||||||
error={error}
|
error={error}
|
||||||
assistantId={assistantId}
|
message={startBlockedMessage}
|
||||||
connect={startConversation}
|
connect={startConversation}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -2245,7 +2262,7 @@ function DebugVoicePanel({
|
|||||||
<DebugIdleHub
|
<DebugIdleHub
|
||||||
status={status}
|
status={status}
|
||||||
error={error}
|
error={error}
|
||||||
assistantId={assistantId}
|
message={startBlockedMessage}
|
||||||
connect={startConversation}
|
connect={startConversation}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -2307,6 +2324,8 @@ function DebugVoicePanel({
|
|||||||
"连接失败,请确认后端已启动且助手已保存后重试。"
|
"连接失败,请确认后端已启动且助手已保存后重试。"
|
||||||
: !assistantId
|
: !assistantId
|
||||||
? "请先保存助手,再开始语音预览。"
|
? "请先保存助手,再开始语音预览。"
|
||||||
|
: hasUnsavedChanges
|
||||||
|
? "请先保存当前改动,再开始语音预览。"
|
||||||
: micWarning
|
: micWarning
|
||||||
? `${micWarning} 可接收助手播报,但无法发送语音。`
|
? `${micWarning} 可接收助手播报,但无法发送语音。`
|
||||||
: recording
|
: recording
|
||||||
@@ -2316,7 +2335,7 @@ function DebugVoicePanel({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={!assistantId || status === "connecting"}
|
disabled={recording ? status === "connecting" : startDisabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (recording) {
|
if (recording) {
|
||||||
disconnect();
|
disconnect();
|
||||||
@@ -2403,33 +2422,40 @@ function DebugVoicePanel({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!showIdleHub && (
|
{!showIdleHub && (
|
||||||
<Button
|
<div className="flex shrink-0 flex-col items-end gap-1">
|
||||||
size="sm"
|
<Button
|
||||||
disabled={!assistantId || status === "connecting"}
|
size="sm"
|
||||||
onClick={() => {
|
disabled={recording ? status === "connecting" : startDisabled}
|
||||||
if (recording) {
|
onClick={() => {
|
||||||
disconnect();
|
if (recording) {
|
||||||
} else {
|
disconnect();
|
||||||
void startConversation();
|
} else {
|
||||||
}
|
void startConversation();
|
||||||
}}
|
}
|
||||||
className={[
|
}}
|
||||||
"h-10 shrink-0 gap-1.5 rounded-full px-4",
|
className={[
|
||||||
recording
|
"h-10 gap-1.5 rounded-full px-4",
|
||||||
? "bg-destructive text-white hover:bg-destructive/90"
|
recording
|
||||||
: "",
|
? "bg-destructive text-white hover:bg-destructive/90"
|
||||||
].join(" ")}
|
: "",
|
||||||
aria-label={recording ? "结束语音测试" : "开始语音测试"}
|
].join(" ")}
|
||||||
>
|
aria-label={recording ? "结束语音测试" : "开始语音测试"}
|
||||||
{status === "connecting" ? (
|
>
|
||||||
<Loader2 size={14} className="animate-spin" />
|
{status === "connecting" ? (
|
||||||
) : recording ? (
|
<Loader2 size={14} className="animate-spin" />
|
||||||
<PhoneOff size={14} />
|
) : recording ? (
|
||||||
) : (
|
<PhoneOff size={14} />
|
||||||
<Mic size={14} />
|
) : (
|
||||||
|
<Mic size={14} />
|
||||||
|
)}
|
||||||
|
{recording ? "结束对话" : "开始对话"}
|
||||||
|
</Button>
|
||||||
|
{!recording && startBlockedMessage && (
|
||||||
|
<span className="max-w-40 text-right text-[11px] leading-4 text-destructive">
|
||||||
|
{startBlockedMessage}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{recording ? "结束对话" : "开始对话"}
|
</div>
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2449,18 +2475,25 @@ function DebugVoicePanel({
|
|||||||
// 空闲态只保留一个明确入口,避免中间区域显得拥挤。
|
// 空闲态只保留一个明确入口,避免中间区域显得拥挤。
|
||||||
function DebugIdleHub({
|
function DebugIdleHub({
|
||||||
status,
|
status,
|
||||||
assistantId,
|
error,
|
||||||
|
message,
|
||||||
connect,
|
connect,
|
||||||
}: {
|
}: {
|
||||||
status: VoicePreviewStatus;
|
status: VoicePreviewStatus;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
assistantId: string | null;
|
message: string;
|
||||||
connect: () => Promise<void>;
|
connect: () => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
|
const helperText =
|
||||||
|
message ||
|
||||||
|
(status === "failed"
|
||||||
|
? error || "连接失败,请确认后端已启动且助手已保存后重试。"
|
||||||
|
: "");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-0 flex-1 items-center justify-center px-6 py-8">
|
<div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-6 py-8 text-center">
|
||||||
<Button
|
<Button
|
||||||
disabled={!assistantId}
|
disabled={Boolean(message)}
|
||||||
onClick={() => void connect()}
|
onClick={() => void connect()}
|
||||||
className="h-11 gap-2 rounded-full px-6 text-sm font-medium shadow-sm"
|
className="h-11 gap-2 rounded-full px-6 text-sm font-medium shadow-sm"
|
||||||
aria-label={status === "failed" ? "重新连接" : "开始语音测试"}
|
aria-label={status === "failed" ? "重新连接" : "开始语音测试"}
|
||||||
@@ -2468,6 +2501,16 @@ function DebugIdleHub({
|
|||||||
<Mic size={18} />
|
<Mic size={18} />
|
||||||
{status === "failed" ? "重新连接" : "开始对话"}
|
{status === "failed" ? "重新连接" : "开始对话"}
|
||||||
</Button>
|
</Button>
|
||||||
|
{helperText && (
|
||||||
|
<p
|
||||||
|
className={[
|
||||||
|
"max-w-xs text-xs leading-5",
|
||||||
|
message ? "text-destructive" : "text-muted-foreground",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
{helperText}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user