mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
feat: Support editing all parameters individually for each model (#1562)
This commit is contained in:
parent
aaa53c248a
commit
d6b594dcfb
11 changed files with 572 additions and 308 deletions
|
|
@ -122,7 +122,11 @@ export default class ChatModelManager {
|
|||
fetch: customModel.enableCors ? safeFetch : undefined,
|
||||
organization: await getDecryptedKey(customModel.openAIOrgId || settings.openAIOrgId),
|
||||
},
|
||||
...this.handleOpenAIExtraArgs(isOSeries, settings.maxTokens, settings.temperature),
|
||||
...this.handleOpenAIExtraArgs(
|
||||
isOSeries,
|
||||
customModel.maxTokens ?? settings.maxTokens,
|
||||
customModel.temperature ?? settings.temperature
|
||||
),
|
||||
},
|
||||
[ChatModelProviders.ANTHROPIC]: {
|
||||
anthropicApiKey: await getDecryptedKey(customModel.apiKey || settings.anthropicApiKey),
|
||||
|
|
@ -156,7 +160,11 @@ export default class ChatModelManager {
|
|||
},
|
||||
fetch: customModel.enableCors ? safeFetch : undefined,
|
||||
},
|
||||
...this.handleOpenAIExtraArgs(isOSeries, settings.maxTokens, settings.temperature),
|
||||
...this.handleOpenAIExtraArgs(
|
||||
isOSeries,
|
||||
customModel.maxTokens ?? settings.maxTokens,
|
||||
customModel.temperature ?? settings.temperature
|
||||
),
|
||||
},
|
||||
[ChatModelProviders.COHEREAI]: {
|
||||
apiKey: await getDecryptedKey(customModel.apiKey || settings.cohereApiKey),
|
||||
|
|
@ -231,7 +239,11 @@ export default class ChatModelManager {
|
|||
fetch: customModel.enableCors ? safeFetch : undefined,
|
||||
defaultHeaders: { "dangerously-allow-browser": "true" },
|
||||
},
|
||||
...this.handleOpenAIExtraArgs(isOSeries, settings.maxTokens, settings.temperature),
|
||||
...this.handleOpenAIExtraArgs(
|
||||
isOSeries,
|
||||
customModel.maxTokens ?? settings.maxTokens,
|
||||
customModel.temperature ?? settings.temperature
|
||||
),
|
||||
},
|
||||
[ChatModelProviders.COPILOT_PLUS]: {
|
||||
modelName: modelName,
|
||||
|
|
@ -259,7 +271,12 @@ export default class ChatModelManager {
|
|||
const selectedProviderConfig =
|
||||
providerConfig[customModel.provider as keyof typeof providerConfig] || {};
|
||||
|
||||
// Add token configuration separately to ensure they don't conflict
|
||||
// Get provider-specific parameters (like topP, frequencyPenalty) that the provider supports
|
||||
const providerSpecificParams = this.getProviderSpecificParams(
|
||||
customModel.provider as ChatModelProviders,
|
||||
customModel
|
||||
);
|
||||
|
||||
const tokenConfig = isThinkingEnabled
|
||||
? {
|
||||
maxTokens: customModel.maxTokens ?? settings.maxTokens,
|
||||
|
|
@ -273,6 +290,7 @@ export default class ChatModelManager {
|
|||
const finalConfig = {
|
||||
...baseConfig,
|
||||
...selectedProviderConfig,
|
||||
...providerSpecificParams,
|
||||
...tokenConfig,
|
||||
};
|
||||
|
||||
|
|
@ -301,6 +319,56 @@ export default class ChatModelManager {
|
|||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns provider-specific parameters (like topP, frequencyPenalty) based on what the provider supports
|
||||
* This prevents passing undefined values to providers that don't support them
|
||||
*/
|
||||
private getProviderSpecificParams(provider: ChatModelProviders, customModel: CustomModel) {
|
||||
const params: Record<string, any> = {};
|
||||
|
||||
// Add topP only if defined
|
||||
if (customModel.topP !== undefined) {
|
||||
// These providers support topP
|
||||
if (
|
||||
[
|
||||
ChatModelProviders.OPENAI,
|
||||
ChatModelProviders.AZURE_OPENAI,
|
||||
ChatModelProviders.ANTHROPIC,
|
||||
ChatModelProviders.GOOGLE,
|
||||
ChatModelProviders.OPENROUTERAI,
|
||||
ChatModelProviders.OLLAMA,
|
||||
ChatModelProviders.LM_STUDIO,
|
||||
ChatModelProviders.OPENAI_FORMAT,
|
||||
ChatModelProviders.MISTRAL,
|
||||
ChatModelProviders.DEEPSEEK,
|
||||
].includes(provider)
|
||||
) {
|
||||
params.topP = customModel.topP;
|
||||
}
|
||||
}
|
||||
|
||||
// Add frequencyPenalty only if defined
|
||||
if (customModel.frequencyPenalty !== undefined) {
|
||||
// These providers support frequencyPenalty
|
||||
if (
|
||||
[
|
||||
ChatModelProviders.OPENAI,
|
||||
ChatModelProviders.AZURE_OPENAI,
|
||||
ChatModelProviders.OPENROUTERAI,
|
||||
ChatModelProviders.OLLAMA,
|
||||
ChatModelProviders.LM_STUDIO,
|
||||
ChatModelProviders.OPENAI_FORMAT,
|
||||
ChatModelProviders.MISTRAL,
|
||||
ChatModelProviders.DEEPSEEK,
|
||||
].includes(provider)
|
||||
) {
|
||||
params.frequencyPenalty = customModel.frequencyPenalty;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
// Build a map of modelKey to model config
|
||||
public buildModelMap() {
|
||||
const activeModels = getSettings().activeModels;
|
||||
|
|
|
|||
|
|
@ -105,8 +105,9 @@ export interface CustomModel {
|
|||
stream?: boolean;
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
topP?: number;
|
||||
frequencyPenalty?: number;
|
||||
|
||||
context?: number;
|
||||
projectEnabled?: boolean;
|
||||
plusExclusive?: boolean;
|
||||
believerExclusive?: boolean;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { HelpCircle } from "lucide-react";
|
|||
import { App, Modal, Notice } from "obsidian";
|
||||
import React, { useState } from "react";
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
import { DEFAULT_MODEL_SETTING } from "@/constants";
|
||||
|
||||
interface AddProjectModalContentProps {
|
||||
initialProject?: ProjectConfig;
|
||||
|
|
@ -41,8 +42,8 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
|
|||
systemPrompt: "",
|
||||
projectModelKey: "",
|
||||
modelConfigs: {
|
||||
temperature: 1.0,
|
||||
maxTokens: 1000,
|
||||
temperature: DEFAULT_MODEL_SETTING.TEMPERATURE,
|
||||
maxTokens: DEFAULT_MODEL_SETTING.MAX_TOKENS,
|
||||
},
|
||||
contextSource: {
|
||||
inclusions: "",
|
||||
|
|
@ -231,7 +232,7 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
|
|||
<div className="tw-grid tw-grid-cols-1 tw-gap-4">
|
||||
<FormField label="Temperature">
|
||||
<SettingSlider
|
||||
value={formData.modelConfigs?.temperature ?? 1}
|
||||
value={formData.modelConfigs?.temperature ?? DEFAULT_MODEL_SETTING.TEMPERATURE}
|
||||
onChange={(value) => handleInputChange("modelConfigs.temperature", value)}
|
||||
min={0}
|
||||
max={2}
|
||||
|
|
@ -241,7 +242,7 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
|
|||
</FormField>
|
||||
<FormField label="Token Limit">
|
||||
<SettingSlider
|
||||
value={formData.modelConfigs?.maxTokens ?? 1000}
|
||||
value={formData.modelConfigs?.maxTokens ?? DEFAULT_MODEL_SETTING.MAX_TOKENS}
|
||||
onChange={(value) => handleInputChange("modelConfigs.maxTokens", value)}
|
||||
min={1}
|
||||
max={65000}
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ const ScrollBar = React.forwardRef<
|
|||
className={cn(
|
||||
"tw-flex tw-touch-none tw-select-none tw-transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"tw-h-full tw-w-2.5 tw-border-l tw-border-l-transparent tw-p-[1px]",
|
||||
"tw-h-full tw-w-2.5 tw-border-l tw-border-l-transparent tw-p-px",
|
||||
orientation === "horizontal" &&
|
||||
"tw-h-2.5 tw-flex-col tw-border-t tw-border-t-transparent tw-p-[1px]",
|
||||
"tw-h-2.5 tw-flex-col tw-border-t tw-border-t-transparent tw-p-px",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const Separator = React.forwardRef<
|
|||
orientation={orientation}
|
||||
className={cn(
|
||||
"tw-shrink-0 tw-bg-[var(--background-modifier-border)]",
|
||||
orientation === "horizontal" ? "tw-h-[1px] tw-w-full" : "tw-h-full tw-w-[1px]",
|
||||
orientation === "horizontal" ? "tw-h-px tw-w-full" : "tw-h-full tw-w-px",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@ export const PLUS_UTM_MEDIUMS = {
|
|||
};
|
||||
export type PlusUtmMedium = (typeof PLUS_UTM_MEDIUMS)[keyof typeof PLUS_UTM_MEDIUMS];
|
||||
|
||||
export const DEFAULT_MODEL_SETTING = {
|
||||
MAX_TOKENS: 6000,
|
||||
TEMPERATURE: 0.1,
|
||||
};
|
||||
|
||||
export enum ChatModels {
|
||||
COPILOT_PLUS_FLASH = "copilot-plus-flash",
|
||||
GPT_41 = "gpt-4.1",
|
||||
|
|
@ -653,8 +658,8 @@ export const DEFAULT_SETTINGS: CopilotSettings = {
|
|||
defaultChainType: ChainType.LLM_CHAIN,
|
||||
defaultModelKey: ChatModels.GPT_41 + "|" + ChatModelProviders.OPENAI,
|
||||
embeddingModelKey: EmbeddingModels.OPENAI_EMBEDDING_SMALL + "|" + EmbeddingModelProviders.OPENAI,
|
||||
temperature: 0.1,
|
||||
maxTokens: 6000,
|
||||
temperature: DEFAULT_MODEL_SETTING.TEMPERATURE,
|
||||
maxTokens: DEFAULT_MODEL_SETTING.MAX_TOKENS,
|
||||
contextTurns: 15,
|
||||
userSystemPrompt: "",
|
||||
openAIProxyBaseUrl: "",
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
import { CustomModel } from "@/aiParams";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { FormField } from "@/components/ui/form-field";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import {
|
||||
DEFAULT_MODEL_SETTING,
|
||||
MODEL_CAPABILITIES,
|
||||
ModelCapability,
|
||||
Provider,
|
||||
|
|
@ -20,28 +14,33 @@ import {
|
|||
ProviderSettingsKeyMap,
|
||||
SettingKeyProviders,
|
||||
} from "@/constants";
|
||||
import { useTab } from "@/contexts/TabContext";
|
||||
import { getSettings } from "@/settings/model";
|
||||
import { debounce, getProviderInfo, getProviderLabel } from "@/utils";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { SettingSlider } from "@/components/ui/setting-slider";
|
||||
import { App, Modal } from "obsidian";
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
|
||||
interface ModelEditDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
model: CustomModel | null;
|
||||
onUpdate: (originalModel: CustomModel, updatedModel: CustomModel) => void;
|
||||
interface ModelEditModalContentProps {
|
||||
model: CustomModel;
|
||||
isEmbeddingModel: boolean;
|
||||
onUpdate: (
|
||||
isEmbeddingModel: boolean,
|
||||
originalModel: CustomModel,
|
||||
updatedModel: CustomModel
|
||||
) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const ModelEditDialog: React.FC<ModelEditDialogProps> = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
export const ModelEditModalContent: React.FC<ModelEditModalContentProps> = ({
|
||||
model,
|
||||
onUpdate,
|
||||
onCancel,
|
||||
isEmbeddingModel,
|
||||
}) => {
|
||||
const { modalContainer } = useTab();
|
||||
const [localModel, setLocalModel] = useState<CustomModel | null>(model);
|
||||
const [originalModel, setOriginalModel] = useState<CustomModel | null>(model);
|
||||
const [localModel, setLocalModel] = useState<CustomModel>(model);
|
||||
const [originalModel, setOriginalModel] = useState<CustomModel>(model);
|
||||
const [providerInfo, setProviderInfo] = useState<ProviderMetadata>({} as ProviderMetadata);
|
||||
const settings = getSettings();
|
||||
|
||||
|
|
@ -52,7 +51,7 @@ export const ModelEditDialog: React.FC<ModelEditDialogProps> = ({
|
|||
useEffect(() => {
|
||||
setLocalModel(model);
|
||||
setOriginalModel(model);
|
||||
if (model?.provider) {
|
||||
if (model.provider) {
|
||||
setProviderInfo(getProviderInfo(model.provider));
|
||||
}
|
||||
}, [model]);
|
||||
|
|
@ -60,19 +59,16 @@ export const ModelEditDialog: React.FC<ModelEditDialogProps> = ({
|
|||
// Debounce the onUpdate callback
|
||||
const debouncedOnUpdate = useMemo(
|
||||
() =>
|
||||
debounce((currentOriginalModel: CustomModel | null, updatedModel: CustomModel) => {
|
||||
if (currentOriginalModel) {
|
||||
onUpdate(currentOriginalModel, updatedModel);
|
||||
}
|
||||
debounce((currentOriginalModel: CustomModel, updatedModel: CustomModel) => {
|
||||
onUpdate(isEmbeddingModel, currentOriginalModel, updatedModel);
|
||||
}, 500),
|
||||
[onUpdate]
|
||||
[isEmbeddingModel, onUpdate]
|
||||
);
|
||||
|
||||
// Function to update local state immediately
|
||||
const handleLocalUpdate = useCallback(
|
||||
(field: keyof CustomModel, value: any) => {
|
||||
setLocalModel((prevModel) => {
|
||||
if (!prevModel) return null;
|
||||
const updatedModel = {
|
||||
...prevModel,
|
||||
[field]: value,
|
||||
|
|
@ -109,147 +105,162 @@ export const ModelEditDialog: React.FC<ModelEditDialogProps> = ({
|
|||
})) as Array<{ id: ModelCapability; label: string; description: string }>;
|
||||
|
||||
const displayApiKey = localModel.apiKey || getDefaultApiKey(localModel.provider as Provider);
|
||||
const showOtherParameters =
|
||||
!isEmbeddingModel &&
|
||||
localModel.provider !== "copilot-plus" &&
|
||||
localModel.provider !== "copilot-plus-jina";
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:tw-max-w-[425px]" container={modalContainer}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Model Settings - {localModel.name}</DialogTitle>
|
||||
<DialogDescription>Customize model parameters.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="tw-space-y-3 tw-p-4">
|
||||
<div className="tw-mb-4">
|
||||
<h2 className="tw-text-xl tw-font-bold">Model Settings - {localModel.name}</h2>
|
||||
<p className="tw-text-sm tw-text-muted">Customize model parameters</p>
|
||||
</div>
|
||||
|
||||
<div className="tw-space-y-3">
|
||||
<FormField label="Model Name" required>
|
||||
<Input
|
||||
type="text"
|
||||
disabled={localModel.core}
|
||||
value={localModel.name}
|
||||
onChange={(e) => handleLocalUpdate("name", e.target.value)}
|
||||
placeholder="Enter model name"
|
||||
/>
|
||||
</FormField>
|
||||
<div className="tw-space-y-3">
|
||||
<FormField label="Model Name" required>
|
||||
<Input
|
||||
type="text"
|
||||
disabled={localModel.core}
|
||||
value={localModel.name}
|
||||
onChange={(e) => handleLocalUpdate("name", e.target.value)}
|
||||
placeholder="Enter model name"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label={
|
||||
<div className="tw-flex tw-items-center tw-gap-1.5">
|
||||
<span className="tw-leading-none">Display Name</span>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-size-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
|
||||
<div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
|
||||
<div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
|
||||
<div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
|
||||
<div className="tw-text-[12px]">
|
||||
Example:
|
||||
<li>Direct-Paid:Ds-r1</li>
|
||||
<li>OpenRouter-Paid:Ds-r1</li>
|
||||
<li>Perplexity-Paid:lg</li>
|
||||
</div>
|
||||
<FormField
|
||||
label={
|
||||
<div className="tw-flex tw-items-center tw-gap-1.5">
|
||||
<span className="tw-leading-none">Display Name</span>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-size-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
|
||||
<div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
|
||||
<div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
|
||||
<div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
|
||||
<div className="tw-text-[12px]">
|
||||
Example:
|
||||
<li>Direct-Paid:Ds-r1</li>
|
||||
<li>OpenRouter-Paid:Ds-r1</li>
|
||||
<li>Perplexity-Paid:lg</li>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Custom display name (optional)"
|
||||
value={localModel.displayName || ""}
|
||||
onChange={(e) => handleLocalUpdate("displayName", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Provider">
|
||||
<Input type="text" value={getProviderLabel(localModel.provider)} disabled />
|
||||
</FormField>
|
||||
|
||||
<FormField label="Base URL" description="Leave it blank, unless you are using a proxy.">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={getPlaceholderUrl()}
|
||||
value={localModel.baseUrl || ""}
|
||||
onChange={(e) => handleLocalUpdate("baseUrl", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="API Key">
|
||||
<PasswordInput
|
||||
placeholder={`Enter ${providerInfo.label || "Provider"} API Key`}
|
||||
value={displayApiKey}
|
||||
onChange={(value) => handleLocalUpdate("apiKey", value)}
|
||||
/>
|
||||
{providerInfo.keyManagementURL && (
|
||||
<p className="tw-text-xs tw-text-muted">
|
||||
<a href={providerInfo.keyManagementURL} target="_blank" rel="noopener noreferrer">
|
||||
Get {providerInfo.label} API Key
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label={
|
||||
<div className="tw-flex tw-items-center tw-gap-1.5">
|
||||
<span className="tw-leading-none">Model Capabilities</span>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-size-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
|
||||
<div className="tw-text-sm tw-text-muted">
|
||||
Only used to display model capabilities, does not affect model functionality
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="tw-flex tw-items-center tw-gap-4">
|
||||
{capabilityOptions.map(({ id, label, description }) => (
|
||||
<div key={id} className="tw-flex tw-items-center tw-gap-2">
|
||||
<Checkbox
|
||||
id={id}
|
||||
checked={localModel.capabilities?.includes(id)}
|
||||
onCheckedChange={(checked) => {
|
||||
const newCapabilities = localModel.capabilities || [];
|
||||
const value = checked
|
||||
? [...newCapabilities, id]
|
||||
: newCapabilities.filter((cap) => cap !== id);
|
||||
handleLocalUpdate("capabilities", value);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={id} className="tw-text-sm">
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>{label}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{description}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</FormField>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Custom display name (optional)"
|
||||
value={localModel.displayName || ""}
|
||||
onChange={(e) => handleLocalUpdate("displayName", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{/*<FormField
|
||||
<FormField label="Provider">
|
||||
<Input type="text" value={getProviderLabel(localModel.provider)} disabled />
|
||||
</FormField>
|
||||
|
||||
<FormField label="Base URL" description="Leave it blank, unless you are using a proxy.">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={getPlaceholderUrl()}
|
||||
value={localModel.baseUrl || ""}
|
||||
onChange={(e) => handleLocalUpdate("baseUrl", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="API Key">
|
||||
<PasswordInput
|
||||
placeholder={`Enter ${providerInfo.label || "Provider"} API Key`}
|
||||
value={displayApiKey}
|
||||
onChange={(value) => handleLocalUpdate("apiKey", value)}
|
||||
/>
|
||||
{providerInfo.keyManagementURL && (
|
||||
<p className="tw-text-xs tw-text-muted">
|
||||
<a href={providerInfo.keyManagementURL} target="_blank" rel="noopener noreferrer">
|
||||
Get {providerInfo.label} API Key
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{showOtherParameters && (
|
||||
<>
|
||||
<FormField
|
||||
label={
|
||||
<div className="tw-flex tw-items-center tw-gap-2">
|
||||
Temperature
|
||||
<div className="tw-flex tw-items-center tw-gap-1.5">
|
||||
<span className="tw-leading-none">Model Capabilities</span>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-h-4 tw-w-4 tw-text-muted" />
|
||||
<HelpCircle className="tw-size-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
|
||||
<div className="tw-text-sm tw-text-muted">
|
||||
Only used to display model capabilities, does not affect model
|
||||
functionality
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="tw-flex tw-items-center tw-gap-4">
|
||||
{capabilityOptions.map(({ id, label, description }) => (
|
||||
<div key={id} className="tw-flex tw-items-center tw-gap-2">
|
||||
<Checkbox
|
||||
id={id}
|
||||
checked={localModel.capabilities?.includes(id)}
|
||||
onCheckedChange={(checked) => {
|
||||
const newCapabilities = localModel.capabilities || [];
|
||||
const value = checked
|
||||
? [...newCapabilities, id]
|
||||
: newCapabilities.filter((cap) => cap !== id);
|
||||
handleLocalUpdate("capabilities", value);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={id} className="tw-text-sm">
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>{label}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{description}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label={
|
||||
<div className="tw-flex tw-items-center tw-gap-2">
|
||||
Token limit
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-size-4 tw-text-muted" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
Controls randomness: 0 is focused and deterministic, 2 is more creative
|
||||
<div className="tw-w-[300px]">
|
||||
<p>
|
||||
The maximum number of <em>output tokens</em> to generate. Default is{" "}
|
||||
{DEFAULT_MODEL_SETTING.MAX_TOKENS}.
|
||||
</p>
|
||||
<em>
|
||||
This number plus the length of your prompt (input tokens) must be
|
||||
smaller than the context window of the model.
|
||||
</em>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
|
@ -257,25 +268,30 @@ export const ModelEditDialog: React.FC<ModelEditDialogProps> = ({
|
|||
}
|
||||
>
|
||||
<SettingSlider
|
||||
value={localModel.temperature ?? 0.1}
|
||||
onChange={(value) => handleLocalUpdate("temperature", value)}
|
||||
max={2}
|
||||
value={
|
||||
localModel.maxTokens ?? settings.maxTokens ?? DEFAULT_MODEL_SETTING.MAX_TOKENS
|
||||
}
|
||||
onChange={(value) => handleLocalUpdate("maxTokens", value)}
|
||||
min={0}
|
||||
step={0.1}
|
||||
max={65000}
|
||||
step={100}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label={
|
||||
<div className="tw-flex tw-items-center tw-gap-2">
|
||||
Context
|
||||
Temperature
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-h-4 tw-w-4 tw-text-muted" />
|
||||
<HelpCircle className="tw-size-4 tw-text-muted" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
Maximum number of tokens to use for context
|
||||
<div className="tw-max-w-[300px]">
|
||||
Default is {DEFAULT_MODEL_SETTING.TEMPERATURE}. Higher values will result
|
||||
in more creativeness, but also more mistakes. Set to 0 for no randomness.
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
|
@ -283,35 +299,132 @@ export const ModelEditDialog: React.FC<ModelEditDialogProps> = ({
|
|||
}
|
||||
>
|
||||
<SettingSlider
|
||||
value={localModel.context ?? 1000}
|
||||
onChange={(value) => handleLocalUpdate("context", value)}
|
||||
max={65000}
|
||||
value={
|
||||
localModel.temperature ??
|
||||
settings.temperature ??
|
||||
DEFAULT_MODEL_SETTING.TEMPERATURE
|
||||
}
|
||||
onChange={(value) => handleLocalUpdate("temperature", value)}
|
||||
max={2}
|
||||
min={0}
|
||||
step={100}
|
||||
step={0.05}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<div className="tw-flex tw-items-center tw-justify-between tw-py-2">
|
||||
<div className="tw-flex tw-items-center tw-gap-2">
|
||||
<span className="tw-text-sm tw-font-medium">Stream output</span>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-h-4 tw-w-4 tw-text-muted" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
Enable streaming responses from the model
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<SettingSwitch
|
||||
checked={localModel.stream ?? true}
|
||||
onCheckedChange={(checked) => handleLocalUpdate("stream", checked)}
|
||||
/>
|
||||
</div>*/}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<FormField
|
||||
label={
|
||||
<div className="tw-flex tw-items-center tw-gap-2">
|
||||
Top-P
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-size-4 tw-text-muted" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<div className="tw-w-[300px]">
|
||||
Default value is 0.9, the smaller the value, the less variety in the
|
||||
answers, the easier to understand, the larger the value, the larger the
|
||||
range of the Al's vocabulary, the more diverse
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SettingSlider
|
||||
value={localModel.topP ?? 0.9}
|
||||
onChange={(value) => handleLocalUpdate("topP", value)}
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.05}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label={
|
||||
<div className="tw-flex tw-items-center tw-gap-2">
|
||||
Frequency Penalty
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<HelpCircle className="tw-size-4 tw-text-muted" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<div className="tw-w-[300px]">
|
||||
<p>
|
||||
The frequency penalty parameter tells the model not to repeat a word
|
||||
that has already been used multiple times in the conversation.
|
||||
</p>
|
||||
<em>
|
||||
The higher the value, the more the model is penalized for repeating
|
||||
words.
|
||||
</em>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SettingSlider
|
||||
value={localModel.frequencyPenalty ?? 0}
|
||||
onChange={(value) => handleLocalUpdate("frequencyPenalty", value)}
|
||||
max={2}
|
||||
min={0}
|
||||
step={0.05}
|
||||
/>
|
||||
</FormField>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export class ModelEditModal extends Modal {
|
||||
private root: Root;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
private model: CustomModel,
|
||||
private isEmbeddingModel: boolean,
|
||||
private onUpdate: (
|
||||
isEmbeddingModel: boolean,
|
||||
originalModel: CustomModel,
|
||||
updatedModel: CustomModel
|
||||
) => void
|
||||
) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
this.root = createRoot(contentEl);
|
||||
|
||||
const handleUpdate = (
|
||||
isEmbeddingModel: boolean,
|
||||
originalModel: CustomModel,
|
||||
updatedModel: CustomModel
|
||||
) => {
|
||||
this.onUpdate(isEmbeddingModel, originalModel, updatedModel);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
this.close();
|
||||
};
|
||||
|
||||
this.root.render(
|
||||
<ModelEditModalContent
|
||||
model={this.model}
|
||||
isEmbeddingModel={this.isEmbeddingModel}
|
||||
onUpdate={handleUpdate}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.root.unmount();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,48 @@ import { BUILTIN_CHAT_MODELS, BUILTIN_EMBEDDING_MODELS } from "@/constants";
|
|||
import EmbeddingManager from "@/LLMProviders/embeddingManager";
|
||||
import ProjectManager from "@/LLMProviders/projectManager";
|
||||
import { logError } from "@/logger";
|
||||
import { setSettings, updateSetting, useSettingsValue } from "@/settings/model";
|
||||
import { CopilotSettings, setSettings, updateSetting, useSettingsValue } from "@/settings/model";
|
||||
import { ModelAddDialog } from "@/settings/v2/components/ModelAddDialog";
|
||||
import { ModelEditDialog } from "@/settings/v2/components/ModelEditDialog";
|
||||
import { ModelEditModal } from "@/settings/v2/components/ModelEditDialog";
|
||||
import { ModelTable } from "@/settings/v2/components/ModelTable";
|
||||
import { omit } from "@/utils";
|
||||
import { Notice } from "obsidian";
|
||||
import React, { useState } from "react";
|
||||
|
||||
export const ModelSettings: React.FC = () => {
|
||||
const settings = useSettingsValue();
|
||||
const [editingModel, setEditingModel] = useState<CustomModel | null>(null);
|
||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||
const [showAddEmbeddingDialog, setShowAddEmbeddingDialog] = useState(false);
|
||||
|
||||
const onCopyModel = (model: CustomModel, isEmbeddingModel: boolean = false) => {
|
||||
const newModel: CustomModel = {
|
||||
...omit(model, [
|
||||
"isBuiltIn",
|
||||
"core",
|
||||
"projectEnabled",
|
||||
"plusExclusive",
|
||||
"believerExclusive",
|
||||
"capabilities",
|
||||
"displayName",
|
||||
"dimensions",
|
||||
]),
|
||||
name: `${model.name} (copy)`,
|
||||
};
|
||||
|
||||
const settingField: keyof CopilotSettings = isEmbeddingModel
|
||||
? "activeEmbeddingModels"
|
||||
: "activeModels";
|
||||
|
||||
updateSetting(settingField, [...settings[settingField], newModel]);
|
||||
};
|
||||
|
||||
const handleModelReorder = (newModels: CustomModel[], isEmbeddingModel: boolean = false) => {
|
||||
const settingField: keyof CopilotSettings = isEmbeddingModel
|
||||
? "activeEmbeddingModels"
|
||||
: "activeModels";
|
||||
updateSetting(settingField, newModels);
|
||||
};
|
||||
|
||||
const onDeleteModel = (modelKey: string) => {
|
||||
const [modelName, provider] = modelKey.split("|");
|
||||
const updatedActiveModels = settings.activeModels.filter(
|
||||
|
|
@ -37,14 +66,22 @@ export const ModelSettings: React.FC = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const handleModelUpdate = (originalModel: CustomModel, updatedModel: CustomModel) => {
|
||||
const modelIndex = settings.activeModels.findIndex(
|
||||
const handleModelUpdate = (
|
||||
isEmbeddingModel: boolean,
|
||||
originalModel: CustomModel,
|
||||
updatedModel: CustomModel
|
||||
) => {
|
||||
const settingField: keyof CopilotSettings = isEmbeddingModel
|
||||
? "activeEmbeddingModels"
|
||||
: "activeModels";
|
||||
|
||||
const modelIndex = settings[settingField].findIndex(
|
||||
(m) => m.name === originalModel.name && m.provider === originalModel.provider
|
||||
);
|
||||
if (modelIndex !== -1) {
|
||||
const updatedModels = [...settings.activeModels];
|
||||
const updatedModels = [...settings[settingField]];
|
||||
updatedModels[modelIndex] = updatedModel;
|
||||
updateSetting("activeModels", updatedModels);
|
||||
updateSetting(settingField, updatedModels);
|
||||
} else {
|
||||
new Notice("Could not find model to update");
|
||||
logError("Could not find model to update:", originalModel);
|
||||
|
|
@ -59,10 +96,6 @@ export const ModelSettings: React.FC = () => {
|
|||
updateSetting("activeModels", updatedModels);
|
||||
};
|
||||
|
||||
const handleModelReorder = (newModels: CustomModel[]) => {
|
||||
updateSetting("activeModels", newModels);
|
||||
};
|
||||
|
||||
const onDeleteEmbeddingModel = (modelKey: string) => {
|
||||
const [modelName, provider] = modelKey.split("|");
|
||||
const updatedModels = settings.activeEmbeddingModels.filter(
|
||||
|
|
@ -78,10 +111,6 @@ export const ModelSettings: React.FC = () => {
|
|||
updateSetting("activeEmbeddingModels", updatedModels);
|
||||
};
|
||||
|
||||
const handleEmbeddingModelReorder = (newModels: CustomModel[]) => {
|
||||
updateSetting("activeEmbeddingModels", newModels);
|
||||
};
|
||||
|
||||
const handleRefreshChatModels = () => {
|
||||
// Get all custom models (non-built-in models)
|
||||
const customModels = settings.activeModels.filter((model) => !model.isBuiltIn);
|
||||
|
|
@ -106,29 +135,27 @@ export const ModelSettings: React.FC = () => {
|
|||
new Notice("Embedding models refreshed successfully");
|
||||
};
|
||||
|
||||
const handleEditModel = (model: CustomModel, isEmbeddingModel: boolean = false) => {
|
||||
const modal = new ModelEditModal(app, model, isEmbeddingModel, handleModelUpdate);
|
||||
modal.open();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tw-space-y-4">
|
||||
<section>
|
||||
<div className="tw-mb-3 tw-text-xl tw-font-bold">Chat Models</div>
|
||||
<ModelTable
|
||||
models={settings.activeModels}
|
||||
onEdit={setEditingModel}
|
||||
onEdit={(model) => handleEditModel(model)}
|
||||
onCopy={(model) => onCopyModel(model)}
|
||||
onDelete={onDeleteModel}
|
||||
onAdd={() => setShowAddDialog(true)}
|
||||
onUpdateModel={handleTableUpdate}
|
||||
onReorderModels={handleModelReorder}
|
||||
onReorderModels={(newModels) => handleModelReorder(newModels)}
|
||||
onRefresh={handleRefreshChatModels}
|
||||
title="Chat Model"
|
||||
/>
|
||||
|
||||
{/* model edit dialog*/}
|
||||
<ModelEditDialog
|
||||
open={!!editingModel}
|
||||
onOpenChange={(open) => !open && setEditingModel(null)}
|
||||
model={editingModel}
|
||||
onUpdate={handleModelUpdate}
|
||||
/>
|
||||
|
||||
{/* model add dialog */}
|
||||
<ModelAddDialog
|
||||
open={showAddDialog}
|
||||
|
|
@ -143,38 +170,6 @@ export const ModelSettings: React.FC = () => {
|
|||
/>
|
||||
|
||||
<div className="tw-space-y-4">
|
||||
<SettingItem
|
||||
type="slider"
|
||||
title="Temperature"
|
||||
description="Default is 0.1. Higher values will result in more creativeness, but also more mistakes. Set to 0 for no randomness."
|
||||
value={settings.temperature}
|
||||
onChange={(value) => updateSetting("temperature", value)}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.05}
|
||||
/>
|
||||
|
||||
<SettingItem
|
||||
type="slider"
|
||||
title="Token limit"
|
||||
description={
|
||||
<>
|
||||
<p>
|
||||
The maximum number of <em>output tokens</em> to generate. Default is 6000.
|
||||
</p>
|
||||
<em>
|
||||
This number plus the length of your prompt (input tokens) must be smaller than the
|
||||
context window of the model.
|
||||
</em>
|
||||
</>
|
||||
}
|
||||
value={settings.maxTokens}
|
||||
onChange={(value) => updateSetting("maxTokens", value)}
|
||||
min={0}
|
||||
max={65000}
|
||||
step={100}
|
||||
/>
|
||||
|
||||
<SettingItem
|
||||
type="slider"
|
||||
title="Conversation turns in context"
|
||||
|
|
@ -192,10 +187,12 @@ export const ModelSettings: React.FC = () => {
|
|||
<div className="tw-mb-3 tw-text-xl tw-font-bold">Embedding Models</div>
|
||||
<ModelTable
|
||||
models={settings.activeEmbeddingModels}
|
||||
onEdit={(model) => handleEditModel(model, true)}
|
||||
onDelete={onDeleteEmbeddingModel}
|
||||
onCopy={(model) => onCopyModel(model, true)}
|
||||
onAdd={() => setShowAddEmbeddingDialog(true)}
|
||||
onUpdateModel={handleEmbeddingModelUpdate}
|
||||
onReorderModels={handleEmbeddingModelReorder}
|
||||
onReorderModels={(newModels) => handleModelReorder(newModels, true)}
|
||||
onRefresh={handleRefreshEmbeddingModels}
|
||||
title="Embedding Model"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -36,17 +36,27 @@ import { CSS } from "@dnd-kit/utilities";
|
|||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Copy,
|
||||
Eye,
|
||||
Globe,
|
||||
GripVertical,
|
||||
Lightbulb,
|
||||
LucideProps,
|
||||
MoreVertical,
|
||||
Pencil,
|
||||
PencilLine,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import React, { ForwardRefExoticComponent, RefAttributes } from "react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useContainerContext } from "@/settings/v2/components/ContainerContext";
|
||||
|
||||
const CAPABILITY_ICONS: Record<
|
||||
ModelCapability,
|
||||
|
|
@ -110,27 +120,25 @@ const renderCapabilities = (model: CustomModel) => {
|
|||
);
|
||||
};
|
||||
|
||||
interface ModelTableProps {
|
||||
models: CustomModel[];
|
||||
onEdit?: (model: CustomModel) => void;
|
||||
onDelete: (modelKey: string) => void;
|
||||
onAdd: () => void;
|
||||
onUpdateModel: (model: CustomModel) => void;
|
||||
onReorderModels?: (newModels: CustomModel[]) => void;
|
||||
onRefresh?: () => void;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface ModelCardProps {
|
||||
model: CustomModel;
|
||||
onEdit?: () => void;
|
||||
onDelete?: () => void;
|
||||
onEdit?: (model: CustomModel) => void;
|
||||
onCopy?: (model: CustomModel) => void;
|
||||
onDelete: (modelKey: string) => void;
|
||||
onUpdateModel: (model: CustomModel) => void;
|
||||
id: string;
|
||||
}
|
||||
|
||||
const ModelCard: React.FC<ModelCardProps> = ({ model, onEdit, onDelete, onUpdateModel, id }) => {
|
||||
const ModelCard: React.FC<ModelCardProps> = ({
|
||||
model,
|
||||
onEdit,
|
||||
onCopy,
|
||||
onDelete,
|
||||
onUpdateModel,
|
||||
id,
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const container = useContainerContext();
|
||||
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id,
|
||||
|
|
@ -205,24 +213,57 @@ const ModelCard: React.FC<ModelCardProps> = ({ model, onEdit, onDelete, onUpdate
|
|||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit();
|
||||
onEdit(model);
|
||||
}}
|
||||
>
|
||||
<Pencil className="tw-size-4" />
|
||||
</Button>
|
||||
)}
|
||||
{onDelete && !model.core && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
>
|
||||
<Trash2 className="tw-size-4" />
|
||||
</Button>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<MoreVertical className="tw-size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" container={container}>
|
||||
{onEdit && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit(model);
|
||||
}}
|
||||
>
|
||||
<PencilLine className="tw-mr-2 tw-size-4" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{onCopy && !model.core && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onCopy(model);
|
||||
}}
|
||||
>
|
||||
<Copy className="tw-mr-2 tw-size-4" />
|
||||
Copy
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{!model.core && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(getModelKeyFromModel(model));
|
||||
}}
|
||||
className="tw-text-error"
|
||||
>
|
||||
<Trash2 className="tw-mr-2 tw-size-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
|
@ -261,23 +302,27 @@ const ModelCard: React.FC<ModelCardProps> = ({ model, onEdit, onDelete, onUpdate
|
|||
);
|
||||
};
|
||||
|
||||
const SortableTableRow: React.FC<{
|
||||
const DesktopSortableTableRow: React.FC<{
|
||||
model: CustomModel;
|
||||
onEdit?: (model: CustomModel) => void;
|
||||
onCopy?: (model: CustomModel) => void;
|
||||
onDelete: (modelKey: string) => void;
|
||||
onUpdateModel: (model: CustomModel) => void;
|
||||
isEmbeddingModel: boolean;
|
||||
}> = ({ model, onEdit, onDelete, onUpdateModel, isEmbeddingModel }) => {
|
||||
}> = ({ model, onEdit, onCopy, onDelete, onUpdateModel, isEmbeddingModel }) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: getModelKeyFromModel(model),
|
||||
disabled: model.core,
|
||||
});
|
||||
const container = useContainerContext();
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
const showDropdownMenu = onEdit || !model.core || (onCopy && !model.core);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
ref={setNodeRef}
|
||||
|
|
@ -334,15 +379,40 @@ const SortableTableRow: React.FC<{
|
|||
<Pencil className="tw-size-4" />
|
||||
</Button>
|
||||
)}
|
||||
{!model.core && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onDelete(getModelKeyFromModel(model))}
|
||||
className="tw-shadow-sm tw-transition-shadow hover:tw-shadow-md"
|
||||
>
|
||||
<Trash2 className="tw-size-4" />
|
||||
</Button>
|
||||
|
||||
{showDropdownMenu && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<MoreVertical className="tw-size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" container={container}>
|
||||
{onEdit && (
|
||||
<DropdownMenuItem onClick={() => onEdit(model)}>
|
||||
<PencilLine className="tw-mr-2 tw-size-4" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{onCopy && !model.core && (
|
||||
<DropdownMenuItem onClick={() => onCopy(model)}>
|
||||
<Copy className="tw-mr-2 tw-size-4" />
|
||||
Copy
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{!model.core && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete(getModelKeyFromModel(model))}
|
||||
className="tw-text-error"
|
||||
>
|
||||
<Trash2 className="tw-mr-2 tw-size-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
|
@ -350,9 +420,22 @@ const SortableTableRow: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
interface ModelTableProps {
|
||||
models: CustomModel[];
|
||||
onEdit?: (model: CustomModel) => void;
|
||||
onCopy?: (model: CustomModel) => void;
|
||||
onDelete: (modelKey: string) => void;
|
||||
onAdd: () => void;
|
||||
onUpdateModel: (model: CustomModel) => void;
|
||||
onReorderModels?: (newModels: CustomModel[]) => void;
|
||||
onRefresh?: () => void;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const ModelTable: React.FC<ModelTableProps> = ({
|
||||
models,
|
||||
onEdit,
|
||||
onCopy,
|
||||
onDelete,
|
||||
onAdd,
|
||||
onUpdateModel,
|
||||
|
|
@ -457,8 +540,9 @@ export const ModelTable: React.FC<ModelTableProps> = ({
|
|||
key={getModelKeyFromModel(model)}
|
||||
id={getModelKeyFromModel(model)}
|
||||
model={model}
|
||||
onEdit={onEdit ? () => onEdit(model) : undefined}
|
||||
onDelete={() => onDelete(getModelKeyFromModel(model))}
|
||||
onEdit={onEdit}
|
||||
onCopy={onCopy}
|
||||
onDelete={onDelete}
|
||||
onUpdateModel={onUpdateModel}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -497,10 +581,11 @@ export const ModelTable: React.FC<ModelTableProps> = ({
|
|||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{models.map((model) => (
|
||||
<SortableTableRow
|
||||
<DesktopSortableTableRow
|
||||
key={getModelKeyFromModel(model)}
|
||||
model={model}
|
||||
onEdit={onEdit ? () => onEdit(model) : undefined}
|
||||
onEdit={onEdit}
|
||||
onCopy={onCopy}
|
||||
onDelete={onDelete}
|
||||
onUpdateModel={onUpdateModel}
|
||||
isEmbeddingModel={isEmbeddingModel}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ describe("projectUtils", () => {
|
|||
projectModelKey: "gpt-3.5-turbo",
|
||||
modelConfigs: {
|
||||
temperature: 0.7,
|
||||
maxTokens: 1000,
|
||||
maxTokens: 6000,
|
||||
},
|
||||
contextSource: {
|
||||
inclusions: "src/**/*.tsx",
|
||||
|
|
|
|||
|
|
@ -487,12 +487,6 @@ export async function safeFetch(url: string, options: RequestInit = {}): Promise
|
|||
// Remove content-length if it exists
|
||||
delete (headers as Record<string, string>)["content-length"];
|
||||
|
||||
if (typeof options.body === "string") {
|
||||
const newBody = JSON.parse(options.body ?? {});
|
||||
delete newBody["frequency_penalty"];
|
||||
options.body = JSON.stringify(newBody);
|
||||
}
|
||||
|
||||
logInfo("==== safeFetch method request ====");
|
||||
|
||||
const method = options.method?.toUpperCase() || "POST";
|
||||
|
|
|
|||
Loading…
Reference in a new issue