Files
ai-video-fullstack/frontend/src/components/editor/vision-config-section.tsx
T

180 lines
6.1 KiB
TypeScript

"use client";
import { Camera, Check } from "lucide-react";
import {
ResourceSelectField,
ToggleRow,
} from "@/components/assistant-editor/editor-controls";
import { SectionCard } from "@/components/editor/section-card";
import { Textarea } from "@/components/ui/textarea";
import type { VisionPromptMode } from "@/lib/api";
type VisionModelOption = {
value: string;
label: string;
};
const promptModes: Array<{
value: VisionPromptMode;
title: string;
description: string;
}> = [
{
value: "fixed",
title: "固定提示词",
description: "每次获取画面都发送同一个问题。",
},
{
value: "runtime",
title: "运行时自动生成",
description: "模型结合当前对话,临时生成看图问题。",
},
{
value: "guided",
title: "固定提示词引导",
description: "模型动态生成问题,同时遵守固定引导。",
},
];
export function VisionConfigSection({
description,
hint,
enabled,
modelResourceId,
promptMode,
prompt,
mainModelResourceId,
mainModelSupportsImageInput,
modelOptions,
onEnabledChange,
onModelResourceIdChange,
onPromptModeChange,
onPromptChange,
}: {
description: string;
hint: string;
enabled: boolean;
modelResourceId: string;
promptMode: VisionPromptMode;
prompt: string;
mainModelResourceId: string;
mainModelSupportsImageInput?: boolean;
modelOptions: VisionModelOption[];
onEnabledChange: (enabled: boolean) => void;
onModelResourceIdChange: (modelResourceId: string) => void;
onPromptModeChange: (promptMode: VisionPromptMode) => void;
onPromptChange: (prompt: string) => void;
}) {
const mainModelSupportsVision =
mainModelSupportsImageInput ??
modelOptions.some((option) => option.value === mainModelResourceId);
const independentModelOptions = modelOptions.filter(
(option) => option.value !== mainModelResourceId,
);
return (
<SectionCard
icon={<Camera size={15} />}
title="视觉理解"
description={description}
>
<ToggleRow
title="允许理解当前画面"
hint={hint}
checked={enabled}
onChange={onEnabledChange}
/>
{enabled && (
<>
<ResourceSelectField
label="视觉模型"
value={modelResourceId}
onChange={onModelResourceIdChange}
options={independentModelOptions}
noneLabel="模型自己"
/>
{!modelResourceId && !mainModelSupportsVision && (
<p className="text-xs text-destructive">
当前模型未标记支持图片输入,请选择独立视觉模型。
</p>
)}
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
看图问题生成方式
</label>
<div role="radiogroup" aria-label="看图问题生成方式" className="space-y-2">
{promptModes.map((option) => {
const selected = promptMode === option.value;
return (
<button
key={option.value}
type="button"
role="radio"
aria-checked={selected}
onClick={() => onPromptModeChange(option.value)}
className={[
"flex w-full items-start gap-3 rounded-xl border px-3.5 py-3 text-left transition-colors",
selected
? "border-primary bg-surface-strong/70 ring-1 ring-primary"
: "border-hairline bg-background hover:border-hairline-strong",
].join(" ")}
>
<span
className={[
"mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border",
selected
? "border-primary bg-primary text-primary-foreground"
: "border-hairline-strong text-transparent",
].join(" ")}
>
<Check size={12} />
</span>
<span className="min-w-0">
<span className="block text-sm font-medium text-foreground">
{option.title}
</span>
<span className="mt-0.5 block text-xs leading-5 text-muted-foreground">
{option.description}
</span>
</span>
</button>
);
})}
</div>
</div>
{promptMode !== "runtime" && (
<div className="space-y-1.5">
<label className="text-sm font-medium text-foreground">
{promptMode === "fixed" ? "固定视觉提示词" : "运行时提示词引导"}
</label>
<Textarea
value={prompt}
maxLength={4000}
rows={5}
onChange={(event) => onPromptChange(event.target.value)}
placeholder={
promptMode === "fixed"
? "例如:请识别画面中的车辆数量、碰撞位置、明显损伤和人员状态。"
: "例如:问题应聚焦车辆数量、碰撞位置和人员状态;只询问画面中可直接判断的信息。"
}
className="field-sizing-fixed min-h-28 resize-y border-hairline-strong bg-background text-sm leading-6 text-foreground placeholder:text-muted-soft"
/>
<p className="text-[11px] leading-5 text-muted-soft">
{promptMode === "fixed"
? "工具调用时不会让模型改写这段内容。"
: "模型会结合当前对话生成具体问题,并受到这段内容约束。"}
</p>
{!prompt.trim() && (
<p className="text-xs leading-5 text-destructive">
当前模式需要填写提示词后才能保存。
</p>
)}
</div>
)}
</>
)}
</SectionCard>
);
}