mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
[2.4.11] Refactor settings to React components (#248)
* Add ollama custom base url setting
This commit is contained in:
parent
9440e9cd17
commit
f84a3bc56b
20 changed files with 962 additions and 739 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "copilot",
|
||||
"name": "Copilot",
|
||||
"version": "2.4.10",
|
||||
"version": "2.4.11",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "A ChatGPT Copilot in Obsidian.",
|
||||
"author": "Logan Yang",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-copilot",
|
||||
"version": "2.4.10",
|
||||
"version": "2.4.11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-copilot",
|
||||
"version": "2.4.10",
|
||||
"version": "2.4.11",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@huggingface/inference": "^2.6.4",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-copilot",
|
||||
"version": "2.4.10",
|
||||
"version": "2.4.11",
|
||||
"description": "ChatGPT integration for Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ export interface ModelConfig {
|
|||
apiKey?: string,
|
||||
openAIProxyBaseUrl?: string,
|
||||
ollamaModel?: string,
|
||||
// OllamaBaseUrl
|
||||
baseUrl?: string,
|
||||
openRouterModel?: string,
|
||||
lmStudioPort?: string,
|
||||
}
|
||||
|
|
@ -44,6 +46,7 @@ export interface LangChainParams {
|
|||
chainType: ChainType, // Default ChainType is set in main.ts getAIStateParams
|
||||
options: SetChainOptions,
|
||||
ollamaModel: string,
|
||||
ollamaBaseUrl: string,
|
||||
openRouterModel: string,
|
||||
lmStudioPort: string,
|
||||
openAIProxyBaseUrl?: string,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
ANTHROPIC,
|
||||
AZURE_MODELS,
|
||||
AZURE_OPENAI,
|
||||
CLAUDE_MODELS,
|
||||
COHEREAI,
|
||||
ChatModelDisplayNames,
|
||||
DEFAULT_SYSTEM_PROMPT,
|
||||
|
|
@ -178,6 +177,7 @@ class AIState {
|
|||
googleApiKey,
|
||||
openRouterAiApiKey,
|
||||
ollamaModel,
|
||||
ollamaBaseUrl,
|
||||
openRouterModel,
|
||||
} = this.langChainParams;
|
||||
|
||||
|
|
@ -239,6 +239,7 @@ class AIState {
|
|||
case OLLAMA:
|
||||
config = {
|
||||
...config,
|
||||
...(ollamaBaseUrl ? { baseUrl: ollamaBaseUrl } : {}),
|
||||
modelName: ollamaModel,
|
||||
};
|
||||
break;
|
||||
|
|
@ -269,14 +270,6 @@ class AIState {
|
|||
};
|
||||
}
|
||||
|
||||
for (const modelDisplayNameKey of CLAUDE_MODELS) {
|
||||
modelMap[modelDisplayNameKey] = {
|
||||
hasApiKey: Boolean(this.langChainParams.anthropicApiKey),
|
||||
AIConstructor: ChatAnthropic,
|
||||
vendor: ANTHROPIC,
|
||||
};
|
||||
}
|
||||
|
||||
for (const modelDisplayNameKey of AZURE_MODELS) {
|
||||
modelMap[modelDisplayNameKey] = {
|
||||
hasApiKey: Boolean(this.langChainParams.azureOpenAIApiKey),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CopilotSettings } from '@/main';
|
||||
import { CopilotSettings } from '@/settings/SettingsPage';
|
||||
|
||||
export const CHAT_VIEWTYPE = 'copilot-chat-view';
|
||||
export const USER_SENDER = 'user';
|
||||
|
|
@ -11,10 +11,10 @@ export enum ChatModels {
|
|||
GPT_4 = 'gpt-4',
|
||||
GPT_4_TURBO = 'gpt-4-1106-preview',
|
||||
GPT_4_32K = 'gpt-4-32k',
|
||||
CLAUDE_1 = 'claude-1',
|
||||
CLAUDE_1_100K = 'claude-1-100k',
|
||||
CLAUDE_INSTANT_1 = 'claude-instant-1',
|
||||
CLAUDE_INSTANT_1_100K = 'claude-instant-1-100k',
|
||||
// CLAUDE_1 = 'claude-1',
|
||||
// CLAUDE_1_100K = 'claude-1-100k',
|
||||
// CLAUDE_INSTANT_1 = 'claude-instant-1',
|
||||
// CLAUDE_INSTANT_1_100K = 'claude-instant-1-100k',
|
||||
AZURE_GPT_35_TURBO = 'gpt-35-turbo',
|
||||
AZURE_GPT_35_TURBO_16K = 'gpt-35-turbo-16k',
|
||||
GEMINI_PRO = 'gemini-pro',
|
||||
|
|
@ -27,10 +27,10 @@ export enum ChatModelDisplayNames {
|
|||
GPT_4 = 'GPT-4',
|
||||
GPT_4_TURBO = 'GPT-4 TURBO',
|
||||
GPT_4_32K = 'GPT-4 32K',
|
||||
CLAUDE_1 = 'CLAUDE-1',
|
||||
CLAUDE_1_100K = 'CLAUDE-1-100K',
|
||||
CLAUDE_INSTANT_1 = 'CLAUDE-INSTANT',
|
||||
CLAUDE_INSTANT_1_100K = 'CLAUDE-INSTANT-100K',
|
||||
// CLAUDE_1 = 'CLAUDE-1',
|
||||
// CLAUDE_1_100K = 'CLAUDE-1-100K',
|
||||
// CLAUDE_INSTANT_1 = 'CLAUDE-INSTANT',
|
||||
// CLAUDE_INSTANT_1_100K = 'CLAUDE-INSTANT-100K',
|
||||
AZURE_GPT_35_TURBO = 'AZURE GPT-3.5',
|
||||
AZURE_GPT_35_TURBO_16K = 'AZURE GPT-3.5-16K',
|
||||
AZURE_GPT_4 = 'AZURE GPT-4',
|
||||
|
|
@ -57,12 +57,12 @@ export const AZURE_MODELS = new Set([
|
|||
ChatModelDisplayNames.AZURE_GPT_4_32K,
|
||||
]);
|
||||
|
||||
export const CLAUDE_MODELS = new Set([
|
||||
ChatModelDisplayNames.CLAUDE_1,
|
||||
ChatModelDisplayNames.CLAUDE_1_100K,
|
||||
ChatModelDisplayNames.CLAUDE_INSTANT_1,
|
||||
ChatModelDisplayNames.CLAUDE_INSTANT_1_100K,
|
||||
]);
|
||||
// export const CLAUDE_MODELS = new Set([
|
||||
// ChatModelDisplayNames.CLAUDE_1,
|
||||
// ChatModelDisplayNames.CLAUDE_1_100K,
|
||||
// ChatModelDisplayNames.CLAUDE_INSTANT_1,
|
||||
// ChatModelDisplayNames.CLAUDE_INSTANT_1_100K,
|
||||
// ]);
|
||||
|
||||
export const GOOGLE_MODELS = new Set([
|
||||
ChatModelDisplayNames.GEMINI_PRO,
|
||||
|
|
@ -86,10 +86,10 @@ export const DISPLAY_NAME_TO_MODEL: Record<string, string> = {
|
|||
[ChatModelDisplayNames.GPT_4]: ChatModels.GPT_4,
|
||||
[ChatModelDisplayNames.GPT_4_TURBO]: ChatModels.GPT_4_TURBO,
|
||||
[ChatModelDisplayNames.GPT_4_32K]: ChatModels.GPT_4_32K,
|
||||
[ChatModelDisplayNames.CLAUDE_1]: ChatModels.CLAUDE_1,
|
||||
[ChatModelDisplayNames.CLAUDE_1_100K]: ChatModels.CLAUDE_1_100K,
|
||||
[ChatModelDisplayNames.CLAUDE_INSTANT_1]: ChatModels.CLAUDE_INSTANT_1,
|
||||
[ChatModelDisplayNames.CLAUDE_INSTANT_1_100K]: ChatModels.CLAUDE_INSTANT_1_100K,
|
||||
// [ChatModelDisplayNames.CLAUDE_1]: ChatModels.CLAUDE_1,
|
||||
// [ChatModelDisplayNames.CLAUDE_1_100K]: ChatModels.CLAUDE_1_100K,
|
||||
// [ChatModelDisplayNames.CLAUDE_INSTANT_1]: ChatModels.CLAUDE_INSTANT_1,
|
||||
// [ChatModelDisplayNames.CLAUDE_INSTANT_1_100K]: ChatModels.CLAUDE_INSTANT_1_100K,
|
||||
[ChatModelDisplayNames.AZURE_GPT_35_TURBO]: ChatModels.AZURE_GPT_35_TURBO,
|
||||
[ChatModelDisplayNames.AZURE_GPT_35_TURBO_16K]: ChatModels.AZURE_GPT_35_TURBO_16K,
|
||||
[ChatModelDisplayNames.AZURE_GPT_4]: ChatModels.GPT_4,
|
||||
|
|
@ -111,13 +111,20 @@ export const OLLAMA = 'ollama';
|
|||
export const VENDOR_MODELS: Record<string, Set<string>> = {
|
||||
[OPENAI]: OPENAI_MODELS,
|
||||
[AZURE_OPENAI]: AZURE_MODELS,
|
||||
[ANTHROPIC]: CLAUDE_MODELS,
|
||||
// [ANTHROPIC]: CLAUDE_MODELS,
|
||||
[GOOGLE]: GOOGLE_MODELS,
|
||||
[OPENROUTERAI]: OPENROUTERAI_MODELS,
|
||||
[OLLAMA]: OLLAMA_MODELS,
|
||||
[LM_STUDIO]: LM_STUDIO_MODELS,
|
||||
};
|
||||
|
||||
export const EMBEDDING_PROVIDERS = [
|
||||
OPENAI,
|
||||
AZURE_OPENAI,
|
||||
COHEREAI,
|
||||
HUGGINGFACE,
|
||||
];
|
||||
|
||||
// Embedding Models
|
||||
export const DISTILBERT_NLI = 'sentence-transformers/distilbert-base-nli-mean-tokens';
|
||||
export const INSTRUCTOR_XL = 'hkunlp/instructor-xl'; // Inference API is off for this
|
||||
|
|
@ -144,6 +151,7 @@ export const DEFAULT_SETTINGS: CopilotSettings = {
|
|||
userSystemPrompt: '',
|
||||
openAIProxyBaseUrl: '',
|
||||
ollamaModel: 'llama2',
|
||||
ollamaBaseUrl: '',
|
||||
lmStudioPort: '1234',
|
||||
ttlDays: 30,
|
||||
stream: true,
|
||||
|
|
|
|||
33
src/main.ts
33
src/main.ts
|
|
@ -9,7 +9,7 @@ import { ToneModal } from "@/components/ToneModal";
|
|||
import {
|
||||
CHAT_VIEWTYPE, DEFAULT_SETTINGS, DEFAULT_SYSTEM_PROMPT,
|
||||
} from '@/constants';
|
||||
import { CopilotSettingTab } from '@/settings';
|
||||
import { CopilotSettingTab, CopilotSettings } from '@/settings/SettingsPage';
|
||||
import SharedState from '@/sharedState';
|
||||
import { sanitizeSettings } from "@/utils";
|
||||
import VectorDBManager, { VectorStoreDocument } from '@/vectorDBManager';
|
||||
|
|
@ -18,35 +18,6 @@ import { Editor, Notice, Plugin, WorkspaceLeaf } from 'obsidian';
|
|||
import PouchDB from 'pouchdb';
|
||||
|
||||
|
||||
export interface CopilotSettings {
|
||||
openAIApiKey: string;
|
||||
huggingfaceApiKey: string;
|
||||
cohereApiKey: string;
|
||||
anthropicApiKey: string;
|
||||
azureOpenAIApiKey: string;
|
||||
azureOpenAIApiInstanceName: string;
|
||||
azureOpenAIApiDeploymentName: string;
|
||||
azureOpenAIApiVersion: string;
|
||||
azureOpenAIApiEmbeddingDeploymentName: string;
|
||||
googleApiKey: string;
|
||||
openRouterAiApiKey: string;
|
||||
openRouterModel: string;
|
||||
defaultModel: string;
|
||||
defaultModelDisplayName: string;
|
||||
temperature: number;
|
||||
maxTokens: number;
|
||||
contextTurns: number;
|
||||
userSystemPrompt: string;
|
||||
openAIProxyBaseUrl: string;
|
||||
ollamaModel: string;
|
||||
lmStudioPort: string;
|
||||
ttlDays: number;
|
||||
stream: boolean;
|
||||
embeddingProvider: string;
|
||||
defaultSaveFolder: string;
|
||||
debug: boolean;
|
||||
}
|
||||
|
||||
interface CustomPrompt {
|
||||
_id: string;
|
||||
_rev?: string;
|
||||
|
|
@ -495,6 +466,7 @@ export default class CopilotPlugin extends Plugin {
|
|||
contextTurns,
|
||||
embeddingProvider,
|
||||
ollamaModel,
|
||||
ollamaBaseUrl,
|
||||
lmStudioPort,
|
||||
} = sanitizeSettings(this.settings);
|
||||
return {
|
||||
|
|
@ -511,6 +483,7 @@ export default class CopilotPlugin extends Plugin {
|
|||
openRouterAiApiKey,
|
||||
openRouterModel: openRouterModel || DEFAULT_SETTINGS.openRouterModel,
|
||||
ollamaModel: ollamaModel || DEFAULT_SETTINGS.ollamaModel,
|
||||
ollamaBaseUrl: ollamaBaseUrl || DEFAULT_SETTINGS.ollamaBaseUrl,
|
||||
lmStudioPort: lmStudioPort || DEFAULT_SETTINGS.lmStudioPort,
|
||||
model: this.settings.defaultModel,
|
||||
modelDisplayName: this.settings.defaultModelDisplayName,
|
||||
|
|
|
|||
667
src/settings.ts
667
src/settings.ts
|
|
@ -1,667 +0,0 @@
|
|||
import {
|
||||
AZURE_OPENAI,
|
||||
COHEREAI,
|
||||
ChatModelDisplayNames,
|
||||
DEFAULT_SETTINGS,
|
||||
DISPLAY_NAME_TO_MODEL,
|
||||
HUGGINGFACE,
|
||||
OPENAI
|
||||
} from "@/constants";
|
||||
import CopilotPlugin from "@/main";
|
||||
import { App, DropdownComponent, Notice, PluginSettingTab, Setting } from "obsidian";
|
||||
|
||||
export class CopilotSettingTab extends PluginSettingTab {
|
||||
plugin: CopilotPlugin;
|
||||
|
||||
constructor(app: App, plugin: CopilotPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
async reloadPlugin() {
|
||||
try {
|
||||
// Save the settings before reloading
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
// Reload the plugin
|
||||
const app = (this.plugin.app as any);
|
||||
await app.plugins.disablePlugin("copilot");
|
||||
await app.plugins.enablePlugin("copilot");
|
||||
|
||||
app.setting.openTabById("copilot").display();
|
||||
new Notice('Plugin reloaded successfully.');
|
||||
} catch (error) {
|
||||
new Notice('Failed to reload the plugin. Please reload manually.');
|
||||
console.error('Error reloading plugin:', error);
|
||||
}
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
containerEl.style.userSelect = 'text';
|
||||
containerEl.createEl('h2', { text: 'Copilot Settings' });
|
||||
|
||||
const buttonContainer = containerEl.createDiv({ cls: 'button-container' });
|
||||
buttonContainer.createEl('button', {
|
||||
text: 'Save and Reload',
|
||||
type: 'button',
|
||||
cls: 'mod-cta',
|
||||
}).addEventListener('click', async () => {
|
||||
await this.plugin.saveSettings();
|
||||
await this.reloadPlugin();
|
||||
new Notice('Settings have been saved and the plugin has been reloaded.');
|
||||
});
|
||||
|
||||
buttonContainer.createEl('button', {
|
||||
text: 'Reset to Default Settings',
|
||||
type: 'button',
|
||||
cls: 'mod-cta',
|
||||
}).addEventListener('click', async () => {
|
||||
this.plugin.settings = DEFAULT_SETTINGS;
|
||||
await this.plugin.saveSettings();
|
||||
await this.reloadPlugin();
|
||||
new Notice('Settings have been reset to their default values.');
|
||||
});
|
||||
|
||||
containerEl.createEl('div', {
|
||||
text: 'Please Save and Reload the plugin when you change any setting below!',
|
||||
cls: 'warning-message'
|
||||
});
|
||||
|
||||
|
||||
const modelDisplayNames = [
|
||||
ChatModelDisplayNames.GPT_35_TURBO,
|
||||
ChatModelDisplayNames.GPT_35_TURBO_16K,
|
||||
ChatModelDisplayNames.GPT_4,
|
||||
ChatModelDisplayNames.GPT_4_TURBO,
|
||||
ChatModelDisplayNames.GPT_4_32K,
|
||||
// ChatModelDisplayNames.CLAUDE_1,
|
||||
// ChatModelDisplayNames.CLAUDE_1_100K,
|
||||
// ChatModelDisplayNames.CLAUDE_INSTANT_1,
|
||||
// ChatModelDisplayNames.CLAUDE_INSTANT_1_100K,
|
||||
ChatModelDisplayNames.AZURE_GPT_35_TURBO,
|
||||
ChatModelDisplayNames.AZURE_GPT_35_TURBO_16K,
|
||||
ChatModelDisplayNames.AZURE_GPT_4,
|
||||
ChatModelDisplayNames.AZURE_GPT_4_32K,
|
||||
ChatModelDisplayNames.GEMINI_PRO,
|
||||
ChatModelDisplayNames.OPENROUTERAI,
|
||||
ChatModelDisplayNames.LM_STUDIO,
|
||||
ChatModelDisplayNames.OLLAMA,
|
||||
];
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default Model")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("The default model to use");
|
||||
})
|
||||
)
|
||||
.addDropdown((dropdown: DropdownComponent) => {
|
||||
modelDisplayNames.forEach(displayName => {
|
||||
dropdown.addOption(displayName, displayName);
|
||||
});
|
||||
dropdown
|
||||
.setValue(this.plugin.settings.defaultModelDisplayName)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultModelDisplayName = value;
|
||||
this.plugin.settings.defaultModel = DISPLAY_NAME_TO_MODEL[this.plugin.settings.defaultModelDisplayName];
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default Conversation Folder Name")
|
||||
.setDesc("The default folder name where chat conversations will be saved. Default is 'copilot-conversations'")
|
||||
.addText(text => text
|
||||
.setPlaceholder("copilot-conversations")
|
||||
.setValue(this.plugin.settings.defaultSaveFolder)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultSaveFolder = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
containerEl.createEl('h4', { text: 'API Settings' });
|
||||
containerEl.createEl('h6', { text: 'OpenAI API' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your OpenAI API key")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("You can find your API key at ");
|
||||
frag.createEl('a', {
|
||||
text: "https://platform.openai.com/api-keys",
|
||||
href: "https://platform.openai.com/api-keys"
|
||||
});
|
||||
frag.createEl('br');
|
||||
frag.appendText(
|
||||
"It is stored locally in your vault at "
|
||||
);
|
||||
frag.createEl('br');
|
||||
frag.createEl(
|
||||
'strong',
|
||||
{ text: "path_to_your_vault/.obsidian/plugins/obsidian-copilot/data.json" }
|
||||
);
|
||||
frag.createEl('br');
|
||||
frag.appendText("and it is only used to make requests to OpenAI.");
|
||||
})
|
||||
)
|
||||
.addText((text) => {
|
||||
text.inputEl.type = "password";
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("OpenAI API key")
|
||||
.setValue(this.plugin.settings.openAIApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openAIApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const warningMessage = containerEl.createEl('div', { cls: 'warning-message' });
|
||||
|
||||
warningMessage.createEl('span', {
|
||||
text: 'If errors occur, pls re-enter the API key, save and reload the plugin to see if it resolves the issue.'
|
||||
});
|
||||
|
||||
warningMessage.createEl('br');
|
||||
|
||||
warningMessage.createEl('span', {
|
||||
text: 'If you are a new user, try '
|
||||
});
|
||||
|
||||
warningMessage.createEl('a', {
|
||||
text: 'OpenAI playground',
|
||||
href: 'https://platform.openai.com/playground?mode=chat'
|
||||
});
|
||||
|
||||
warningMessage.createEl('span', {
|
||||
text: ' to see if you have correct API access first.'
|
||||
});
|
||||
|
||||
// containerEl.createEl('h6', { text: 'Anthropic' });
|
||||
|
||||
// new Setting(containerEl)
|
||||
// .setName("Your Anthropic API key")
|
||||
// .setDesc(
|
||||
// createFragment((frag) => {
|
||||
// frag.appendText("This is for Claude models. Sign up on their waitlist if you don't have access.");
|
||||
// frag.createEl('a', {
|
||||
// text: "https://docs.anthropic.com/claude/docs/getting-access-to-claude",
|
||||
// href: "https://docs.anthropic.com/claude/docs/getting-access-to-claude"
|
||||
// });
|
||||
// })
|
||||
// )
|
||||
// .addText((text) => {
|
||||
// text.inputEl.type = "password";
|
||||
// text.inputEl.style.width = "100%";
|
||||
// text
|
||||
// .setPlaceholder("Anthropic API key")
|
||||
// .setValue(this.plugin.settings.anthropicApiKey)
|
||||
// .onChange(async (value) => {
|
||||
// this.plugin.settings.anthropicApiKey = value;
|
||||
// await this.plugin.saveSettings();
|
||||
// })
|
||||
// }
|
||||
// );
|
||||
|
||||
containerEl.createEl('h6', { text: 'Google Gemini API' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your Google API key")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("If you have Google Cloud, you can get Gemini API key ");
|
||||
frag.createEl('a', {
|
||||
text: "here",
|
||||
href: "https://makersuite.google.com/app/apikey"
|
||||
});
|
||||
})
|
||||
)
|
||||
.addText((text) => {
|
||||
text.inputEl.type = "password";
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("Google API key")
|
||||
.setValue(this.plugin.settings.googleApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.googleApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
containerEl.createEl('h6', { text: 'OpenRouter.ai API' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your OpenRouterAI API key")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("You can get your OpenRouterAI key ");
|
||||
frag.createEl('a', {
|
||||
text: "here",
|
||||
href: "https://openrouter.ai/keys"
|
||||
});
|
||||
})
|
||||
)
|
||||
.addText((text) => {
|
||||
text.inputEl.type = "password";
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("OpenRouterAI API key")
|
||||
.setValue(this.plugin.settings.openRouterAiApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openRouterAiApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("OpenRouterAI model")
|
||||
.setDesc("Default: cognitivecomputations/dolphin-mixtral-8x7b")
|
||||
.addText(text => {
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("cognitivecomputations/dolphin-mixtral-8x7b")
|
||||
.setValue(this.plugin.settings.openRouterModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.openRouterModel = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
});
|
||||
|
||||
containerEl.createEl('h6', { text: 'Azure OpenAI API' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your Azure OpenAI API key")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("This is for Azure OpenAI APIs. Sign up on their waitlist if you don't have access.");
|
||||
})
|
||||
)
|
||||
.addText((text) => {
|
||||
text.inputEl.type = "password";
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("Azure OpenAI API key")
|
||||
.setValue(this.plugin.settings.azureOpenAIApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.azureOpenAIApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your Azure OpenAI instance name")
|
||||
.addText((text) => {
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("Azure OpenAI instance name")
|
||||
.setValue(this.plugin.settings.azureOpenAIApiInstanceName)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.azureOpenAIApiInstanceName = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your Azure OpenAI deployment name")
|
||||
.addText((text) => {
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("Azure OpenAI deployment name")
|
||||
.setValue(this.plugin.settings.azureOpenAIApiDeploymentName)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.azureOpenAIApiDeploymentName = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your Azure OpenAI API version")
|
||||
.addText((text) => {
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("Azure OpenAI API version")
|
||||
.setValue(this.plugin.settings.azureOpenAIApiVersion)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.azureOpenAIApiVersion = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your Azure OpenAI embedding model deployment name (Optional)")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("Only if you'd like to use Azure as the embedding provider.");
|
||||
})
|
||||
)
|
||||
.addText((text) => {
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("Azure OpenAI embedding model deployment name")
|
||||
.setValue(this.plugin.settings.azureOpenAIApiEmbeddingDeploymentName)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.azureOpenAIApiEmbeddingDeploymentName = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
containerEl.createEl(
|
||||
'h6',
|
||||
{
|
||||
text: 'Please be mindful of the number of tokens and context conversation turns you set here, as they will affect the cost of your API requests.'
|
||||
}
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Temperature")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText(
|
||||
"Default is 0.7. Higher values will result in more creativeness, but also more mistakes. Set to 0 for no randomness."
|
||||
);
|
||||
})
|
||||
)
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0, 2, 0.05)
|
||||
.setValue(
|
||||
this.plugin.settings.temperature !== undefined &&
|
||||
this.plugin.settings.temperature !== null ?
|
||||
this.plugin.settings.temperature : 0.7
|
||||
)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.temperature = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Token limit")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText(
|
||||
"The maximum number of output tokens to generate. Default is 1000."
|
||||
);
|
||||
frag.createEl(
|
||||
'strong',
|
||||
{
|
||||
text: 'This number plus the length of your prompt (input tokens) must be smaller than the context window of the model.'
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0, 10000, 100)
|
||||
.setValue(
|
||||
this.plugin.settings.maxTokens !== undefined &&
|
||||
this.plugin.settings.maxTokens !== null ?
|
||||
this.plugin.settings.maxTokens : 1000
|
||||
)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.maxTokens = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Conversation turns in context")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText(
|
||||
"The number of previous conversation turns to include in the context. Default is 3 turns, i.e. 6 messages."
|
||||
);
|
||||
})
|
||||
)
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(1, 30, 1)
|
||||
.setValue(
|
||||
this.plugin.settings.contextTurns !== undefined &&
|
||||
this.plugin.settings.contextTurns !== null ?
|
||||
this.plugin.settings.contextTurns : 3
|
||||
)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.contextTurns = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
containerEl.createEl('h4', { text: 'Vector-based QA Settings (Beta). No context limit!' });
|
||||
containerEl.createEl('p', { text: 'To start the QA session, use the Mode Selection dropdown and select "QA". Switch back to "Chat" when you are done!' });
|
||||
containerEl.createEl(
|
||||
'p',
|
||||
{
|
||||
text: 'NOTE: OpenAI embeddings are not free but may give better QA results. CohereAI offers trial API for FREE and the quality is very good! It is more stable than Huggingface Inference API (more timeouts).'
|
||||
}
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Embedding Provider")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("The embedding provider to use");
|
||||
})
|
||||
)
|
||||
.addDropdown((dropdown: DropdownComponent) => {
|
||||
dropdown
|
||||
.addOption(OPENAI, 'OpenAI')
|
||||
.addOption(COHEREAI, 'CohereAI')
|
||||
.addOption(AZURE_OPENAI, 'Azure OpenAI')
|
||||
.addOption(HUGGINGFACE, 'Huggingface')
|
||||
.setValue(this.plugin.settings.embeddingProvider)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.embeddingProvider = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("TTL (Days)")
|
||||
.setDesc("Specify the Time To Live (TTL) for the saved embeddings in days. Default is 30 days. Embeddings older than the TTL will be deleted automatically to save storage space.")
|
||||
.addText((text) => {
|
||||
text
|
||||
.setPlaceholder("30")
|
||||
.setValue(this.plugin.settings.ttlDays ? this.plugin.settings.ttlDays.toString() : '')
|
||||
.onChange(async (value: string) => {
|
||||
const intValue = parseInt(value);
|
||||
if (!isNaN(intValue)) {
|
||||
this.plugin.settings.ttlDays = intValue;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your CohereAI trial API key")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("You can sign up at CohereAI and find your API key at ");
|
||||
frag.createEl('a', {
|
||||
text: "https://dashboard.cohere.ai/api-keys",
|
||||
href: "https://dashboard.cohere.ai/api-keys"
|
||||
});
|
||||
frag.createEl('br');
|
||||
frag.appendText("It is used to make requests to CohereAI trial API for free embeddings.");
|
||||
})
|
||||
)
|
||||
.addText((text) => {
|
||||
text.inputEl.type = "password";
|
||||
text.inputEl.style.width = "80%";
|
||||
text
|
||||
.setPlaceholder("CohereAI trial API key")
|
||||
.setValue(this.plugin.settings.cohereApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.cohereApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your Huggingface Inference API key")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("You can find your API key at ");
|
||||
frag.createEl('a', {
|
||||
text: "https://hf.co/settings/tokens",
|
||||
href: "https://hf.co/settings/tokens"
|
||||
});
|
||||
frag.createEl('br');
|
||||
frag.appendText("It is used to make requests to Huggingface Inference API for free embeddings.");
|
||||
frag.createEl('br');
|
||||
frag.createEl('strong', {
|
||||
text: "Please note that the quality may be worse than OpenAI embeddings,"
|
||||
});
|
||||
frag.createEl('br');
|
||||
frag.createEl('strong', {
|
||||
text: "and may have more API timeout errors."
|
||||
});
|
||||
})
|
||||
)
|
||||
.addText((text) => {
|
||||
text.inputEl.type = "password";
|
||||
text.inputEl.style.width = "80%";
|
||||
text
|
||||
.setPlaceholder("Huggingface Inference API key")
|
||||
.setValue(this.plugin.settings.huggingfaceApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.huggingfaceApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
containerEl.createEl('h4', { text: 'Advanced Settings' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("User custom system prompt")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("You can set your own system prompt here. ")
|
||||
frag.createEl(
|
||||
'strong',
|
||||
{ text: "Warning: It will override the default system prompt for all messages! " }
|
||||
);
|
||||
frag.appendText(
|
||||
"Use with caution! Also note that OpenAI can return error codes for some system prompts."
|
||||
);
|
||||
})
|
||||
)
|
||||
.addTextArea(text => {
|
||||
text.inputEl.style.width = "200px";
|
||||
text.inputEl.style.height = "100px";
|
||||
text
|
||||
.setPlaceholder("User system prompt")
|
||||
.setValue(this.plugin.settings.userSystemPrompt)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.userSystemPrompt = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("OpenAI Proxy Base URL (3rd-party providers)")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.createEl(
|
||||
'strong',
|
||||
{ text: "CAUTION: This overrides the OpenAI API URL " },
|
||||
);
|
||||
frag.createEl('br');
|
||||
frag.createEl('strong', {
|
||||
text: "for both chat and embedding models when OpenAI models are picked!"
|
||||
});
|
||||
frag.createEl('br');
|
||||
frag.appendText(" Leave blank to use the official OpenAI API.");
|
||||
})
|
||||
)
|
||||
.addText((text) => {
|
||||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("https://openai.example.com/v1")
|
||||
.setValue(this.plugin.settings.openAIProxyBaseUrl)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openAIProxyBaseUrl = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
});
|
||||
|
||||
containerEl.createEl('h4', { text: 'Local Copilot (No Internet Required!)' });
|
||||
containerEl.createEl('div', {
|
||||
text: 'Please check the doc to set up LM Studio or Ollama server on your device.',
|
||||
cls: 'warning-message'
|
||||
});
|
||||
containerEl.createEl('p', { text: 'Local models can be limited in capabilities and may not work for some use cases at this time. Keep in mind that it is still in early experimental phase. But some 13B even 7B models are already quite capable!' });
|
||||
|
||||
|
||||
containerEl.createEl('h5', { text: 'LM Studio' });
|
||||
containerEl.createEl('p', { text: 'To use Local Copilot with LM Studio:' });
|
||||
containerEl.createEl('p', { text: '1. Start LM Studio server with CORS on. Default port is 1234 but if you change it, you can provide it below.' });
|
||||
containerEl.createEl('p', { text: '2. Pick LM Studio in the Copilot Chat model selection dropdown to chat with it!' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("LM Studio server port")
|
||||
.setDesc("The default is 1234")
|
||||
.addText(text => text
|
||||
.setPlaceholder("1234")
|
||||
.setValue(this.plugin.settings.lmStudioPort)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.lmStudioPort = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
containerEl.createEl('h5', { text: 'Ollama' });
|
||||
containerEl.createEl('p', { text: 'To use Local Copilot with Ollama, pick Ollama in the Copilot Chat model selection dropdown.' });
|
||||
containerEl.createEl('p', { text: 'Run the local Ollama server by running this in your terminal:' });
|
||||
containerEl.createEl(
|
||||
'strong',
|
||||
{ text: "OLLAMA_ORIGINS=app://obsidian.md* ollama serve" }
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Ollama model")
|
||||
.setDesc("The default is llama2")
|
||||
.addText(text => text
|
||||
.setPlaceholder("llama2")
|
||||
.setValue(this.plugin.settings.ollamaModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.ollamaModel = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
containerEl.createEl('h4', { text: 'Development mode' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Debug mode")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("Debug mode will log all API requests and prompts to the console.");
|
||||
})
|
||||
)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.debug)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.debug = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
92
src/settings/SettingsPage.tsx
Normal file
92
src/settings/SettingsPage.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import CopilotPlugin from "@/main";
|
||||
import { App, Notice, PluginSettingTab, Setting } from "obsidian";
|
||||
import React from 'react';
|
||||
import { createRoot } from "react-dom/client";
|
||||
import SettingsMain from "./components/SettingsMain";
|
||||
|
||||
export interface CopilotSettings {
|
||||
openAIApiKey: string;
|
||||
huggingfaceApiKey: string;
|
||||
cohereApiKey: string;
|
||||
anthropicApiKey: string;
|
||||
azureOpenAIApiKey: string;
|
||||
azureOpenAIApiInstanceName: string;
|
||||
azureOpenAIApiDeploymentName: string;
|
||||
azureOpenAIApiVersion: string;
|
||||
azureOpenAIApiEmbeddingDeploymentName: string;
|
||||
googleApiKey: string;
|
||||
openRouterAiApiKey: string;
|
||||
openRouterModel: string;
|
||||
defaultModel: string;
|
||||
defaultModelDisplayName: string;
|
||||
temperature: number;
|
||||
maxTokens: number;
|
||||
contextTurns: number;
|
||||
userSystemPrompt: string;
|
||||
openAIProxyBaseUrl: string;
|
||||
ollamaModel: string;
|
||||
ollamaBaseUrl: string;
|
||||
lmStudioPort: string;
|
||||
ttlDays: number;
|
||||
stream: boolean;
|
||||
embeddingProvider: string;
|
||||
defaultSaveFolder: string;
|
||||
debug: boolean;
|
||||
}
|
||||
|
||||
export class CopilotSettingTab extends PluginSettingTab {
|
||||
plugin: CopilotPlugin;
|
||||
|
||||
constructor(app: App, plugin: CopilotPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
async reloadPlugin() {
|
||||
try {
|
||||
// Save the settings before reloading
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
// Reload the plugin
|
||||
const app = (this.plugin.app as any);
|
||||
await app.plugins.disablePlugin("copilot");
|
||||
await app.plugins.enablePlugin("copilot");
|
||||
|
||||
app.setting.openTabById("copilot").display();
|
||||
new Notice('Plugin reloaded successfully.');
|
||||
} catch (error) {
|
||||
new Notice('Failed to reload the plugin. Please reload manually.');
|
||||
console.error('Error reloading plugin:', error);
|
||||
}
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.style.userSelect = 'text';
|
||||
const div = containerEl.createDiv("div")
|
||||
const sections = createRoot(div);
|
||||
|
||||
sections.render(
|
||||
<SettingsMain plugin={this.plugin} reloadPlugin={this.reloadPlugin.bind(this)} />
|
||||
);
|
||||
|
||||
const devModeHeader = containerEl.createEl('h1', { text: 'Development mode' });
|
||||
devModeHeader.style.marginTop = '40px';
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Debug mode")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("Debug mode will log all API requests and prompts to the console.");
|
||||
})
|
||||
)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.debug)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.debug = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
43
src/settings/components/AdvancedSettings.tsx
Normal file
43
src/settings/components/AdvancedSettings.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import { TextAreaComponent, TextComponent } from './SettingBlocks';
|
||||
|
||||
interface AdvancedSettingsProps {
|
||||
openAIProxyBaseUrl: string;
|
||||
setOpenAIProxyBaseUrl: (value: string) => void;
|
||||
userSystemPrompt: string;
|
||||
setUserSystemPrompt: (value: string) => void;
|
||||
}
|
||||
|
||||
const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
|
||||
openAIProxyBaseUrl,
|
||||
setOpenAIProxyBaseUrl,
|
||||
userSystemPrompt,
|
||||
setUserSystemPrompt,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<br/>
|
||||
<br/>
|
||||
<h1>Advanced Settings</h1>
|
||||
<div className="warning-message">
|
||||
OpenAI Proxy Base URL overrides the default OpenAI base URL, meaning now your OpenAI models are routed to this provider instead! Clear this field to use OpenAI again.
|
||||
</div>
|
||||
<TextComponent
|
||||
name="OpenAI Proxy Base URL"
|
||||
description="For providers that shares the same API as OpenAI."
|
||||
value={openAIProxyBaseUrl}
|
||||
onChange={setOpenAIProxyBaseUrl}
|
||||
placeholder="https://openai.example.com/v1"
|
||||
/>
|
||||
<TextAreaComponent
|
||||
name="User System Prompt"
|
||||
description="Warning: It will override the default system prompt for all messages!"
|
||||
value={userSystemPrompt}
|
||||
onChange={setUserSystemPrompt}
|
||||
placeholder="Enter your custom system prompt here"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedSettings;
|
||||
27
src/settings/components/ApiSetting.tsx
Normal file
27
src/settings/components/ApiSetting.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import { TextComponent } from './SettingBlocks';
|
||||
|
||||
const ApiSetting: React.FC<{
|
||||
title: string;
|
||||
description?: string;
|
||||
value: string;
|
||||
setValue: (value: string) => void;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
}> = ({ title, description, value, setValue, placeholder, type }) => {
|
||||
return (
|
||||
<div>
|
||||
<TextComponent
|
||||
name={title}
|
||||
description={description}
|
||||
value={value}
|
||||
// @ts-ignore
|
||||
onChange={setValue}
|
||||
placeholder={placeholder || ''}
|
||||
type={type || 'password'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiSetting;
|
||||
163
src/settings/components/ApiSettings.tsx
Normal file
163
src/settings/components/ApiSettings.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import { DEFAULT_SETTINGS } from "@/constants";
|
||||
import React from 'react';
|
||||
import ApiSetting from './ApiSetting';
|
||||
import Collapsible from './Collapsible';
|
||||
|
||||
interface ApiSettingsProps {
|
||||
openAIApiKey: string;
|
||||
setOpenAIApiKey: (value: string) => void;
|
||||
googleApiKey: string;
|
||||
setGoogleApiKey: (value: string) => void;
|
||||
openRouterAiApiKey: string;
|
||||
setOpenRouterAiApiKey: (value: string) => void;
|
||||
openRouterModel: string;
|
||||
setOpenRouterModel: (value: string) => void;
|
||||
azureOpenAIApiKey: string;
|
||||
setAzureOpenAIApiKey: (value: string) => void;
|
||||
azureOpenAIApiInstanceName: string;
|
||||
setAzureOpenAIApiInstanceName: (value: string) => void;
|
||||
azureOpenAIApiDeploymentName: string;
|
||||
setAzureOpenAIApiDeploymentName: (value: string) => void;
|
||||
azureOpenAIApiVersion: string;
|
||||
setAzureOpenAIApiVersion: (value: string) => void;
|
||||
}
|
||||
|
||||
const ApiSettings: React.FC<ApiSettingsProps> = ({
|
||||
openAIApiKey,
|
||||
setOpenAIApiKey,
|
||||
googleApiKey,
|
||||
setGoogleApiKey,
|
||||
openRouterAiApiKey,
|
||||
setOpenRouterAiApiKey,
|
||||
openRouterModel,
|
||||
setOpenRouterModel,
|
||||
azureOpenAIApiKey,
|
||||
setAzureOpenAIApiKey,
|
||||
azureOpenAIApiInstanceName,
|
||||
setAzureOpenAIApiInstanceName,
|
||||
azureOpenAIApiDeploymentName,
|
||||
setAzureOpenAIApiDeploymentName,
|
||||
azureOpenAIApiVersion,
|
||||
setAzureOpenAIApiVersion,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<br/>
|
||||
<br/>
|
||||
<h1>API Settings</h1>
|
||||
<p>
|
||||
All your API keys are stored locally.
|
||||
</p>
|
||||
<div className="warning-message">
|
||||
Make sure you have access to the model and the correct API key.
|
||||
<br />
|
||||
If errors occur, please re-enter the API key, save and reload the plugin to see if it resolves the issue.
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<ApiSetting
|
||||
title="OpenAI API Key"
|
||||
value={openAIApiKey}
|
||||
setValue={setOpenAIApiKey}
|
||||
placeholder="Enter OpenAI API Key"
|
||||
/>
|
||||
<p>
|
||||
You can find your API key at{' '}
|
||||
<a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer">
|
||||
https://platform.openai.com/api-keys
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="warning-message">
|
||||
<span>If you are a new user, try </span>
|
||||
<a href="https://platform.openai.com/playground?mode=chat" target="_blank" rel="noopener noreferrer">
|
||||
OpenAI playground
|
||||
</a>
|
||||
<span> to see if you have correct API access first.</span>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<Collapsible title="Google API Settings">
|
||||
<div>
|
||||
<ApiSetting
|
||||
title="Google API Key"
|
||||
value={googleApiKey}
|
||||
setValue={setGoogleApiKey}
|
||||
placeholder="Enter Google API Key"
|
||||
/>
|
||||
<p>
|
||||
If you have Google Cloud, you can get Gemini API key {' '}
|
||||
<a href="https://makersuite.google.com/app/apikey" target="_blank" rel="noopener noreferrer">
|
||||
here
|
||||
</a>.
|
||||
<br />
|
||||
Your API key is stored locally and is only used to make requests to Google's services.
|
||||
</p>
|
||||
</div>
|
||||
</Collapsible>
|
||||
|
||||
<Collapsible title="OpenRouter.ai API Settings">
|
||||
<div>
|
||||
<ApiSetting
|
||||
title="OpenRouter AI API Key"
|
||||
value={openRouterAiApiKey}
|
||||
setValue={setOpenRouterAiApiKey}
|
||||
placeholder="Enter OpenRouter AI API Key"
|
||||
/>
|
||||
<ApiSetting
|
||||
title="OpenRouter Model"
|
||||
value={openRouterModel}
|
||||
// @ts-ignore
|
||||
setValue={setOpenRouterModel}
|
||||
placeholder={DEFAULT_SETTINGS.openRouterModel}
|
||||
type="text"
|
||||
/>
|
||||
<p>
|
||||
You can get your OpenRouterAI key {' '}
|
||||
<a href="https://openrouter.ai/keys" target="_blank" rel="noopener noreferrer">
|
||||
here
|
||||
</a>.
|
||||
<br />
|
||||
Find models <a href="https://openrouter.ai/models" target="_blank" rel="noopener noreferrer">
|
||||
here
|
||||
</a>.
|
||||
</p>
|
||||
</div>
|
||||
</Collapsible>
|
||||
|
||||
<Collapsible title="Azure OpenAI API Settings">
|
||||
<div>
|
||||
<ApiSetting
|
||||
title="Azure OpenAI API Key"
|
||||
value={azureOpenAIApiKey}
|
||||
setValue={setAzureOpenAIApiKey}
|
||||
placeholder="Enter Azure OpenAI API Key"
|
||||
/>
|
||||
<ApiSetting
|
||||
title="Azure OpenAI API Instance Name"
|
||||
value={azureOpenAIApiInstanceName}
|
||||
setValue={setAzureOpenAIApiInstanceName}
|
||||
placeholder="Enter Azure OpenAI API Instance Name"
|
||||
type="text"
|
||||
/>
|
||||
<ApiSetting
|
||||
title="Azure OpenAI API Deployment Name"
|
||||
value={azureOpenAIApiDeploymentName}
|
||||
setValue={setAzureOpenAIApiDeploymentName}
|
||||
placeholder="Enter Azure OpenAI API Deployment Name"
|
||||
type="text"
|
||||
/>
|
||||
<ApiSetting
|
||||
title="Azure OpenAI API Version"
|
||||
value={azureOpenAIApiVersion}
|
||||
setValue={setAzureOpenAIApiVersion}
|
||||
placeholder="Enter Azure OpenAI API Version"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</Collapsible>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiSettings;
|
||||
54
src/settings/components/Collapsible.tsx
Normal file
54
src/settings/components/Collapsible.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import React, { ReactNode, useState } from 'react';
|
||||
|
||||
interface CollapsibleProps {
|
||||
title: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const Collapsible: React.FC<CollapsibleProps> = ({ title, children }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const titleStyle = {
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: '10px',
|
||||
borderRadius: '5px',
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
padding: '10px 10px',
|
||||
borderRadius: '8px',
|
||||
marginTop: '10px',
|
||||
};
|
||||
|
||||
const ChevronDown = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ChevronRight = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<div style={titleStyle} onClick={() => setIsOpen(!isOpen)}>
|
||||
{title}
|
||||
{isOpen ? <ChevronDown /> : <ChevronRight />}
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div style={contentStyle}>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Collapsible;
|
||||
66
src/settings/components/LocalCopilotSettings.tsx
Normal file
66
src/settings/components/LocalCopilotSettings.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import React from 'react';
|
||||
import { TextComponent } from './SettingBlocks';
|
||||
|
||||
interface LocalCopilotSettingsProps {
|
||||
lmStudioPort: string;
|
||||
setLmStudioPort: (value: string) => void;
|
||||
ollamaModel: string;
|
||||
setOllamaModel: (value: string) => void;
|
||||
ollamaBaseUrl: string;
|
||||
setOllamaBaseUrl: (value: string) => void;
|
||||
}
|
||||
|
||||
const LocalCopilotSettings: React.FC<LocalCopilotSettingsProps> = ({
|
||||
lmStudioPort,
|
||||
setLmStudioPort,
|
||||
ollamaModel,
|
||||
setOllamaModel,
|
||||
ollamaBaseUrl,
|
||||
setOllamaBaseUrl,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<br/>
|
||||
<h1>Local Copilot (No Internet Required!)</h1>
|
||||
<div className="warning-message">
|
||||
Please check the doc to set up LM Studio or Ollama server on your device.
|
||||
</div>
|
||||
<p>
|
||||
Local models can be limited in capabilities and may not work for some use cases at this time. Keep in mind that it is still in early experimental phase. But some 13B even 7B models are already quite capable!
|
||||
</p>
|
||||
<h3>LM Studio</h3>
|
||||
<p>
|
||||
To use Local Copilot with LM Studio:<br />
|
||||
1. Start LM Studio server with CORS on. Default port is 1234 but if you change it, you can provide it below.<br />
|
||||
2. Pick LM Studio in the Copilot Chat model selection dropdown to chat with it!
|
||||
</p>
|
||||
<TextComponent
|
||||
name="LM Studio Server Port"
|
||||
description="Default is 1234."
|
||||
value={lmStudioPort}
|
||||
onChange={setLmStudioPort}
|
||||
placeholder="1234"
|
||||
/>
|
||||
<h3>Ollama</h3>
|
||||
<p>To use Local Copilot with Ollama, pick Ollama in the Copilot Chat model selection dropdown.</p>
|
||||
<p>Run the local Ollama server by running this in your terminal:</p>
|
||||
<p><strong>OLLAMA_ORIGINS=app://obsidian.md* ollama serve</strong></p>
|
||||
<TextComponent
|
||||
name="Ollama model"
|
||||
description="Default is llama2 (7b)."
|
||||
value={ollamaModel}
|
||||
onChange={setOllamaModel}
|
||||
placeholder="llama2"
|
||||
/>
|
||||
<TextComponent
|
||||
name="Ollama Base URL"
|
||||
description="Default is http://localhost:11434. If you'd like to use a remote server, provide the URL here."
|
||||
value={ollamaBaseUrl}
|
||||
onChange={setOllamaBaseUrl}
|
||||
placeholder="http://localhost:11434"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocalCopilotSettings;
|
||||
97
src/settings/components/QASettings.tsx
Normal file
97
src/settings/components/QASettings.tsx
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import { EMBEDDING_PROVIDERS } from '@/constants';
|
||||
import React from 'react';
|
||||
import ApiSetting from './ApiSetting';
|
||||
import Collapsible from './Collapsible';
|
||||
import { DropdownComponent, SliderComponent } from './SettingBlocks';
|
||||
|
||||
interface QASettingsProps {
|
||||
embeddingProvider: string;
|
||||
setEmbeddingProvider: (value: string) => void;
|
||||
ttlDays: number;
|
||||
setTtlDays: (value: number) => void;
|
||||
cohereApiKey: string;
|
||||
setCohereApiKey: (value: string) => void;
|
||||
huggingfaceApiKey: string;
|
||||
setHuggingfaceApiKey: (value: string) => void;
|
||||
}
|
||||
|
||||
const QASettings: React.FC<QASettingsProps> = ({
|
||||
embeddingProvider,
|
||||
setEmbeddingProvider,
|
||||
ttlDays,
|
||||
setTtlDays,
|
||||
cohereApiKey,
|
||||
setCohereApiKey,
|
||||
huggingfaceApiKey,
|
||||
setHuggingfaceApiKey,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<br/>
|
||||
<br/>
|
||||
<h1>QA Settings</h1>
|
||||
<div className="warning-message">
|
||||
YOU MUST REBUILD YOUR INDEX AFTER SWITCHING EMBEDDING PROVIDERS!
|
||||
</div>
|
||||
<p>QA mode relies a <em>local</em> vector index (experimental)
|
||||
<br />
|
||||
OpenAI embeddings currently has the best retrieval quality. CohereAI embeddings are free during trial and are decent. With Huggingface Inference API, your mileage may vary.
|
||||
</p>
|
||||
<DropdownComponent
|
||||
name="Embedding Provider"
|
||||
description="The embedding API to call"
|
||||
value={embeddingProvider}
|
||||
onChange={setEmbeddingProvider}
|
||||
options={EMBEDDING_PROVIDERS}
|
||||
/>
|
||||
<SliderComponent
|
||||
name="TTL Days"
|
||||
description="The number of days to keep embeddings in the index"
|
||||
value={ttlDays}
|
||||
onChange={setTtlDays}
|
||||
min={1}
|
||||
max={365}
|
||||
step={1}
|
||||
/>
|
||||
<br />
|
||||
<Collapsible title="Cohere API Settings">
|
||||
<ApiSetting
|
||||
title="Cohere API Key"
|
||||
value={cohereApiKey}
|
||||
setValue={setCohereApiKey}
|
||||
placeholder="Enter Cohere API Key"
|
||||
/>
|
||||
<p>
|
||||
Get your free Cohere API key{' '}
|
||||
<a
|
||||
href="https://dashboard.cohere.ai/api-keys"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</p>
|
||||
</Collapsible>
|
||||
<Collapsible title="Huggingface Inference API Settings">
|
||||
<ApiSetting
|
||||
title="Huggingface Inference API Key"
|
||||
value={huggingfaceApiKey}
|
||||
setValue={setHuggingfaceApiKey}
|
||||
placeholder="Enter Huggingface Inference API key"
|
||||
/>
|
||||
<p>
|
||||
Get your Huggingface Inference API key{' '}
|
||||
<a
|
||||
href="https://hf.co/settings/tokens"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QASettings;
|
||||
136
src/settings/components/SettingBlocks.tsx
Normal file
136
src/settings/components/SettingBlocks.tsx
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import React from 'react';
|
||||
|
||||
type DropdownComponentProps = {
|
||||
name: string;
|
||||
description?: string;
|
||||
options: string[];
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
type TextComponentProps = {
|
||||
name: string;
|
||||
description?: string;
|
||||
placeholder: string;
|
||||
value: string;
|
||||
type?: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
type TextAreaComponentProps = {
|
||||
name: string;
|
||||
description?: string;
|
||||
placeholder: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
type SliderComponentProps = {
|
||||
name: string;
|
||||
description?: React.ReactNode; // This allows for JSX elements, strings, etc.
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
};
|
||||
|
||||
type ToggleComponentProps = {
|
||||
name: string;
|
||||
description?: string;
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const DropdownComponent: React.FC<DropdownComponentProps> = ({ name, description, options, value, onChange }) => {
|
||||
return (
|
||||
<div className="copilot-setting-item">
|
||||
<div className="copilot-setting-item-name">{name}</div>
|
||||
<div className="copilot-setting-item-description">{description}</div>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="copilot-setting-item-control"
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
<option key={index} value={option}>
|
||||
{option.replace(/_/g, ' ').toUpperCase()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TextComponent: React.FC<TextComponentProps> = ({ name, description, placeholder, value, type, onChange }) => {
|
||||
return (
|
||||
<div className="copilot-setting-item">
|
||||
<div className="copilot-setting-item-name">{name}</div>
|
||||
<div className="copilot-setting-item-description">{description}</div>
|
||||
<input
|
||||
type={type || 'text'}
|
||||
className="copilot-setting-item-control"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TextAreaComponent: React.FC<TextAreaComponentProps> = ({ name, description, placeholder, value, onChange }) => {
|
||||
return (
|
||||
<div className="copilot-setting-item">
|
||||
<div className="copilot-setting-item-name">{name}</div>
|
||||
<div className="copilot-setting-item-description">{description}</div>
|
||||
<textarea
|
||||
className="copilot-setting-item-control"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const SliderComponent: React.FC<SliderComponentProps> = ({ name, description, min, max, step, value, onChange }) => {
|
||||
return (
|
||||
<div className="copilot-setting-item">
|
||||
<div className="copilot-setting-item-name">{name}</div>
|
||||
<div className="copilot-setting-item-description">{description}</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<input
|
||||
type="range"
|
||||
className="copilot-setting-item-control"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={(e) => onChange(parseFloat(e.target.value))}
|
||||
/>
|
||||
<span style={{ marginLeft: '20px', fontWeight: 'bold', color: 'var(--inline-title-color)' }}>{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ToggleComponent: React.FC<ToggleComponentProps> = ({ name, description, value, onChange }) => {
|
||||
return (
|
||||
<div className="copilot-setting-item">
|
||||
<div className="copilot-setting-item-name">{name}</div>
|
||||
<div className="copilot-setting-item-description">{description}</div>
|
||||
<label className="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
/>
|
||||
<span className="slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { DropdownComponent, SliderComponent, TextAreaComponent, TextComponent, ToggleComponent };
|
||||
|
||||
212
src/settings/components/SettingsMain.tsx
Normal file
212
src/settings/components/SettingsMain.tsx
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
import CopilotPlugin from "@/main";
|
||||
import { Notice } from 'obsidian';
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import { ChatModelDisplayNames, DEFAULT_SETTINGS, DISPLAY_NAME_TO_MODEL } from '../../constants';
|
||||
import AdvancedSettings from './AdvancedSettings';
|
||||
import ApiSettings from './ApiSettings';
|
||||
import LocalCopilotSettings from './LocalCopilotSettings';
|
||||
import QASettings from './QASettings';
|
||||
import { DropdownComponent, SliderComponent, TextComponent } from './SettingBlocks';
|
||||
|
||||
interface SettingsMainProps {
|
||||
plugin: CopilotPlugin;
|
||||
reloadPlugin: () => Promise<void>;
|
||||
}
|
||||
|
||||
export default function SettingsMain({ plugin, reloadPlugin }: SettingsMainProps) {
|
||||
const [defaultModelDisplayName, setDefaultModelDisplayName] = useState(plugin.settings.defaultModelDisplayName);
|
||||
const [defaultSaveFolder, setDefaultSaveFolder] = useState(plugin.settings.defaultSaveFolder);
|
||||
const [temperature, setTemperature] = useState(plugin.settings.temperature);
|
||||
const [maxTokens, setMaxTokens] = useState(plugin.settings.maxTokens);
|
||||
const [contextTurns, setContextTurns] = useState(plugin.settings.contextTurns);
|
||||
|
||||
// API settings
|
||||
const [openAIApiKey, setOpenAIApiKey] = useState(plugin.settings.openAIApiKey);
|
||||
const [googleApiKey, setGoogleApiKey] = useState(plugin.settings.googleApiKey);
|
||||
|
||||
const [openRouterAiApiKey, setOpenRouterAiApiKey] = useState(plugin.settings.openRouterAiApiKey);
|
||||
const [openRouterModel, setOpenRouterModel] = useState(plugin.settings.openRouterModel);
|
||||
|
||||
const [azureOpenAIApiKey, setAzureOpenAIApiKey] = useState(plugin.settings.azureOpenAIApiKey);
|
||||
const [azureOpenAIApiInstanceName, setAzureOpenAIApiInstanceName] = useState(plugin.settings.azureOpenAIApiInstanceName);
|
||||
const [azureOpenAIApiDeploymentName, setAzureOpenAIApiDeploymentName] = useState(plugin.settings.azureOpenAIApiDeploymentName);
|
||||
const [azureOpenAIApiVersion, setAzureOpenAIApiVersion] = useState(plugin.settings.azureOpenAIApiVersion);
|
||||
|
||||
// QA settings
|
||||
const [embeddingProvider, setEmbeddingProvider] = useState(plugin.settings.embeddingProvider);
|
||||
const [ttlDays, setTtlDays] = useState(plugin.settings.ttlDays);
|
||||
const [cohereApiKey, setCohereApiKey] = useState(plugin.settings.cohereApiKey);
|
||||
const [huggingfaceApiKey, setHuggingfaceApiKey] = useState(plugin.settings.huggingfaceApiKey);
|
||||
|
||||
// Advanced settings
|
||||
const [userSystemPrompt, setUserSystemPrompt] = useState(plugin.settings.userSystemPrompt);
|
||||
const [openAIProxyBaseUrl, setOpenAIProxyBaseUrl] = useState(plugin.settings.openAIProxyBaseUrl);
|
||||
|
||||
// Local Copilot Settings
|
||||
const [lmStudioPort, setLmStudioPort] = useState(plugin.settings.lmStudioPort);
|
||||
const [ollamaModel, setOllamaModel] = useState(plugin.settings.ollamaModel);
|
||||
const [ollamaBaseUrl, setOllamaBaseUrl] = useState(plugin.settings.ollamaBaseUrl);
|
||||
|
||||
// NOTE: When new settings are added, make sure to add them to saveAllSettings
|
||||
const saveAllSettings = async () => {
|
||||
plugin.settings.defaultModelDisplayName = defaultModelDisplayName;
|
||||
plugin.settings.defaultModel = DISPLAY_NAME_TO_MODEL[defaultModelDisplayName];
|
||||
plugin.settings.defaultSaveFolder = defaultSaveFolder;
|
||||
plugin.settings.temperature = temperature;
|
||||
plugin.settings.maxTokens = maxTokens;
|
||||
plugin.settings.contextTurns = contextTurns;
|
||||
|
||||
// API settings
|
||||
plugin.settings.openAIApiKey = openAIApiKey;
|
||||
plugin.settings.googleApiKey = googleApiKey;
|
||||
plugin.settings.openRouterAiApiKey = openRouterAiApiKey;
|
||||
plugin.settings.openRouterModel = openRouterModel;
|
||||
plugin.settings.azureOpenAIApiKey = azureOpenAIApiKey;
|
||||
plugin.settings.azureOpenAIApiInstanceName = azureOpenAIApiInstanceName;
|
||||
plugin.settings.azureOpenAIApiDeploymentName = azureOpenAIApiDeploymentName;
|
||||
plugin.settings.azureOpenAIApiVersion = azureOpenAIApiVersion;
|
||||
|
||||
// QA settings
|
||||
plugin.settings.embeddingProvider = embeddingProvider;
|
||||
plugin.settings.ttlDays = ttlDays;
|
||||
plugin.settings.cohereApiKey = cohereApiKey;
|
||||
plugin.settings.huggingfaceApiKey = huggingfaceApiKey;
|
||||
|
||||
// Advanced settings
|
||||
plugin.settings.userSystemPrompt = userSystemPrompt;
|
||||
plugin.settings.openAIProxyBaseUrl = openAIProxyBaseUrl;
|
||||
|
||||
// Local Copilot Settings
|
||||
plugin.settings.lmStudioPort = lmStudioPort;
|
||||
plugin.settings.ollamaModel = ollamaModel;
|
||||
plugin.settings.ollamaBaseUrl = ollamaBaseUrl;
|
||||
|
||||
await plugin.saveSettings();
|
||||
await reloadPlugin();
|
||||
new Notice('Settings have been saved and the plugin has been reloaded.');
|
||||
};
|
||||
|
||||
const resetToDefaultSettings = async () => {
|
||||
plugin.settings = DEFAULT_SETTINGS;
|
||||
await plugin.saveSettings();
|
||||
await reloadPlugin();
|
||||
new Notice('Settings have been reset to their default values.');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h2>Copilot Settings</h2>
|
||||
<div className="button-container">
|
||||
<button className="mod-cta" onClick={saveAllSettings}>
|
||||
Save and Reload
|
||||
</button>
|
||||
<button className="mod-cta" onClick={resetToDefaultSettings}>
|
||||
Reset to Default Settings
|
||||
</button>
|
||||
</div>
|
||||
<div className="warning-message">
|
||||
Please Save and Reload the plugin when you change any setting below!
|
||||
</div>
|
||||
|
||||
<DropdownComponent
|
||||
name="Default Model"
|
||||
options={Object.values(ChatModelDisplayNames)}
|
||||
value={defaultModelDisplayName}
|
||||
onChange={setDefaultModelDisplayName}
|
||||
/>
|
||||
<TextComponent
|
||||
name="Default Conversation Folder Name"
|
||||
description="The default folder name where chat conversations will be saved. Default is 'copilot-conversations'"
|
||||
placeholder="copilot-conversations"
|
||||
value={defaultSaveFolder}
|
||||
onChange={setDefaultSaveFolder}
|
||||
/>
|
||||
<h6>
|
||||
Please be mindful of the number of tokens and context conversation turns you set here, as they will affect the cost of your API requests.
|
||||
</h6>
|
||||
<SliderComponent
|
||||
name="Temperature"
|
||||
description="Default is 0.1. Higher values will result in more creativeness, but also more mistakes. Set to 0 for no randomness."
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.05}
|
||||
value={temperature}
|
||||
onChange={async (value) => {
|
||||
setTemperature(value);
|
||||
}}
|
||||
/>
|
||||
<SliderComponent
|
||||
name="Token limit"
|
||||
description={
|
||||
<Fragment>
|
||||
<p>The maximum number of <em>output tokens</em> to generate. Default is 1000.</p>
|
||||
<em>This number plus the length of your prompt (input tokens) must be smaller than the context window of the model.</em>
|
||||
</Fragment>
|
||||
}
|
||||
min={0}
|
||||
max={10000}
|
||||
step={100}
|
||||
value={maxTokens}
|
||||
onChange={async (value) => {
|
||||
setMaxTokens(value);
|
||||
}}
|
||||
/>
|
||||
<SliderComponent
|
||||
name="Conversation turns in context"
|
||||
description="The number of previous conversation turns to include in the context. Default is 15 turns, i.e. 30 messages."
|
||||
min={1}
|
||||
max={30}
|
||||
step={1}
|
||||
value={contextTurns}
|
||||
onChange={async (value) => {
|
||||
setContextTurns(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ApiSettings
|
||||
openAIApiKey={openAIApiKey}
|
||||
setOpenAIApiKey={setOpenAIApiKey}
|
||||
googleApiKey={googleApiKey}
|
||||
setGoogleApiKey={setGoogleApiKey}
|
||||
openRouterAiApiKey={openRouterAiApiKey}
|
||||
setOpenRouterAiApiKey={setOpenRouterAiApiKey}
|
||||
openRouterModel={openRouterModel}
|
||||
setOpenRouterModel={setOpenRouterModel}
|
||||
azureOpenAIApiKey={azureOpenAIApiKey}
|
||||
setAzureOpenAIApiKey={setAzureOpenAIApiKey}
|
||||
azureOpenAIApiInstanceName={azureOpenAIApiInstanceName}
|
||||
setAzureOpenAIApiInstanceName={setAzureOpenAIApiInstanceName}
|
||||
azureOpenAIApiDeploymentName={azureOpenAIApiDeploymentName}
|
||||
setAzureOpenAIApiDeploymentName={setAzureOpenAIApiDeploymentName}
|
||||
azureOpenAIApiVersion={azureOpenAIApiVersion}
|
||||
setAzureOpenAIApiVersion={setAzureOpenAIApiVersion}
|
||||
/>
|
||||
<QASettings
|
||||
embeddingProvider={embeddingProvider}
|
||||
setEmbeddingProvider={setEmbeddingProvider}
|
||||
ttlDays={ttlDays}
|
||||
setTtlDays={setTtlDays}
|
||||
cohereApiKey={cohereApiKey}
|
||||
setCohereApiKey={setCohereApiKey}
|
||||
huggingfaceApiKey={huggingfaceApiKey}
|
||||
setHuggingfaceApiKey={setHuggingfaceApiKey}
|
||||
/>
|
||||
<AdvancedSettings
|
||||
openAIProxyBaseUrl={openAIProxyBaseUrl}
|
||||
setOpenAIProxyBaseUrl={setOpenAIProxyBaseUrl}
|
||||
userSystemPrompt={userSystemPrompt}
|
||||
setUserSystemPrompt={setUserSystemPrompt}
|
||||
/>
|
||||
<LocalCopilotSettings
|
||||
lmStudioPort={lmStudioPort}
|
||||
setLmStudioPort={setLmStudioPort}
|
||||
ollamaModel={ollamaModel}
|
||||
setOllamaModel={setOllamaModel}
|
||||
ollamaBaseUrl={ollamaBaseUrl}
|
||||
setOllamaBaseUrl={setOllamaBaseUrl}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
10
src/utils.ts
10
src/utils.ts
|
|
@ -1,16 +1,14 @@
|
|||
import { ChainType } from '@/chainFactory';
|
||||
import {
|
||||
ANTHROPIC,
|
||||
AZURE_MODELS,
|
||||
AZURE_OPENAI,
|
||||
CLAUDE_MODELS,
|
||||
DEFAULT_SETTINGS,
|
||||
DISPLAY_NAME_TO_MODEL,
|
||||
OPENAI,
|
||||
OPENAI_MODELS,
|
||||
USER_SENDER,
|
||||
USER_SENDER
|
||||
} from '@/constants';
|
||||
import { CopilotSettings } from '@/main';
|
||||
import { CopilotSettings } from '@/settings/SettingsPage';
|
||||
import { ChatMessage } from '@/sharedState';
|
||||
import {
|
||||
BaseChain,
|
||||
|
|
@ -57,10 +55,6 @@ export const getModelVendorMap = (): Record<string, string> => {
|
|||
for (const model of AZURE_MODELS) {
|
||||
model_to_vendor[model] = AZURE_OPENAI;
|
||||
}
|
||||
|
||||
for (const model of CLAUDE_MODELS) {
|
||||
model_to_vendor[model] = ANTHROPIC;
|
||||
}
|
||||
return model_to_vendor;
|
||||
}
|
||||
|
||||
|
|
|
|||
28
styles.css
28
styles.css
|
|
@ -29,6 +29,34 @@ If your plugin does not need CSS, delete this file.
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.copilot-setting-item-name {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
color: var(--inline-title-color);
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.copilot-setting-item-description {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.copilot-setting-item-control {
|
||||
width: 50%; /* Adjust the width as needed */
|
||||
max-width: 100%; /* Ensures it doesn't exceed the parent width */
|
||||
}
|
||||
|
||||
.copilot-setting-item-control::placeholder {
|
||||
color: gray; /* Set the color you want for the placeholder text */
|
||||
opacity: 0.5; /* Adjust for lighter appearance */
|
||||
}
|
||||
|
||||
.copilot-setting-item-control[type="range"] {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -31,5 +31,6 @@
|
|||
"2.4.7": "0.15.0",
|
||||
"2.4.8": "0.15.0",
|
||||
"2.4.9": "0.15.0",
|
||||
"2.4.10": "0.15.0"
|
||||
"2.4.10": "0.15.0",
|
||||
"2.4.11": "0.15.0"
|
||||
}
|
||||
Loading…
Reference in a new issue