180 lines
6.1 KiB
TypeScript
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>
|
|
);
|
|
}
|