Fix: Template attribute dialog input and type issues

This commit is contained in:
2025-05-30 18:35:40 +08:00
parent 13295bd24d
commit 6b8426868a
3 changed files with 186 additions and 16 deletions

View File

@@ -77,6 +77,7 @@ import { Slider } from "@/components/ui/slider";
import { NonOverflowScrollArea, ScrollArea } from "@/components/ui/scroll-area";
import { AppChatStoreContext, AppContext } from "@/pages/App";
import { toast } from "@/hooks/use-toast";
import { TemplateAttributeDialog } from "@/components/TemplateAttributeDialog";
const TTS_VOICES: string[] = [
"alloy",
@@ -739,6 +740,7 @@ export default (props: {}) => {
// @ts-ignore
const { langCode, setLangCode } = useContext(langCodeContext);
const [open, setOpen] = useState<boolean>(false);
const [showTemplateDialog, setShowTemplateDialog] = useState(false);
useEffect(() => {
themeChange(false);
@@ -804,7 +806,7 @@ export default (props: {}) => {
<LongInput
label="System Prompt"
field="systemMessageContent"
help="系统消息用于指示ChatGPT的角色和一些前置条件例如“你是一个有帮助的人工智能助理”或者“你是一个专业英语翻译把我的话全部翻译成英语”详情参考 OPEAN AI API 文档"
help="System prompt, used to indicate the role of ChatGPT and some preconditions, such as 'You are a helpful AI assistant' or 'You are a professional English translator, translate my words into English', please refer to the OpenAI API documentation"
{...props}
/>
@@ -1051,21 +1053,7 @@ export default (props: {}) => {
<Button
variant="outline"
className="w-full"
onClick={() => {
const name = prompt(
tr("Give this template a name:", langCode)
);
if (!name) {
alert(tr("No template name specified", langCode));
return;
}
const tmp: ChatStore = structuredClone(chatStore);
tmp.history = tmp.history.filter((h) => h.example);
// @ts-ignore
tmp.name = name;
templates.push(tmp as TemplateChatStore);
setTemplates([...templates]);
}}
onClick={() => setShowTemplateDialog(true)}
>
<Tr>As template</Tr>
</Button>
@@ -1594,6 +1582,25 @@ export default (props: {}) => {
</div>
</NonOverflowScrollArea>
</SheetContent>
<TemplateAttributeDialog
open={showTemplateDialog}
chatStore={chatStore}
langCode={langCode}
onClose={() => setShowTemplateDialog(false)}
onSave={(name, selectedAttributes) => {
const tmp: ChatStore = {
...chatStore,
...selectedAttributes,
history: chatStore.history.filter((h) => h.example),
};
// @ts-ignore
tmp.name = name;
templates.push(tmp as TemplateChatStore);
setTemplates([...templates]);
setShowTemplateDialog(false);
}}
/>
</Sheet>
);
};

View File

@@ -0,0 +1,142 @@
import { useState } from "react";
import { ChatStore } from "@/types/chatstore";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Label } from "@/components/ui/label";
import { ControlledInput } from "@/components/ui/controlled-input";
import { tr } from "@/translate";
interface TemplateAttributeDialogProps {
chatStore: ChatStore;
onSave: (name: string, selectedAttributes: Partial<ChatStore>) => void;
onClose: () => void;
open: boolean;
langCode: "en-US" | "zh-CN";
}
export function TemplateAttributeDialog({
chatStore,
onSave,
onClose,
open,
langCode,
}: TemplateAttributeDialogProps) {
// Create a map of all ChatStore attributes and their selection state
const [selectedAttributes, setSelectedAttributes] = useState<Record<string, boolean>>(() => {
const initial: Record<string, boolean> = {};
// Initialize all attributes as selected by default
Object.keys(chatStore).forEach((key) => {
initial[key] = true;
});
return initial;
});
const [templateName, setTemplateName] = useState("");
const [nameError, setNameError] = useState("");
const handleSave = () => {
// Validate name
if (!templateName.trim()) {
setNameError(tr("Template name is required", langCode));
return;
}
setNameError("");
// Create a new object with only the selected attributes
const filteredStore = {} as Partial<ChatStore>;
Object.entries(selectedAttributes).forEach(([key, isSelected]) => {
if (isSelected) {
const typedKey = key as keyof ChatStore;
// Use type assertion to ensure type safety
(filteredStore as any)[typedKey] = chatStore[typedKey];
}
});
onSave(templateName, filteredStore);
};
const toggleAll = (checked: boolean) => {
const newSelected = { ...selectedAttributes };
Object.keys(newSelected).forEach((key) => {
newSelected[key] = checked;
});
setSelectedAttributes(newSelected);
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Select Template Attributes</DialogTitle>
<DialogDescription>
Choose which attributes to include in your template. Unselected attributes will be omitted.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="template-name">Template Name</Label>
<ControlledInput
id="template-name"
value={templateName}
onChange={(e) => {
setTemplateName(e.target.value);
setNameError("");
}}
placeholder={tr("Enter template name", langCode)}
/>
{nameError && (
<p className="text-sm text-red-500">{nameError}</p>
)}
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="select-all"
checked={Object.values(selectedAttributes).every((v) => v)}
onCheckedChange={(checked) => toggleAll(checked as boolean)}
/>
<Label htmlFor="select-all">Select All</Label>
</div>
<ScrollArea className="h-[400px] rounded-md border p-4">
<div className="grid grid-cols-2 gap-4">
{Object.keys(chatStore).map((key) => (
<div key={key} className="flex items-center space-x-2">
<Checkbox
id={key}
checked={selectedAttributes[key]}
onCheckedChange={(checked) =>
setSelectedAttributes((prev) => ({
...prev,
[key]: checked as boolean,
}))
}
/>
<Label htmlFor={key} className="text-sm">
{key}
</Label>
</div>
))}
</div>
</ScrollArea>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button onClick={handleSave}>Save Template</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,21 @@
import { ComponentProps, forwardRef } from "react";
import { cn } from "@/lib/utils";
const ControlledInput = forwardRef<HTMLInputElement, ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
);
}
);
ControlledInput.displayName = "ControlledInput";
export { ControlledInput };