mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
commit730d0c82b1Author: Logan Yang <logancyang@gmail.com> Date: Mon Apr 21 20:13:21 2025 -0700 Switch insert and copy button positions (#1463) commit4efdc941f2Author: Logan Yang <logancyang@gmail.com> Date: Mon Apr 21 16:15:50 2025 -0700 Fix canvas read (#1462) commit93e4a04ef1Author: Emt-lin <41323133+Emt-lin@users.noreply.github.com> Date: Tue Apr 22 07:13:47 2025 +0800 fix: Add a new line when press the Enter key on mobile. (#1450) commit3c65d79a74Author: Logan Yang <logancyang@gmail.com> Date: Mon Apr 21 15:38:36 2025 -0700 Implement canvas adaptor (#1461) * Implement canvas parser * Refactor context processing and custom prompt handling - Update `processContextNotes` to exclude notes already processed by custom prompts using a set of excluded note paths. - Modify `extractVariablesFromPrompt` to return both a variables map and a set of included files. - Adjust `processPrompt` to track included files during prompt processing. - Update `Chat` component to handle context notes more efficiently by avoiding duplication of processed notes. - Ensure that the `FileParserManager` is initialized with the vault in `main.ts`. * Include canvas files in note context modal * Fix tests commit6a9085a71cAuthor: Logan Yang <logancyang@gmail.com> Date: Sun Apr 20 16:14:54 2025 -0700 Add a toggle to turn custom prompt templating off (#1460) commit448a64c383Author: Logan Yang <logancyang@gmail.com> Date: Sun Apr 20 15:52:42 2025 -0700 Update dependencies and support gpt 4.1 series, o4-mini and grok 3 (#1459) commit1d46ab90b0Author: Logan Yang <logancyang@gmail.com> Date: Sun Apr 20 12:14:56 2025 -0700 Fix image in note logic (#1457) * Refactor image processing in note context * Add passMarkdownImages setting commitf4bf334c27Author: Felix Haase <felix.haase@feki.de> Date: Fri Apr 18 08:19:35 2025 +0200 Ollama ApiKey support (#1421) * Update @langchain/ollama and ollama * Ollama: support api keys by passing headers commit19d8b50a74Author: Emt-lin <41323133+Emt-lin@users.noreply.github.com> Date: Fri Apr 18 14:13:25 2025 +0800 refactor: Optimize some user experiences. (#1441) commitfe9b9311baAuthor: Zero Liu <zerolxy@gmail.com> Date: Sun Apr 13 17:40:42 2025 -0700 Improve custom command (v3) (#1446) * Trim responses and auto focus replace on finishing * Make textarea auto scroll to bottom when generating * Support custom prompt syntax in custom command * Support follow up instruction * Fix unit test * Allow followup instruction to use custom prompt syntax commit648412a914Author: Zero Liu <zerolxy@gmail.com> Date: Wed Apr 2 21:44:37 2025 -0700 Add update notification (#1415) commitcb4510e920Author: Zero Liu <zerolxy@gmail.com> Date: Wed Apr 2 21:43:26 2025 -0700 Add user_id to broca requests (#1414)
323 lines
12 KiB
TypeScript
323 lines
12 KiB
TypeScript
import React, { useEffect, useState, useMemo, useCallback } from "react";
|
|
import { useTab } from "@/contexts/TabContext";
|
|
import { CustomModel } from "@/aiParams";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { HelpCircle } from "lucide-react";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
import { FormField } from "@/components/ui/form-field";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { Label } from "@/components/ui/label";
|
|
import {
|
|
MODEL_CAPABILITIES,
|
|
ModelCapability,
|
|
Provider,
|
|
ProviderMetadata,
|
|
ProviderSettingsKeyMap,
|
|
SettingKeyProviders,
|
|
} from "@/constants";
|
|
import { getProviderInfo, getProviderLabel } from "@/utils";
|
|
import { PasswordInput } from "@/components/ui/password-input";
|
|
import { getSettings } from "@/settings/model";
|
|
import { debounce } from "@/utils";
|
|
|
|
interface ModelEditDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
model: CustomModel | null;
|
|
onUpdate: (originalModel: CustomModel, updatedModel: CustomModel) => void;
|
|
}
|
|
|
|
export const ModelEditDialog: React.FC<ModelEditDialogProps> = ({
|
|
open,
|
|
onOpenChange,
|
|
model,
|
|
onUpdate,
|
|
}) => {
|
|
const { modalContainer } = useTab();
|
|
const [localModel, setLocalModel] = useState<CustomModel | null>(model);
|
|
const [originalModel, setOriginalModel] = useState<CustomModel | null>(model);
|
|
const [providerInfo, setProviderInfo] = useState<ProviderMetadata>({} as ProviderMetadata);
|
|
const settings = getSettings();
|
|
|
|
const getDefaultApiKey = (provider: Provider): string => {
|
|
return (settings[ProviderSettingsKeyMap[provider as SettingKeyProviders]] as string) || "";
|
|
};
|
|
|
|
useEffect(() => {
|
|
setLocalModel(model);
|
|
setOriginalModel(model);
|
|
if (model?.provider) {
|
|
setProviderInfo(getProviderInfo(model.provider));
|
|
}
|
|
}, [model]);
|
|
|
|
// Debounce the onUpdate callback
|
|
const debouncedOnUpdate = useMemo(
|
|
() =>
|
|
debounce((currentOriginalModel: CustomModel | null, updatedModel: CustomModel) => {
|
|
if (currentOriginalModel) {
|
|
onUpdate(currentOriginalModel, updatedModel);
|
|
}
|
|
}, 500),
|
|
[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,
|
|
};
|
|
// Call the debounced update function, passing the stable originalModel and the new updatedModel
|
|
debouncedOnUpdate(originalModel, updatedModel);
|
|
return updatedModel; // Return the updated model for immediate state update
|
|
});
|
|
},
|
|
[originalModel, debouncedOnUpdate]
|
|
);
|
|
|
|
if (!localModel) return null;
|
|
|
|
const getPlaceholderUrl = () => {
|
|
if (!localModel || !localModel.provider || localModel.provider !== "azure-openai") {
|
|
return providerInfo.host || "https://api.example.com/v1";
|
|
}
|
|
|
|
const instanceName = localModel.azureOpenAIApiInstanceName || "[instance]";
|
|
const deploymentName = localModel.isEmbeddingModel
|
|
? localModel.azureOpenAIApiEmbeddingDeploymentName || "[deployment]"
|
|
: localModel.azureOpenAIApiDeploymentName || "[deployment]";
|
|
const apiVersion = localModel.azureOpenAIApiVersion || "[api-version]";
|
|
const endpoint = localModel.isEmbeddingModel ? "embeddings" : "chat/completions";
|
|
|
|
return `https://${instanceName}.openai.azure.com/openai/deployments/${deploymentName}/${endpoint}?api-version=${apiVersion}`;
|
|
};
|
|
|
|
const capabilityOptions = Object.entries(MODEL_CAPABILITIES).map(([id, description]) => ({
|
|
id,
|
|
label: id.charAt(0).toUpperCase() + id.slice(1),
|
|
description,
|
|
})) as Array<{ id: ModelCapability; label: string; description: string }>;
|
|
|
|
const displayApiKey = localModel.apiKey || getDefaultApiKey(localModel.provider as Provider);
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="sm:max-w-[425px]" container={modalContainer}>
|
|
<DialogHeader>
|
|
<DialogTitle>Model Settings - {localModel.name}</DialogTitle>
|
|
<DialogDescription>Customize model parameters.</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="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="flex items-center gap-1.5">
|
|
<span className="leading-none">Display Name</span>
|
|
<TooltipProvider delayDuration={0}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<HelpCircle className="size-4" />
|
|
</TooltipTrigger>
|
|
<TooltipContent align="start" className="max-w-96" side="bottom">
|
|
<div className="text-sm text-muted flex flex-col gap-0.5">
|
|
<div className="text-[12px] font-bold">Suggested format:</div>
|
|
<div className="text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
|
|
<div className="text-[12px]">
|
|
Example:
|
|
<li>Direct-Paid:Ds-r1</li>
|
|
<li>OpenRouter-Paid:Ds-r1</li>
|
|
<li>Perplexity-Paid:lg</li>
|
|
</div>
|
|
</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
|
|
className="bg-muted"
|
|
/>
|
|
</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="text-xs text-muted">
|
|
<a href={providerInfo.keyManagementURL} target="_blank" rel="noopener noreferrer">
|
|
Get {providerInfo.label} API Key
|
|
</a>
|
|
</p>
|
|
)}
|
|
</FormField>
|
|
|
|
<FormField
|
|
label={
|
|
<div className="flex items-center gap-1.5">
|
|
<span className="leading-none">Model Capabilities</span>
|
|
<TooltipProvider delayDuration={0}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<HelpCircle className="size-4" />
|
|
</TooltipTrigger>
|
|
<TooltipContent align="start" className="max-w-96" side="bottom">
|
|
<div className="text-sm text-muted">
|
|
Only used to display model capabilities, does not affect model functionality
|
|
</div>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
}
|
|
>
|
|
<div className="flex gap-4 items-center">
|
|
{capabilityOptions.map(({ id, label, description }) => (
|
|
<div key={id} className="flex items-center 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="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="flex items-center gap-2">
|
|
Temperature
|
|
<TooltipProvider delayDuration={0}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<HelpCircle className="h-4 w-4 text-muted" />
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">
|
|
Controls randomness: 0 is focused and deterministic, 2 is more creative
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
}
|
|
>
|
|
<SettingSlider
|
|
value={localModel.temperature ?? 0.1}
|
|
onChange={(value) => handleLocalUpdate("temperature", value)}
|
|
max={2}
|
|
min={0}
|
|
step={0.1}
|
|
/>
|
|
</FormField>
|
|
|
|
<FormField
|
|
label={
|
|
<div className="flex items-center gap-2">
|
|
Context
|
|
<TooltipProvider delayDuration={0}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<HelpCircle className="h-4 w-4 text-muted" />
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">
|
|
Maximum number of tokens to use for context
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
}
|
|
>
|
|
<SettingSlider
|
|
value={localModel.context ?? 1000}
|
|
onChange={(value) => handleLocalUpdate("context", value)}
|
|
max={16000}
|
|
min={0}
|
|
step={100}
|
|
/>
|
|
</FormField>
|
|
|
|
<div className="flex items-center justify-between py-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm font-medium">Stream output</span>
|
|
<TooltipProvider delayDuration={0}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<HelpCircle className="h-4 w-4 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>
|
|
);
|
|
};
|