Fix: Template attribute dialog input and type issues
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
142
src/components/TemplateAttributeDialog.tsx
Normal file
142
src/components/TemplateAttributeDialog.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
21
src/components/ui/controlled-input.tsx
Normal file
21
src/components/ui/controlled-input.tsx
Normal 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 };
|
||||
Reference in New Issue
Block a user