feat(frontend): embed prompt dynamic variables section
This commit is contained in:
@@ -1,16 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Braces, Check, Copy } from "lucide-react";
|
import { Check, Copy } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -141,40 +134,6 @@ export function DynamicVariableEditorHint({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DynamicVariablesDialog({
|
|
||||||
open,
|
|
||||||
onOpenChange,
|
|
||||||
definitions,
|
|
||||||
onChange,
|
|
||||||
}: {
|
|
||||||
open: boolean;
|
|
||||||
onOpenChange: (open: boolean) => void;
|
|
||||||
definitions: Record<string, DynamicVariableDefinition>;
|
|
||||||
onChange: (definitions: Record<string, DynamicVariableDefinition>) => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
||||||
<DialogContent className="max-h-[78vh] overflow-hidden rounded-2xl border-hairline bg-card p-0 sm:max-w-[520px]">
|
|
||||||
<DialogHeader className="border-b border-hairline px-6 py-5 text-left">
|
|
||||||
<DialogTitle className="flex items-center gap-2 text-base font-medium">
|
|
||||||
<Braces size={17} />
|
|
||||||
动态变量
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription className="text-xs leading-5">
|
|
||||||
助手配置中引用的自定义变量会自动出现在这里。
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DynamicVariablesEditor
|
|
||||||
definitions={definitions}
|
|
||||||
onChange={onChange}
|
|
||||||
layout="dialog"
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DynamicVariablesPanel({
|
export function DynamicVariablesPanel({
|
||||||
definitions,
|
definitions,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -196,6 +155,27 @@ export function DynamicVariablesPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function DynamicVariablesSection({
|
||||||
|
definitions,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
definitions: Record<string, DynamicVariableDefinition>;
|
||||||
|
onChange: (definitions: Record<string, DynamicVariableDefinition>) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-xs leading-5 text-muted-foreground">
|
||||||
|
助手配置中引用的自定义变量会自动出现在这里。
|
||||||
|
</p>
|
||||||
|
<DynamicVariablesEditor
|
||||||
|
definitions={definitions}
|
||||||
|
onChange={onChange}
|
||||||
|
layout="section"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function DynamicVariablesEditor({
|
function DynamicVariablesEditor({
|
||||||
definitions,
|
definitions,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -203,7 +183,7 @@ function DynamicVariablesEditor({
|
|||||||
}: {
|
}: {
|
||||||
definitions: Record<string, DynamicVariableDefinition>;
|
definitions: Record<string, DynamicVariableDefinition>;
|
||||||
onChange: (definitions: Record<string, DynamicVariableDefinition>) => void;
|
onChange: (definitions: Record<string, DynamicVariableDefinition>) => void;
|
||||||
layout: "dialog" | "panel";
|
layout: "panel" | "section";
|
||||||
}) {
|
}) {
|
||||||
const entries = Object.entries(definitions);
|
const entries = Object.entries(definitions);
|
||||||
const [copiedSystemVariable, setCopiedSystemVariable] = useState<string | null>(
|
const [copiedSystemVariable, setCopiedSystemVariable] = useState<string | null>(
|
||||||
@@ -226,7 +206,7 @@ function DynamicVariablesEditor({
|
|||||||
className={
|
className={
|
||||||
layout === "panel"
|
layout === "panel"
|
||||||
? "flex min-h-0 flex-1 flex-col"
|
? "flex min-h-0 flex-1 flex-col"
|
||||||
: "min-h-0 px-6 pb-6"
|
: "min-h-0 flex-none"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TabsList className="grid w-full shrink-0 grid-cols-2 bg-surface-strong">
|
<TabsList className="grid w-full shrink-0 grid-cols-2 bg-surface-strong">
|
||||||
@@ -249,7 +229,7 @@ function DynamicVariablesEditor({
|
|||||||
className={
|
className={
|
||||||
layout === "panel"
|
layout === "panel"
|
||||||
? "scrollbar-subtle min-h-0 flex-1 overflow-y-auto pt-3"
|
? "scrollbar-subtle min-h-0 flex-1 overflow-y-auto pt-3"
|
||||||
: "max-h-[calc(78vh-168px)] overflow-y-auto pt-3"
|
: "flex-none pt-3"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{entries.length === 0 ? (
|
{entries.length === 0 ? (
|
||||||
@@ -371,7 +351,7 @@ function DynamicVariablesEditor({
|
|||||||
className={
|
className={
|
||||||
layout === "panel"
|
layout === "panel"
|
||||||
? "scrollbar-subtle min-h-0 flex-1 space-y-3 overflow-y-auto pt-3"
|
? "scrollbar-subtle min-h-0 flex-1 space-y-3 overflow-y-auto pt-3"
|
||||||
: "max-h-[calc(78vh-168px)] space-y-3 overflow-y-auto pt-3"
|
: "flex-none space-y-3 pt-3"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<p className="text-[11px] leading-5 text-muted-foreground">
|
<p className="text-[11px] leading-5 text-muted-foreground">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
|
Braces,
|
||||||
Bot,
|
Bot,
|
||||||
Brain,
|
Brain,
|
||||||
Database,
|
Database,
|
||||||
@@ -15,7 +16,7 @@ import {
|
|||||||
import { DebugDrawer } from "@/components/assistant-editor/debug-preview";
|
import { DebugDrawer } from "@/components/assistant-editor/debug-preview";
|
||||||
import {
|
import {
|
||||||
DynamicVariableEditorHint,
|
DynamicVariableEditorHint,
|
||||||
DynamicVariablesDialog,
|
DynamicVariablesSection,
|
||||||
} from "@/components/assistant-editor/dynamic-variables";
|
} from "@/components/assistant-editor/dynamic-variables";
|
||||||
import {
|
import {
|
||||||
AssistantIdentity,
|
AssistantIdentity,
|
||||||
@@ -43,6 +44,7 @@ const promptSections = [
|
|||||||
{ id: "models", label: "模型与语音" },
|
{ id: "models", label: "模型与语音" },
|
||||||
{ id: "capabilities", label: "知识与工具" },
|
{ id: "capabilities", label: "知识与工具" },
|
||||||
{ id: "interaction", label: "交互策略" },
|
{ id: "interaction", label: "交互策略" },
|
||||||
|
{ id: "variables", label: "动态变量" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type PromptSectionId = (typeof promptSections)[number]["id"];
|
type PromptSectionId = (typeof promptSections)[number]["id"];
|
||||||
@@ -50,7 +52,6 @@ type PromptSectionId = (typeof promptSections)[number]["id"];
|
|||||||
type PromptEditorProps = {
|
type PromptEditorProps = {
|
||||||
assistantId: string | null;
|
assistantId: string | null;
|
||||||
form: AssistantForm;
|
form: AssistantForm;
|
||||||
dynamicVariablesOpen: boolean;
|
|
||||||
dynamicVariableDefinitions: Record<string, DynamicVariableDefinition>;
|
dynamicVariableDefinitions: Record<string, DynamicVariableDefinition>;
|
||||||
saving: boolean;
|
saving: boolean;
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
@@ -64,7 +65,6 @@ type PromptEditorProps = {
|
|||||||
tools: Tool[];
|
tools: Tool[];
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
setDynamicVariablesOpen: (open: boolean) => void;
|
|
||||||
updateForm: <K extends keyof AssistantForm>(
|
updateForm: <K extends keyof AssistantForm>(
|
||||||
key: K,
|
key: K,
|
||||||
value: AssistantForm[K],
|
value: AssistantForm[K],
|
||||||
@@ -76,7 +76,6 @@ type PromptEditorProps = {
|
|||||||
export function PromptEditor({
|
export function PromptEditor({
|
||||||
assistantId,
|
assistantId,
|
||||||
form,
|
form,
|
||||||
dynamicVariablesOpen,
|
|
||||||
dynamicVariableDefinitions,
|
dynamicVariableDefinitions,
|
||||||
saving,
|
saving,
|
||||||
dirty,
|
dirty,
|
||||||
@@ -90,7 +89,6 @@ export function PromptEditor({
|
|||||||
tools,
|
tools,
|
||||||
onBack,
|
onBack,
|
||||||
onSave,
|
onSave,
|
||||||
setDynamicVariablesOpen,
|
|
||||||
updateForm,
|
updateForm,
|
||||||
handlePromptVisionEnabledChange,
|
handlePromptVisionEnabledChange,
|
||||||
handlePromptModelChange,
|
handlePromptModelChange,
|
||||||
@@ -102,6 +100,7 @@ export function PromptEditor({
|
|||||||
models: null,
|
models: null,
|
||||||
capabilities: null,
|
capabilities: null,
|
||||||
interaction: null,
|
interaction: null,
|
||||||
|
variables: null,
|
||||||
});
|
});
|
||||||
const selectedAnchorRef = useRef<PromptSectionId | null>(null);
|
const selectedAnchorRef = useRef<PromptSectionId | null>(null);
|
||||||
const [activeSection, setActiveSection] =
|
const [activeSection, setActiveSection] =
|
||||||
@@ -217,7 +216,7 @@ export function PromptEditor({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="-mt-6 flex h-full flex-col gap-4">
|
<div className="-mt-6 flex h-full flex-col gap-4 overflow-hidden bg-background">
|
||||||
<div className="flex shrink-0 items-center justify-between gap-6 border-b border-hairline pb-3 pt-1">
|
<div className="flex shrink-0 items-center justify-between gap-6 border-b border-hairline pb-3 pt-1">
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<EditorBackButton onClick={onBack} />
|
<EditorBackButton onClick={onBack} />
|
||||||
@@ -249,25 +248,13 @@ export function PromptEditor({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DynamicVariablesDialog
|
|
||||||
open={dynamicVariablesOpen}
|
|
||||||
onOpenChange={setDynamicVariablesOpen}
|
|
||||||
definitions={dynamicVariableDefinitions}
|
|
||||||
onChange={(dynamicVariableDefinitions) =>
|
|
||||||
updateForm(
|
|
||||||
"dynamicVariableDefinitions",
|
|
||||||
dynamicVariableDefinitions,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex min-h-0 flex-1 gap-4">
|
<div className="flex min-h-0 flex-1 gap-4">
|
||||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col gap-3">
|
<div className="flex min-h-0 min-w-0 flex-1 flex-col gap-3">
|
||||||
<nav
|
<nav
|
||||||
aria-label="提示词配置分区"
|
aria-label="提示词配置分区"
|
||||||
className="shrink-0 overflow-x-auto"
|
className="shrink-0 overflow-x-auto overscroll-none"
|
||||||
>
|
>
|
||||||
<div className="grid min-w-[22rem] grid-cols-4 border-b border-hairline">
|
<div className="grid min-w-[28rem] grid-cols-5 border-b border-hairline">
|
||||||
{promptSections.map((section) => {
|
{promptSections.map((section) => {
|
||||||
const active = activeSection === section.id;
|
const active = activeSection === section.id;
|
||||||
|
|
||||||
@@ -293,7 +280,7 @@ export function PromptEditor({
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
ref={scrollContainerRef}
|
ref={scrollContainerRef}
|
||||||
className="scrollbar-subtle min-h-0 min-w-0 flex-1 space-y-3 overflow-y-auto pr-1"
|
className="scrollbar-subtle min-h-0 min-w-0 flex-1 space-y-3 overflow-y-auto overscroll-none bg-background pr-1"
|
||||||
>
|
>
|
||||||
<section
|
<section
|
||||||
ref={(element) => {
|
ref={(element) => {
|
||||||
@@ -314,7 +301,7 @@ export function PromptEditor({
|
|||||||
/>
|
/>
|
||||||
<DynamicVariableEditorHint
|
<DynamicVariableEditorHint
|
||||||
count={Object.keys(dynamicVariableDefinitions).length}
|
count={Object.keys(dynamicVariableDefinitions).length}
|
||||||
onOpen={() => setDynamicVariablesOpen(true)}
|
onOpen={() => scrollToSection("variables")}
|
||||||
/>
|
/>
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
|
|
||||||
@@ -330,7 +317,7 @@ export function PromptEditor({
|
|||||||
/>
|
/>
|
||||||
<DynamicVariableEditorHint
|
<DynamicVariableEditorHint
|
||||||
count={Object.keys(dynamicVariableDefinitions).length}
|
count={Object.keys(dynamicVariableDefinitions).length}
|
||||||
onOpen={() => setDynamicVariablesOpen(true)}
|
onOpen={() => scrollToSection("variables")}
|
||||||
/>
|
/>
|
||||||
{form.runtimeMode === "pipeline" && (
|
{form.runtimeMode === "pipeline" && (
|
||||||
<div className="mt-4 space-y-3 border-t border-hairline pt-4">
|
<div className="mt-4 space-y-3 border-t border-hairline pt-4">
|
||||||
@@ -550,6 +537,29 @@ export function PromptEditor({
|
|||||||
)}
|
)}
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
ref={(element) => {
|
||||||
|
sectionRefs.current.variables = element;
|
||||||
|
}}
|
||||||
|
className="scroll-mt-3 space-y-3"
|
||||||
|
>
|
||||||
|
<SectionCard
|
||||||
|
icon={<Braces size={15} />}
|
||||||
|
title="动态变量"
|
||||||
|
description="编辑提示词和开场白中引用的自定义变量"
|
||||||
|
>
|
||||||
|
<DynamicVariablesSection
|
||||||
|
definitions={dynamicVariableDefinitions}
|
||||||
|
onChange={(dynamicVariableDefinitions) =>
|
||||||
|
updateForm(
|
||||||
|
"dynamicVariableDefinitions",
|
||||||
|
dynamicVariableDefinitions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SectionCard>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
|||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
<div className="fixed inset-0 flex overflow-hidden bg-background text-foreground">
|
||||||
<Sidebar collapsed={collapsed} onToggle={() => setCollapsed((v) => !v)} />
|
<Sidebar collapsed={collapsed} onToggle={() => setCollapsed((v) => !v)} />
|
||||||
|
|
||||||
<main className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
<main className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||||
|
|||||||
@@ -1565,7 +1565,6 @@ export function AssistantPage(props: AssistantPageProps) {
|
|||||||
<PromptEditor
|
<PromptEditor
|
||||||
assistantId={editingId}
|
assistantId={editingId}
|
||||||
form={form}
|
form={form}
|
||||||
dynamicVariablesOpen={dynamicVariablesOpen}
|
|
||||||
dynamicVariableDefinitions={effectiveDynamicVariableDefinitions}
|
dynamicVariableDefinitions={effectiveDynamicVariableDefinitions}
|
||||||
saving={saving}
|
saving={saving}
|
||||||
dirty={dirty}
|
dirty={dirty}
|
||||||
@@ -1579,7 +1578,6 @@ export function AssistantPage(props: AssistantPageProps) {
|
|||||||
tools={tools}
|
tools={tools}
|
||||||
onBack={() => router.push("/assistants")}
|
onBack={() => router.push("/assistants")}
|
||||||
onSave={() => void handleSavePrompt()}
|
onSave={() => void handleSavePrompt()}
|
||||||
setDynamicVariablesOpen={setDynamicVariablesOpen}
|
|
||||||
updateForm={updateForm}
|
updateForm={updateForm}
|
||||||
handlePromptVisionEnabledChange={handlePromptVisionEnabledChange}
|
handlePromptVisionEnabledChange={handlePromptVisionEnabledChange}
|
||||||
handlePromptModelChange={handlePromptModelChange}
|
handlePromptModelChange={handlePromptModelChange}
|
||||||
|
|||||||
Reference in New Issue
Block a user