fix: isolate startup tools and preview dialogs

This commit is contained in:
Xin Wang
2026-08-02 00:07:35 +08:00
parent 0331f8cd07
commit 479a516546
10 changed files with 202 additions and 49 deletions

View File

@@ -645,6 +645,8 @@ function DebugVoicePanel({
const recording = status === "connecting" || status === "connected";
const [textDraft, setTextDraft] = useState("");
const [inputMode, setInputMode] = useState<DebugInputMode>("mic");
const [clientDialogContainer, setClientDialogContainer] =
useState<HTMLDivElement | null>(null);
const inChatView = view === "chat" && (!SHOW_VOICE_VIZ || showTranscript);
const idleOrFailed = status === "idle" || status === "failed";
const showIdleHub =
@@ -682,10 +684,17 @@ function DebugVoicePanel({
]);
return (
<div className="flex min-h-0 flex-1 flex-col">
<div
ref={setClientDialogContainer}
className="relative isolate flex min-h-0 flex-1 flex-col overflow-hidden"
>
{/* 后端 TTS 音频经 WebRTC 媒体流过来,挂这里播放 */}
<audio ref={audioRef} autoPlay playsInline className="hidden" />
<ClientMessageDialog preview={preview} />
<ClientMessageDialog
preview={preview}
container={clientDialogContainer}
contained
/>
{vision && !showIdleHub ? (
<DebugVisionWorkspace
view={view}

View File

@@ -135,13 +135,6 @@ export function PromptEditor({
]
: otherActions,
});
if (
enabled &&
defaultToolId &&
!form.toolIds.includes(defaultToolId)
) {
updateForm("toolIds", [...form.toolIds, defaultToolId]);
}
}
function updateOpeningMessage(
@@ -254,12 +247,7 @@ export function PromptEditor({
value={openingMessage.toolId}
options={showMessageTools}
noneLabel="请选择 show_message 工具"
onChange={(toolId) => {
updateOpeningMessage({ toolId });
if (toolId && !form.toolIds.includes(toolId)) {
updateForm("toolIds", [...form.toolIds, toolId]);
}
}}
onChange={(toolId) => updateOpeningMessage({ toolId })}
/>
{showMessageTools.length === 0 && (
<p className="text-xs leading-5 text-muted-foreground">
@@ -308,7 +296,8 @@ export function PromptEditor({
/>
</label>
<p className="text-xs leading-5 text-muted-foreground">
Esc
</p>
</div>
)}

View File

@@ -120,7 +120,15 @@ function actionVariant(style: MessageActionStyle) {
* Renders messages requested by the agent through the generic Client Tool
* channel. The tool result is held until the user chooses an action.
*/
export function ClientMessageDialog({ preview }: { preview: VoicePreview }) {
export function ClientMessageDialog({
preview,
container,
contained = false,
}: {
preview: VoicePreview;
container?: HTMLElement | null;
contained?: boolean;
}) {
const { registerClientTool, status } = preview;
const [message, setMessage] = useState<MessageState | null>(null);
const pendingRef = useRef<PendingMessage | null>(null);
@@ -163,6 +171,7 @@ export function ClientMessageDialog({ preview }: { preview: VoicePreview }) {
return (
<Dialog
modal={!contained}
open={message !== null}
onOpenChange={(open) => {
if (!open && message?.dismissible) complete("dismissed");
@@ -170,6 +179,8 @@ export function ClientMessageDialog({ preview }: { preview: VoicePreview }) {
>
<DialogContent
className="gap-5 sm:max-w-md"
portalContainer={container}
contained={contained}
showCloseButton={message?.dismissible ?? true}
onEscapeKeyDown={(event) => {
if (!message?.dismissible) event.preventDefault();

View File

@@ -33,13 +33,17 @@ function DialogClose({
function DialogOverlay({
className,
contained = false,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
}: React.ComponentProps<typeof DialogPrimitive.Overlay> & {
contained?: boolean
}) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 isolate z-50 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
contained ? "absolute inset-0" : "fixed inset-0",
"isolate z-50 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className
)}
{...props}
@@ -51,17 +55,22 @@ function DialogContent({
className,
children,
showCloseButton = true,
portalContainer,
contained = false,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
portalContainer?: Element | DocumentFragment | null
contained?: boolean
}) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPortal container={portalContainer}>
<DialogOverlay contained={contained} />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-4xl bg-popover p-6 text-sm text-popover-foreground shadow-xl ring-1 ring-foreground/5 duration-100 outline-none sm:max-w-md dark:ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
contained ? "absolute" : "fixed",
"top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-4xl bg-popover p-6 text-sm text-popover-foreground shadow-xl ring-1 ring-foreground/5 duration-100 outline-none sm:max-w-md dark:ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className
)}
{...props}