| | import { |
| | Button, |
| | InputCheckbox, |
| | InputCheckboxList, |
| | InputSelect, |
| | InputSlider, |
| | InputTextarea, |
| | } from "@theme"; |
| | import { formatBytes } from "@utils/format.ts"; |
| | import { MODELS } from "@utils/models.ts"; |
| | import { TOOLS } from "@utils/tools.ts"; |
| | import { useEffect, useRef } from "react"; |
| | import { Controller, useForm } from "react-hook-form"; |
| |
|
| | import type { ChatSettings } from "./types.ts"; |
| |
|
| | export default function ChatSettingsModalForm({ |
| | defaultValues, |
| | onSubmit, |
| | downloadedModels, |
| | visible, |
| | }: { |
| | defaultValues: ChatSettings; |
| | onSubmit: (data: ChatSettings) => void; |
| | downloadedModels: Array<string>; |
| | visible: boolean; |
| | }) { |
| | const selectModelRef = useRef<HTMLSelectElement>(null); |
| | const { |
| | control, |
| | handleSubmit, |
| | formState: { errors }, |
| | } = useForm<ChatSettings>({ |
| | defaultValues, |
| | }); |
| |
|
| | const modelOptions = Object.values( |
| | Object.entries(MODELS).reduce( |
| | (groups, [key, model]) => { |
| | const groupName = model.group; |
| |
|
| | if (!groups[groupName]) { |
| | groups[groupName] = { |
| | label: groupName.charAt(0).toUpperCase() + groupName.slice(1), |
| | options: [], |
| | }; |
| | } |
| |
|
| | groups[groupName].options.push({ |
| | value: key, |
| | label: `${model.title} - ${formatBytes(model.size)}${downloadedModels.includes(key) ? " *" : ""}`, |
| | title: model.title, |
| | }); |
| |
|
| | return groups; |
| | }, |
| | {} as Record< |
| | string, |
| | { |
| | label: string; |
| | options: Array<{ value: string; label: string; title: string }>; |
| | } |
| | > |
| | ) |
| | ) |
| | .sort((a, b) => a.label.localeCompare(b.label)) |
| | .map((group) => ({ |
| | ...group, |
| | options: group.options |
| | .sort((a, b) => a.title.localeCompare(b.title)) |
| | .map(({ value, label }) => ({ value, label })), |
| | })); |
| |
|
| | const toolOptions = TOOLS.map((tool) => ({ |
| | name: tool.name, |
| | label: tool.name, |
| | description: tool.description, |
| | })); |
| |
|
| | useEffect(() => { |
| | if (visible && selectModelRef.current) { |
| | |
| | setTimeout(() => { |
| | selectModelRef.current?.focus(); |
| | }, 200); |
| | } |
| | }, [visible]); |
| |
|
| | return ( |
| | <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-6"> |
| | <Controller |
| | name="modelKey" |
| | control={control} |
| | rules={{ required: "Model is required" }} |
| | render={({ field }) => ( |
| | <InputSelect |
| | id="model-select" |
| | label="Model" |
| | error={errors.modelKey?.message as string} |
| | value={field.value} |
| | onChange={field.onChange} |
| | options={modelOptions} |
| | required |
| | tooltip="(*) are already downloaded" |
| | ref={selectModelRef} |
| | /> |
| | )} |
| | /> |
| | |
| | <Controller |
| | name="systemPrompt" |
| | control={control} |
| | rules={{ required: "System prompt is required" }} |
| | render={({ field }) => ( |
| | <InputTextarea |
| | id="system-prompt" |
| | label="System Prompt" |
| | placeholder="You are a helpful AI Assistant" |
| | error={errors.systemPrompt?.message as string} |
| | value={field.value} |
| | onChange={field.onChange} |
| | rows={6} |
| | required |
| | /> |
| | )} |
| | /> |
| | |
| | <Controller |
| | name="tools" |
| | control={control} |
| | render={({ field }) => ( |
| | <InputCheckboxList |
| | id="tools" |
| | label="Tools (only if supported by the model)" |
| | options={toolOptions} |
| | value={field.value} |
| | onChange={field.onChange} |
| | error={errors.tools?.message as string} |
| | /> |
| | )} |
| | /> |
| | |
| | <Controller |
| | name="temperature" |
| | control={control} |
| | rules={{ |
| | required: "Temperature is required", |
| | min: { value: 0, message: "Temperature must be at least 0" }, |
| | max: { value: 2, message: "Temperature must be at most 2" }, |
| | }} |
| | render={({ field }) => ( |
| | <InputSlider |
| | id="temperature" |
| | label="Temperature" |
| | value={field.value} |
| | onChange={field.onChange} |
| | min={0} |
| | max={2} |
| | step={0.1} |
| | error={errors.temperature?.message as string} |
| | tooltip="Controls randomness in responses. Lower values are more focused, higher values are more creative." |
| | /> |
| | )} |
| | /> |
| | |
| | <Controller |
| | name="enableThinking" |
| | control={control} |
| | render={({ field }) => ( |
| | <InputCheckbox |
| | id="enable-thinking" |
| | label="Enable Thinking" |
| | description="Allow the model to show its reasoning process (only if supported by the model)" |
| | checked={field.value} |
| | onChange={field.onChange} |
| | error={errors.enableThinking?.message as string} |
| | /> |
| | )} |
| | /> |
| | |
| | <Button type="submit">Start Conversation</Button> |
| | </form> |
| | ); |
| | } |
| |
|