Files
ai-video-fullstack/frontend/src/hooks/use-photo-capture-tool.ts
2026-08-03 14:00:33 +08:00

82 lines
2.1 KiB
TypeScript

"use client";
import { useCallback, useEffect, useState } from "react";
import type { VoicePreview } from "@/hooks/use-voice-preview";
const PHOTO_BUTTON_TOOL = "set_photo_button_visible";
const PHOTO_BUTTON_DEFINITION = {
name: PHOTO_BUTTON_TOOL,
label: "控制拍照按钮",
description: "显示或隐藏客户端的拍照按钮。",
parameters: [
{
name: "visible",
type: "boolean",
required: true,
description: "是否显示拍照按钮。",
},
],
} as const;
export function usePhotoCaptureTool(preview: VoicePreview, enabled = true) {
const { registerClientTool, sendUserInput, status } = preview;
const [visible, setVisible] = useState(false);
const [capturing, setCapturing] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!enabled) return;
return registerClientTool(
PHOTO_BUTTON_DEFINITION,
({ visible: nextValue }) => {
if (typeof nextValue !== "boolean") {
throw new Error("visible 参数必须是布尔值");
}
setVisible(nextValue);
setError(null);
return { visible: nextValue };
},
);
}, [enabled, registerClientTool]);
useEffect(() => {
if (enabled && status === "connected") return;
// eslint-disable-next-line react-hooks/set-state-in-effect
setVisible(false);
setCapturing(false);
}, [enabled, status]);
const capture = useCallback(async () => {
if (capturing || status !== "connected") return;
setCapturing(true);
setError(null);
try {
await sendUserInput(
[
{
type: "input_image",
source: { type: "camera_frame", frame: "current" },
},
],
{ runImmediately: true, interrupt: true },
);
} catch (captureError) {
setError(
captureError instanceof Error ? captureError.message : "拍照提交失败",
);
} finally {
setCapturing(false);
}
}, [capturing, sendUserInput, status]);
return {
visible,
capturing,
error,
capture,
};
}
export type PhotoCaptureTool = ReturnType<typeof usePhotoCaptureTool>;