mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
Add new models (#75)
* Add notice for vector store creation * Add new models to dropdown in settings and chat UI * Implement new set model logic * Disable claude models for now because of CORS * Add working version of AIState with more models * Make the button force rebuild index * Wrap the buttons when the view is narrow
This commit is contained in:
parent
4a6e427eda
commit
2dc2ae2113
10 changed files with 655 additions and 176 deletions
369
src/aiState.ts
369
src/aiState.ts
|
|
@ -1,22 +1,29 @@
|
|||
import ChainFactory, {
|
||||
LLM_CHAIN,
|
||||
RETRIEVAL_QA_CHAIN
|
||||
ChainType
|
||||
} from '@/chainFactory';
|
||||
import {
|
||||
AI_SENDER,
|
||||
ANTHROPIC,
|
||||
AZURE_MODELS,
|
||||
AZURE_OPENAI,
|
||||
CLAUDE_MODELS,
|
||||
COHEREAI,
|
||||
DEFAULT_SYSTEM_PROMPT,
|
||||
HUGGINGFACE,
|
||||
OPENAI,
|
||||
OPENAI_MODELS,
|
||||
USER_SENDER
|
||||
} from '@/constants';
|
||||
import { ChatMessage } from '@/sharedState';
|
||||
import { getModelName, isSupportedChain } from '@/utils';
|
||||
import {
|
||||
BaseChain,
|
||||
ConversationChain,
|
||||
ConversationalRetrievalQAChain,
|
||||
RetrievalQAChain,
|
||||
RetrievalQAChain
|
||||
} from "langchain/chains";
|
||||
import { ChatAnthropic } from 'langchain/chat_models/anthropic';
|
||||
import { BaseChatModel } from 'langchain/chat_models/base';
|
||||
import { ChatOpenAI } from 'langchain/chat_models/openai';
|
||||
import { VectorStore } from 'langchain/dist/vectorstores/base';
|
||||
import { Embeddings } from "langchain/embeddings/base";
|
||||
|
|
@ -38,42 +45,93 @@ import { MemoryVectorStore } from "langchain/vectorstores/memory";
|
|||
import { Notice } from 'obsidian';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
interface ModelConfig {
|
||||
modelName: string,
|
||||
temperature: number,
|
||||
streaming: boolean,
|
||||
maxRetries: number,
|
||||
maxConcurrency: number,
|
||||
maxTokens?: number,
|
||||
openAIApiKey?: string,
|
||||
anthropicApiKey?: string,
|
||||
azureOpenAIApiKey?: string,
|
||||
azureOpenAIApiInstanceName?: string,
|
||||
azureOpenAIApiDeploymentName?: string,
|
||||
azureOpenAIApiVersion?: string,
|
||||
}
|
||||
|
||||
export interface LangChainParams {
|
||||
openAiApiKey: string,
|
||||
openAIApiKey: string,
|
||||
huggingfaceApiKey: string,
|
||||
cohereApiKey: string,
|
||||
anthropicApiKey: string,
|
||||
azureOpenAIApiKey: string,
|
||||
azureOpenAIApiInstanceName: string,
|
||||
azureOpenAIApiDeploymentName: string,
|
||||
azureOpenAIApiVersion: string,
|
||||
model: string,
|
||||
modelDisplayName: string,
|
||||
temperature: number,
|
||||
maxTokens: number,
|
||||
systemMessage: string,
|
||||
chatContextTurns: number,
|
||||
embeddingProvider: string,
|
||||
chainType: string,
|
||||
chainType: ChainType, // Default ChainType is set in main.ts getAIStateParams
|
||||
options: SetChainOptions,
|
||||
}
|
||||
|
||||
export interface SetChainOptions {
|
||||
prompt?: ChatPromptTemplate;
|
||||
noteContent?: string | null;
|
||||
noteContent?: string;
|
||||
forceNewCreation?: boolean;
|
||||
}
|
||||
|
||||
class AIState {
|
||||
static chatOpenAI: ChatOpenAI;
|
||||
static chain: BaseChain;
|
||||
static retrievalChain: RetrievalQAChain;
|
||||
static conversationalRetrievalChain: ConversationalRetrievalQAChain;
|
||||
private static chatModel: BaseChatModel;
|
||||
private static chatOpenAI: ChatOpenAI;
|
||||
private static chatAnthropic: ChatAnthropic;
|
||||
private static azureChatOpenAI: ChatOpenAI;
|
||||
private static chain: BaseChain;
|
||||
private static retrievalChain: RetrievalQAChain;
|
||||
private static conversationalRetrievalChain: ConversationalRetrievalQAChain;
|
||||
|
||||
private chatPrompt: ChatPromptTemplate;
|
||||
private vectorStore: VectorStore;
|
||||
|
||||
memory: BufferWindowMemory;
|
||||
langChainParams: LangChainParams;
|
||||
chatPrompt: ChatPromptTemplate;
|
||||
vectorStore: VectorStore
|
||||
modelMap: Record<
|
||||
string,
|
||||
{
|
||||
hasApiKey: boolean;
|
||||
AIConstructor: new (config: ModelConfig) => BaseChatModel;
|
||||
vendor: string;
|
||||
}>;
|
||||
|
||||
constructor(langChainParams: LangChainParams) {
|
||||
this.langChainParams = langChainParams;
|
||||
this.buildModelMap();
|
||||
this.initMemory();
|
||||
this.initChatPrompt();
|
||||
// This takes care of chain creation as well
|
||||
this.setModel(this.langChainParams.modelDisplayName);
|
||||
}
|
||||
|
||||
private initMemory(): void {
|
||||
this.memory = new BufferWindowMemory({
|
||||
k: this.langChainParams.chatContextTurns * 2,
|
||||
memoryKey: 'history',
|
||||
inputKey: 'input',
|
||||
returnMessages: true,
|
||||
});
|
||||
}
|
||||
|
||||
private setNoteContent(noteContent: string): void {
|
||||
this.langChainParams.options.noteContent = noteContent;
|
||||
}
|
||||
|
||||
private initChatPrompt(): void {
|
||||
this.chatPrompt = ChatPromptTemplate.fromPromptMessages([
|
||||
SystemMessagePromptTemplate.fromTemplate(
|
||||
this.langChainParams.systemMessage
|
||||
|
|
@ -81,23 +139,108 @@ class AIState {
|
|||
new MessagesPlaceholder("history"),
|
||||
HumanMessagePromptTemplate.fromTemplate("{input}"),
|
||||
]);
|
||||
this.createNewChain(LLM_CHAIN);
|
||||
}
|
||||
|
||||
clearChatMemory(): void {
|
||||
console.log('clearing chat memory');
|
||||
this.memory.clear();
|
||||
private validateChainType(chainType: ChainType): void {
|
||||
if (chainType === undefined || chainType === null) throw new Error('No chain type set');
|
||||
}
|
||||
|
||||
setModel(newModel: string): void {
|
||||
console.log('setting model to', newModel);
|
||||
this.langChainParams.model = newModel;
|
||||
AIState.chatOpenAI.modelName = newModel;
|
||||
private validateChatModel(chatModel: BaseChatModel): void {
|
||||
if (chatModel === undefined || chatModel === null) throw new Error('No chat model set');
|
||||
}
|
||||
|
||||
private getModelConfig(chatModelProvider: string): ModelConfig {
|
||||
const {
|
||||
openAIApiKey,
|
||||
anthropicApiKey,
|
||||
azureOpenAIApiKey,
|
||||
azureOpenAIApiInstanceName,
|
||||
azureOpenAIApiDeploymentName,
|
||||
azureOpenAIApiVersion,
|
||||
model,
|
||||
temperature,
|
||||
maxTokens,
|
||||
} = this.langChainParams;
|
||||
|
||||
// Create a base configuration that applies to all models
|
||||
let config: ModelConfig = {
|
||||
modelName: model,
|
||||
temperature: temperature,
|
||||
streaming: true,
|
||||
maxRetries: 3,
|
||||
maxConcurrency: 3
|
||||
};
|
||||
|
||||
switch(chatModelProvider) {
|
||||
case OPENAI:
|
||||
config = {
|
||||
...config,
|
||||
openAIApiKey,
|
||||
maxTokens,
|
||||
};
|
||||
break;
|
||||
case ANTHROPIC:
|
||||
config = {
|
||||
...config,
|
||||
anthropicApiKey,
|
||||
};
|
||||
break;
|
||||
case AZURE_OPENAI:
|
||||
config = {
|
||||
...config,
|
||||
maxTokens,
|
||||
azureOpenAIApiKey: azureOpenAIApiKey,
|
||||
azureOpenAIApiInstanceName: azureOpenAIApiInstanceName,
|
||||
azureOpenAIApiDeploymentName: azureOpenAIApiDeploymentName,
|
||||
azureOpenAIApiVersion: azureOpenAIApiVersion,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private buildModelMap() {
|
||||
const modelMap: Record<
|
||||
string,
|
||||
{
|
||||
hasApiKey: boolean;
|
||||
AIConstructor: new (config: ModelConfig) => BaseChatModel;
|
||||
vendor: string;
|
||||
}
|
||||
> = {};
|
||||
|
||||
// Build modelMap
|
||||
for (const modelDisplayNameKey of OPENAI_MODELS) {
|
||||
modelMap[modelDisplayNameKey] = {
|
||||
hasApiKey: Boolean(this.langChainParams.openAIApiKey),
|
||||
AIConstructor: ChatOpenAI,
|
||||
vendor: OPENAI,
|
||||
};
|
||||
}
|
||||
|
||||
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),
|
||||
AIConstructor: ChatOpenAI,
|
||||
vendor: AZURE_OPENAI,
|
||||
};
|
||||
}
|
||||
|
||||
this.modelMap = modelMap;
|
||||
}
|
||||
|
||||
getEmbeddingsAPI(): Embeddings {
|
||||
const OpenAIEmbeddingsAPI = new OpenAIEmbeddings({
|
||||
openAIApiKey: this.langChainParams.openAiApiKey,
|
||||
openAIApiKey: this.langChainParams.openAIApiKey,
|
||||
maxRetries: 3,
|
||||
maxConcurrency: 3,
|
||||
timeout: 10000,
|
||||
|
|
@ -127,61 +270,118 @@ class AIState {
|
|||
}
|
||||
}
|
||||
|
||||
/* Create a new chain, or update chain with new model */
|
||||
createNewChain(
|
||||
chainType: string,
|
||||
options?: SetChainOptions,
|
||||
): void {
|
||||
const {
|
||||
openAiApiKey, model, temperature, maxTokens,
|
||||
} = this.langChainParams;
|
||||
clearChatMemory(): void {
|
||||
console.log('clearing chat memory');
|
||||
this.memory.clear();
|
||||
}
|
||||
|
||||
if (!openAiApiKey) {
|
||||
new Notice(
|
||||
'No OpenAI API key provided. Please set it in Copilot settings, and restart the plugin.'
|
||||
);
|
||||
return;
|
||||
private setChatModel(modelDisplayName: string): void {
|
||||
if (!this.modelMap.hasOwnProperty(modelDisplayName)) {
|
||||
throw new Error(`No model found for: ${modelDisplayName}`);
|
||||
}
|
||||
|
||||
AIState.chatOpenAI = new ChatOpenAI({
|
||||
openAIApiKey: openAiApiKey,
|
||||
modelName: model,
|
||||
temperature: temperature,
|
||||
maxTokens: maxTokens,
|
||||
streaming: true,
|
||||
maxRetries: 3,
|
||||
maxConcurrency: 3,
|
||||
});
|
||||
// Create and return the appropriate model
|
||||
const selectedModel = this.modelMap[modelDisplayName];
|
||||
if (!selectedModel.hasApiKey) {
|
||||
new Notice(`API key is not provided for the model: ${modelDisplayName}`);
|
||||
console.error(`API key is not provided for the model: ${modelDisplayName}`);
|
||||
}
|
||||
|
||||
this.setChain(chainType, options);
|
||||
const modelConfig = this.getModelConfig(selectedModel.vendor);
|
||||
|
||||
try {
|
||||
const newModelInstance = new selectedModel.AIConstructor({
|
||||
...modelConfig,
|
||||
});
|
||||
|
||||
switch(selectedModel.vendor) {
|
||||
case OPENAI:
|
||||
AIState.chatOpenAI = newModelInstance as ChatOpenAI;
|
||||
break;
|
||||
case ANTHROPIC:
|
||||
AIState.chatAnthropic = newModelInstance as ChatAnthropic;
|
||||
break;
|
||||
case AZURE_OPENAI:
|
||||
AIState.azureChatOpenAI = newModelInstance as ChatOpenAI;
|
||||
break;
|
||||
}
|
||||
|
||||
AIState.chatModel = newModelInstance;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
new Notice(`Error creating model: ${modelDisplayName}`);
|
||||
}
|
||||
}
|
||||
|
||||
setModel(newModelDisplayName: string): void {
|
||||
const newModel = getModelName(newModelDisplayName);
|
||||
// model and model display name must be update at the same time!
|
||||
this.langChainParams.model = newModel;
|
||||
this.langChainParams.modelDisplayName = newModelDisplayName;
|
||||
this.setChatModel(newModelDisplayName);
|
||||
// Must update the chatModel for chain because ChainFactory always
|
||||
// retrieves the old chain without the chatModel change if it exists!
|
||||
// Create a new chain with the new chatModel
|
||||
this.createChain(
|
||||
this.langChainParams.chainType,
|
||||
{...this.langChainParams.options, forceNewCreation: true},
|
||||
)
|
||||
console.log(`Setting model to ${newModelDisplayName}: ${newModel}`);
|
||||
}
|
||||
|
||||
/* Create a new chain, or update chain with new model */
|
||||
createChain(
|
||||
chainType: ChainType,
|
||||
options?: SetChainOptions,
|
||||
): void {
|
||||
this.validateChainType(chainType);
|
||||
try {
|
||||
this.setChain(chainType, options);
|
||||
} catch (error) {
|
||||
new Notice('Error creating chain:', error);
|
||||
console.error('Error creating chain:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async setChain(
|
||||
chainType: string,
|
||||
chainType: ChainType,
|
||||
options: SetChainOptions = {},
|
||||
): Promise<void> {
|
||||
this.validateChatModel(AIState.chatModel);
|
||||
this.validateChainType(chainType);
|
||||
|
||||
switch (chainType) {
|
||||
case LLM_CHAIN: {
|
||||
AIState.chain = ChainFactory.getLLMChain({
|
||||
llm: AIState.chatOpenAI,
|
||||
memory: this.memory,
|
||||
prompt: options.prompt || this.chatPrompt,
|
||||
}) as ConversationChain;
|
||||
this.langChainParams.chainType = LLM_CHAIN;
|
||||
console.log('Set chain:', LLM_CHAIN);
|
||||
break;
|
||||
}
|
||||
case RETRIEVAL_QA_CHAIN: {
|
||||
if (!options.noteContent) {
|
||||
console.error('No note content provided.');
|
||||
return;
|
||||
case ChainType.LLM_CHAIN: {
|
||||
if (options.forceNewCreation) {
|
||||
AIState.chain = ChainFactory.createNewLLMChain({
|
||||
llm: AIState.chatModel,
|
||||
memory: this.memory,
|
||||
prompt: options.prompt || this.chatPrompt,
|
||||
}) as ConversationChain;
|
||||
} else {
|
||||
AIState.chain = ChainFactory.getLLMChainFromMap({
|
||||
llm: AIState.chatModel,
|
||||
memory: this.memory,
|
||||
prompt: options.prompt || this.chatPrompt,
|
||||
}) as ConversationChain;
|
||||
}
|
||||
|
||||
this.langChainParams.chainType = ChainType.LLM_CHAIN;
|
||||
console.log('Set chain:', ChainType.LLM_CHAIN);
|
||||
break;
|
||||
}
|
||||
case ChainType.RETRIEVAL_QA_CHAIN: {
|
||||
if (!options.noteContent) {
|
||||
new Notice('No note content provided');
|
||||
throw new Error('No note content provided');
|
||||
}
|
||||
|
||||
this.setNoteContent(options.noteContent);
|
||||
const docHash = ChainFactory.getDocumentHash(options.noteContent);
|
||||
const vectorStore = ChainFactory.vectorStoreMap.get(docHash);
|
||||
if (vectorStore) {
|
||||
AIState.retrievalChain = RetrievalQAChain.fromLLM(
|
||||
AIState.chatOpenAI,
|
||||
AIState.chatModel,
|
||||
vectorStore.asRetriever(),
|
||||
);
|
||||
console.log('Existing vector store for document hash: ', docHash);
|
||||
|
|
@ -192,13 +392,13 @@ class AIState {
|
|||
return;
|
||||
}
|
||||
|
||||
const baseCompressor = LLMChainExtractor.fromLLM(AIState.chatOpenAI);
|
||||
const baseCompressor = LLMChainExtractor.fromLLM(AIState.chatModel);
|
||||
const retriever = new ContextualCompressionRetriever({
|
||||
baseCompressor,
|
||||
baseRetriever: this.vectorStore.asRetriever(),
|
||||
});
|
||||
AIState.retrievalChain = RetrievalQAChain.fromLLM(
|
||||
AIState.chatOpenAI,
|
||||
AIState.chatModel,
|
||||
retriever,
|
||||
);
|
||||
console.log(
|
||||
|
|
@ -207,12 +407,12 @@ class AIState {
|
|||
);
|
||||
}
|
||||
|
||||
this.langChainParams.chainType = RETRIEVAL_QA_CHAIN;
|
||||
console.log('Set chain:', RETRIEVAL_QA_CHAIN);
|
||||
this.langChainParams.chainType = ChainType.RETRIEVAL_QA_CHAIN;
|
||||
console.log('Set chain:', ChainType.RETRIEVAL_QA_CHAIN);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error('No chain type set.');
|
||||
this.validateChainType(chainType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -231,9 +431,10 @@ class AIState {
|
|||
);
|
||||
ChainFactory.setVectorStore(this.vectorStore, docHash);
|
||||
console.log('Vector store created successfully.');
|
||||
new Notice('Vector store created successfully.');
|
||||
} catch (error) {
|
||||
new Notice('Failed to create vector store, please try again:', error);
|
||||
console.log('Failed to create vector store, please try again.:', error);
|
||||
console.error('Failed to create vector store, please try again.:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,7 +442,7 @@ class AIState {
|
|||
return AIState.chatOpenAI.getNumTokens(inputStr);
|
||||
}
|
||||
|
||||
async runChatOpenAI(
|
||||
async runChatModel(
|
||||
userMessage: ChatMessage,
|
||||
chatContext: ChatMessage[],
|
||||
abortController: AbortController,
|
||||
|
|
@ -250,6 +451,7 @@ class AIState {
|
|||
debug = false,
|
||||
) {
|
||||
if (debug) {
|
||||
console.log('chatModel:', AIState.chatModel);
|
||||
for (const [i, chatMessage] of chatContext.entries()) {
|
||||
console.log(
|
||||
`chat message ${i}:\nsender: ${chatMessage.sender}\n${chatMessage.message}`
|
||||
|
|
@ -269,7 +471,7 @@ class AIState {
|
|||
];
|
||||
|
||||
let fullAIResponse = '';
|
||||
await AIState.chatOpenAI.call(
|
||||
await AIState.chatModel.call(
|
||||
messages,
|
||||
{ signal: abortController.signal },
|
||||
[
|
||||
|
|
@ -293,17 +495,26 @@ class AIState {
|
|||
|
||||
async runChain(
|
||||
userMessage: string,
|
||||
chatContext: ChatMessage[],
|
||||
abortController: AbortController,
|
||||
updateCurrentAiMessage: (message: string) => void,
|
||||
addMessage: (message: ChatMessage) => void,
|
||||
debug = false,
|
||||
) {
|
||||
// Check if chain is initialized properly
|
||||
if (!isSupportedChain(AIState.chain)) {
|
||||
console.error(
|
||||
'Chain is not initialized properly, re-initializing chain: ',
|
||||
this.langChainParams.chainType
|
||||
);
|
||||
this.setChain(this.langChainParams.chainType, this.langChainParams.options);
|
||||
}
|
||||
|
||||
let fullAIResponse = '';
|
||||
try {
|
||||
switch(this.langChainParams.chainType) {
|
||||
case LLM_CHAIN:
|
||||
case ChainType.LLM_CHAIN:
|
||||
if (debug) {
|
||||
console.log('chain:', AIState.chain);
|
||||
console.log('Chat memory:', this.memory);
|
||||
}
|
||||
await AIState.chain.call(
|
||||
|
|
@ -321,7 +532,7 @@ class AIState {
|
|||
]
|
||||
);
|
||||
break;
|
||||
case RETRIEVAL_QA_CHAIN:
|
||||
case ChainType.RETRIEVAL_QA_CHAIN:
|
||||
if (debug) {
|
||||
console.log('embedding provider:', this.langChainParams.embeddingProvider);
|
||||
}
|
||||
|
|
@ -367,13 +578,13 @@ export function useAIState(
|
|||
): [
|
||||
string,
|
||||
(model: string) => void,
|
||||
string,
|
||||
(chain: string, options?: SetChainOptions) => void,
|
||||
ChainType,
|
||||
(chain: ChainType, options?: SetChainOptions) => void,
|
||||
() => void,
|
||||
] {
|
||||
const { langChainParams } = aiState;
|
||||
const [currentModel, setCurrentModel] = useState<string>(langChainParams.model);
|
||||
const [currentChain, setCurrentChain] = useState<string>(langChainParams.chainType);
|
||||
const [currentModel, setCurrentModel] = useState<string>(langChainParams.modelDisplayName);
|
||||
const [currentChain, setCurrentChain] = useState<ChainType>(langChainParams.chainType);
|
||||
const [, setChatMemory] = useState<BufferWindowMemory | null>(aiState.memory);
|
||||
|
||||
const clearChatMemory = () => {
|
||||
|
|
@ -381,12 +592,12 @@ export function useAIState(
|
|||
setChatMemory(aiState.memory);
|
||||
};
|
||||
|
||||
const setModel = (newModel: string) => {
|
||||
aiState.setModel(newModel);
|
||||
setCurrentModel(newModel);
|
||||
const setModel = (newModelDisplayName: string) => {
|
||||
aiState.setModel(newModelDisplayName);
|
||||
setCurrentModel(newModelDisplayName);
|
||||
};
|
||||
|
||||
const setChain = (newChain: string, options?: SetChainOptions) => {
|
||||
const setChain = (newChain: ChainType, options?: SetChainOptions) => {
|
||||
aiState.setChain(newChain, options);
|
||||
setCurrentChain(newChain);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,30 +28,33 @@ export interface ConversationalRetrievalChainParams {
|
|||
}
|
||||
}
|
||||
|
||||
// Add new chain types here
|
||||
export const LLM_CHAIN = 'llm_chain';
|
||||
// Issue where conversational retrieval chain gives rephrased question
|
||||
// when streaming: https://github.com/hwchase17/langchainjs/issues/754#issuecomment-1540257078
|
||||
// Temp workaround triggers CORS issue 'refused to set header user-agent'
|
||||
// TODO: Wait for official fix and use conversational retrieval chain instead of retrieval qa.
|
||||
export const CONVERSATIONAL_RETRIEVAL_QA_CHAIN = 'conversational_retrieval_chain';
|
||||
export const RETRIEVAL_QA_CHAIN = 'retrieval_qa';
|
||||
export const SUPPORTED_CHAIN_TYPES = new Set([
|
||||
LLM_CHAIN,
|
||||
RETRIEVAL_QA_CHAIN,
|
||||
CONVERSATIONAL_RETRIEVAL_QA_CHAIN,
|
||||
]);
|
||||
|
||||
// Add new chain types here
|
||||
export enum ChainType {
|
||||
LLM_CHAIN = 'llm_chain',
|
||||
RETRIEVAL_QA_CHAIN = 'retrieval_qa',
|
||||
// TODO: Wait for official fix and use conversational retrieval chain instead of retrieval qa.
|
||||
CONVERSATIONAL_RETRIEVAL_QA_CHAIN = 'conversational_retrieval_chain',
|
||||
}
|
||||
|
||||
class ChainFactory {
|
||||
public static instances: Map<string, BaseChain> = new Map();
|
||||
public static vectorStoreMap: Map<string, VectorStore> = new Map();
|
||||
|
||||
public static getLLMChain(args: LLMChainInput): BaseChain {
|
||||
let instance = ChainFactory.instances.get(LLM_CHAIN);
|
||||
public static createNewLLMChain(args: LLMChainInput): BaseChain {
|
||||
const instance = new ConversationChain(args as LLMChainInput);
|
||||
console.log('New chain created: ', instance._chainType());
|
||||
ChainFactory.instances.set(ChainType.LLM_CHAIN, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static getLLMChainFromMap(args: LLMChainInput): BaseChain {
|
||||
let instance = ChainFactory.instances.get(ChainType.LLM_CHAIN);
|
||||
if (!instance) {
|
||||
instance = new ConversationChain(args as LLMChainInput);
|
||||
console.log('New chain created: ', instance._chainType());
|
||||
ChainFactory.instances.set(LLM_CHAIN, instance);
|
||||
instance = ChainFactory.createNewLLMChain(args);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import AIState, { useAIState } from '@/aiState';
|
||||
import ChainFactory, { RETRIEVAL_QA_CHAIN } from '@/chainFactory';
|
||||
import ChainFactory, { ChainType } from '@/chainFactory';
|
||||
import ChatIcons from '@/components/ChatComponents/ChatIcons';
|
||||
import ChatInput from '@/components/ChatComponents/ChatInput';
|
||||
import ChatMessages from '@/components/ChatComponents/ChatMessages';
|
||||
|
|
@ -121,7 +121,7 @@ const Chat: React.FC<ChatProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const useActiveNoteAsContext = async () => {
|
||||
const forceRebuildActiveNoteContext = async () => {
|
||||
if (!app) {
|
||||
console.error('App instance is not available.');
|
||||
return;
|
||||
|
|
@ -141,26 +141,16 @@ const Chat: React.FC<ChatProps> = ({
|
|||
return;
|
||||
}
|
||||
|
||||
let activeNoteOnMessage: ChatMessage;
|
||||
const docHash = ChainFactory.getDocumentHash(noteContent);
|
||||
const vectorStore = ChainFactory.vectorStoreMap.get(docHash);
|
||||
if (vectorStore) {
|
||||
activeNoteOnMessage = {
|
||||
sender: AI_SENDER,
|
||||
message: `I have read [[${noteName}]].\n\n Please switch to "QA: Active Note" in Mode Selection to ask questions about it.`,
|
||||
isVisible: true,
|
||||
};
|
||||
} else {
|
||||
await aiState.buildIndex(noteContent, docHash);
|
||||
activeNoteOnMessage = {
|
||||
sender: AI_SENDER,
|
||||
message: `Reading [[${noteName}]]...\n\n Please switch to "QA: Active Note" in Mode Selection to ask questions about it.`,
|
||||
isVisible: true,
|
||||
};
|
||||
}
|
||||
await aiState.buildIndex(noteContent, docHash);
|
||||
const activeNoteOnMessage: ChatMessage = {
|
||||
sender: AI_SENDER,
|
||||
message: `Reading [[${noteName}]]...\n\n Please switch to "QA: Active Note" in Mode Selection to ask questions about it.`,
|
||||
isVisible: true,
|
||||
};
|
||||
|
||||
if (currentChain === RETRIEVAL_QA_CHAIN) {
|
||||
setChain(RETRIEVAL_QA_CHAIN, { noteContent });
|
||||
if (currentChain === ChainType.RETRIEVAL_QA_CHAIN) {
|
||||
setChain(ChainType.RETRIEVAL_QA_CHAIN, { noteContent });
|
||||
}
|
||||
|
||||
addMessage(activeNoteOnMessage);
|
||||
|
|
@ -312,7 +302,7 @@ const Chat: React.FC<ChatProps> = ({
|
|||
}
|
||||
}
|
||||
onSaveAsNote={handleSaveAsNote}
|
||||
onUseActiveNoteAsContext={useActiveNoteAsContext}
|
||||
onForceRebuildActiveNoteContext={forceRebuildActiveNoteContext}
|
||||
addMessage={addMessage}
|
||||
/>
|
||||
<ChatInput
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { SetChainOptions } from '@/aiState';
|
||||
import { AI_SENDER } from '@/constants';
|
||||
import {
|
||||
AI_SENDER,
|
||||
ChatModelDisplayNames,
|
||||
} from '@/constants';
|
||||
import {
|
||||
ChatMessage
|
||||
} from '@/sharedState';
|
||||
|
|
@ -13,23 +16,24 @@ import {
|
|||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { RETRIEVAL_QA_CHAIN } from '@/chainFactory';
|
||||
import { ChainType } from '@/chainFactory';
|
||||
import {
|
||||
RefreshIcon, SaveAsNoteIcon,
|
||||
StopIcon,
|
||||
UseActiveNoteAsContextIcon
|
||||
} from '@/components/Icons';
|
||||
import { stringToChainType } from '@/utils';
|
||||
import React from 'react';
|
||||
|
||||
interface ChatIconsProps {
|
||||
currentModel: string;
|
||||
setCurrentModel: (model: string) => void;
|
||||
currentChain: string;
|
||||
setCurrentChain: (chain: string, options?: SetChainOptions) => void;
|
||||
currentChain: ChainType;
|
||||
setCurrentChain: (chain: ChainType, options?: SetChainOptions) => void;
|
||||
onStopGenerating: () => void;
|
||||
onNewChat: () => void;
|
||||
onSaveAsNote: () => void;
|
||||
onUseActiveNoteAsContext: () => void;
|
||||
onForceRebuildActiveNoteContext: () => void;
|
||||
addMessage: (message: ChatMessage) => void;
|
||||
}
|
||||
|
||||
|
|
@ -41,22 +45,22 @@ const ChatIcons: React.FC<ChatIconsProps> = ({
|
|||
onStopGenerating,
|
||||
onNewChat,
|
||||
onSaveAsNote,
|
||||
onUseActiveNoteAsContext,
|
||||
onForceRebuildActiveNoteContext,
|
||||
addMessage,
|
||||
}) => {
|
||||
const [selectedChain, setSelectedChain] = useState<string>(currentChain);
|
||||
const [selectedChain, setSelectedChain] = useState<ChainType>(currentChain);
|
||||
|
||||
const handleModelChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setCurrentModel(event.target.value);
|
||||
};
|
||||
|
||||
const handleChainChange = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setSelectedChain(event.target.value);
|
||||
setSelectedChain(stringToChainType(event.target.value));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleRetrievalQAChain = async () => {
|
||||
if (selectedChain !== RETRIEVAL_QA_CHAIN) {
|
||||
if (selectedChain !== ChainType.RETRIEVAL_QA_CHAIN) {
|
||||
setCurrentChain(selectedChain);
|
||||
return;
|
||||
}
|
||||
|
|
@ -81,8 +85,9 @@ const ChatIcons: React.FC<ChatIconsProps> = ({
|
|||
isVisible: true,
|
||||
};
|
||||
addMessage(activeNoteOnMessage);
|
||||
|
||||
setCurrentChain(selectedChain, { noteContent });
|
||||
if (noteContent) {
|
||||
setCurrentChain(selectedChain, { noteContent });
|
||||
}
|
||||
};
|
||||
|
||||
handleRetrievalQAChain();
|
||||
|
|
@ -98,8 +103,17 @@ const ChatIcons: React.FC<ChatIconsProps> = ({
|
|||
value={currentModel}
|
||||
onChange={handleModelChange}
|
||||
>
|
||||
<option value='gpt-3.5-turbo'>GPT-3.5</option>
|
||||
<option value='gpt-4'>GPT-4</option>
|
||||
<option value={ChatModelDisplayNames.GPT_35_TURBO}>{ChatModelDisplayNames.GPT_35_TURBO}</option>
|
||||
<option value={ChatModelDisplayNames.GPT_35_TURBO_16K}>{ChatModelDisplayNames.GPT_35_TURBO_16K}</option>
|
||||
<option value={ChatModelDisplayNames.GPT_4}>{ChatModelDisplayNames.GPT_4}</option>
|
||||
<option value={ChatModelDisplayNames.GPT_4_32K}>{ChatModelDisplayNames.GPT_4_32K}</option>
|
||||
{/* <option value={ChatModelDisplayNames.CLAUDE_1}>{ChatModelDisplayNames.CLAUDE_1}</option>
|
||||
<option value={ChatModelDisplayNames.CLAUDE_1_100K}>{ChatModelDisplayNames.CLAUDE_1_100K}</option>
|
||||
<option value={ChatModelDisplayNames.CLAUDE_INSTANT_1}>{ChatModelDisplayNames.CLAUDE_INSTANT_1}</option>
|
||||
<option value={ChatModelDisplayNames.CLAUDE_INSTANT_1_100K}>{ChatModelDisplayNames.CLAUDE_INSTANT_1_100K}</option> */}
|
||||
<option value={ChatModelDisplayNames.AZURE_GPT_35_TURBO}>{ChatModelDisplayNames.AZURE_GPT_35_TURBO}</option>
|
||||
<option value={ChatModelDisplayNames.AZURE_GPT_4}>{ChatModelDisplayNames.AZURE_GPT_4}</option>
|
||||
<option value={ChatModelDisplayNames.AZURE_GPT_4_32K}>{ChatModelDisplayNames.AZURE_GPT_4_32K}</option>
|
||||
</select>
|
||||
<span className="tooltip-text">Model Selection</span>
|
||||
</div>
|
||||
|
|
@ -130,7 +144,7 @@ const ChatIcons: React.FC<ChatIconsProps> = ({
|
|||
<span className="tooltip-text">Mode Selection</span>
|
||||
</div>
|
||||
</div>
|
||||
<button className='chat-icon-button' onClick={onUseActiveNoteAsContext}>
|
||||
<button className='chat-icon-button' onClick={onForceRebuildActiveNoteContext}>
|
||||
<UseActiveNoteAsContextIcon className='icon-scaler' />
|
||||
<span className="tooltip-text">Rebuild Index for Active Note</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,78 @@ export const USER_SENDER = 'user';
|
|||
export const AI_SENDER = 'ai';
|
||||
export const DEFAULT_SYSTEM_PROMPT = 'You are Obsidian Copilot, a helpful assistant that integrates AI to Obsidian note-taking.';
|
||||
|
||||
// Embedding Providers
|
||||
export enum ChatModels {
|
||||
GPT_35_TURBO = 'gpt-3.5-turbo',
|
||||
GPT_35_TURBO_16K = 'gpt-3.5-turbo-16k',
|
||||
GPT_4 = 'gpt-4',
|
||||
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',
|
||||
AZURE_GPT_35_TURBO = 'gpt-35-turbo',
|
||||
}
|
||||
|
||||
export enum ChatModelDisplayNames {
|
||||
GPT_35_TURBO = 'GPT-3.5',
|
||||
GPT_35_TURBO_16K = 'GPT-3.5 16K',
|
||||
GPT_4 = 'GPT-4',
|
||||
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',
|
||||
AZURE_GPT_35_TURBO = 'AZURE GPT-3.5',
|
||||
AZURE_GPT_4 = 'AZURE GPT-4',
|
||||
AZURE_GPT_4_32K = 'AZURE GPT-4 32K',
|
||||
}
|
||||
|
||||
export const OPENAI_MODELS = new Set([
|
||||
ChatModelDisplayNames.GPT_35_TURBO,
|
||||
ChatModelDisplayNames.GPT_35_TURBO_16K,
|
||||
ChatModelDisplayNames.GPT_4,
|
||||
ChatModelDisplayNames.GPT_4_32K,
|
||||
]);
|
||||
|
||||
export const AZURE_MODELS = new Set([
|
||||
ChatModelDisplayNames.AZURE_GPT_35_TURBO,
|
||||
ChatModelDisplayNames.AZURE_GPT_4,
|
||||
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 DISPLAY_NAME_TO_MODEL: Record<string, string> = {
|
||||
[ChatModelDisplayNames.GPT_35_TURBO]: ChatModels.GPT_35_TURBO,
|
||||
[ChatModelDisplayNames.GPT_35_TURBO_16K]: ChatModels.GPT_35_TURBO_16K,
|
||||
[ChatModelDisplayNames.GPT_4]: ChatModels.GPT_4,
|
||||
[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.AZURE_GPT_35_TURBO]: ChatModels.AZURE_GPT_35_TURBO,
|
||||
[ChatModelDisplayNames.AZURE_GPT_4]: ChatModels.GPT_4,
|
||||
[ChatModelDisplayNames.AZURE_GPT_4_32K]: ChatModels.GPT_4_32K,
|
||||
};
|
||||
|
||||
// Model Providers
|
||||
export const OPENAI = 'openai';
|
||||
export const HUGGINGFACE = 'huggingface';
|
||||
export const COHEREAI = 'cohereai';
|
||||
export const AZURE_OPENAI = 'azure_openai';
|
||||
export const ANTHROPIC = 'anthropic';
|
||||
|
||||
export const VENDOR_MODELS: Record<string, Set<string>> = {
|
||||
[OPENAI]: OPENAI_MODELS,
|
||||
[AZURE_OPENAI]: AZURE_MODELS,
|
||||
[ANTHROPIC]: CLAUDE_MODELS,
|
||||
};
|
||||
|
||||
// Embedding Models
|
||||
export const DISTILBERT_NLI = 'sentence-transformers/distilbert-base-nli-mean-tokens';
|
||||
|
|
@ -16,10 +84,16 @@ export const INSTRUCTOR_XL = 'hkunlp/instructor-xl'; // Inference API is off for
|
|||
export const MPNET_V2 = 'sentence-transformers/all-mpnet-base-v2'; // Inference API returns 400
|
||||
|
||||
export const DEFAULT_SETTINGS: CopilotSettings = {
|
||||
openAiApiKey: '',
|
||||
openAIApiKey: '',
|
||||
huggingfaceApiKey: '',
|
||||
cohereApiKey: '',
|
||||
defaultModel: 'gpt-3.5-turbo',
|
||||
anthropicApiKey: '',
|
||||
azureOpenAIApiKey: '',
|
||||
azureOpenAIApiInstanceName: '',
|
||||
azureOpenAIApiDeploymentName: '',
|
||||
azureOpenAIApiVersion: '',
|
||||
defaultModel: ChatModels.GPT_35_TURBO_16K,
|
||||
defaultModelDisplayName: ChatModelDisplayNames.GPT_35_TURBO_16K,
|
||||
temperature: 0.7,
|
||||
maxTokens: 1000,
|
||||
contextTurns: 3,
|
||||
|
|
|
|||
|
|
@ -14,19 +14,12 @@ export const getAIResponse = async (
|
|||
debug = false,
|
||||
) => {
|
||||
const {
|
||||
openAiApiKey,
|
||||
model,
|
||||
temperature,
|
||||
maxTokens,
|
||||
systemMessage,
|
||||
chatContextTurns,
|
||||
} = aiState.langChainParams;
|
||||
if (!openAiApiKey) {
|
||||
new Notice(
|
||||
'No OpenAI API key provided. Please set it in Copilot settings, and restart the plugin.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
|
|
@ -42,12 +35,19 @@ export const getAIResponse = async (
|
|||
);
|
||||
}
|
||||
|
||||
await aiState.runChain(
|
||||
userMessage.message,
|
||||
chatContext,
|
||||
abortController,
|
||||
updateCurrentAiMessage,
|
||||
addMessage,
|
||||
debug,
|
||||
);
|
||||
try {
|
||||
// TODO: Need to run certain models without langchain
|
||||
// it will mean no retrieval qa mode for those models!
|
||||
|
||||
await aiState.runChain(
|
||||
userMessage.message,
|
||||
abortController,
|
||||
updateCurrentAiMessage,
|
||||
addMessage,
|
||||
debug,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Model request failed:', error);
|
||||
new Notice('Model request failed:', error);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
34
src/main.ts
34
src/main.ts
|
|
@ -1,5 +1,5 @@
|
|||
import AIState, { LangChainParams } from '@/aiState';
|
||||
import { LLM_CHAIN } from '@/chainFactory';
|
||||
import AIState, { LangChainParams, SetChainOptions } from '@/aiState';
|
||||
import { ChainType } from '@/chainFactory';
|
||||
import { AddPromptModal } from "@/components/AddPromptModal";
|
||||
import CopilotView from '@/components/CopilotView';
|
||||
import { LanguageModal } from "@/components/LanguageModal";
|
||||
|
|
@ -16,10 +16,16 @@ import PouchDB from 'pouchdb';
|
|||
|
||||
|
||||
export interface CopilotSettings {
|
||||
openAiApiKey: string;
|
||||
openAIApiKey: string;
|
||||
huggingfaceApiKey: string;
|
||||
cohereApiKey: string;
|
||||
anthropicApiKey: string;
|
||||
azureOpenAIApiKey: string;
|
||||
azureOpenAIApiInstanceName: string;
|
||||
azureOpenAIApiDeploymentName: string;
|
||||
azureOpenAIApiVersion: string;
|
||||
defaultModel: string;
|
||||
defaultModelDisplayName: string;
|
||||
temperature: number;
|
||||
maxTokens: number;
|
||||
contextTurns: number;
|
||||
|
|
@ -407,25 +413,37 @@ export default class CopilotPlugin extends Plugin {
|
|||
|
||||
getAIStateParams(): LangChainParams {
|
||||
const {
|
||||
openAiApiKey,
|
||||
openAIApiKey,
|
||||
huggingfaceApiKey,
|
||||
cohereApiKey,
|
||||
anthropicApiKey,
|
||||
azureOpenAIApiKey,
|
||||
azureOpenAIApiInstanceName,
|
||||
azureOpenAIApiDeploymentName,
|
||||
azureOpenAIApiVersion,
|
||||
temperature,
|
||||
maxTokens,
|
||||
contextTurns,
|
||||
embeddingProvider,
|
||||
} = sanitizeSettings(this.settings);
|
||||
return {
|
||||
openAiApiKey: openAiApiKey,
|
||||
huggingfaceApiKey: huggingfaceApiKey,
|
||||
cohereApiKey: cohereApiKey,
|
||||
openAIApiKey,
|
||||
huggingfaceApiKey,
|
||||
cohereApiKey,
|
||||
anthropicApiKey,
|
||||
azureOpenAIApiKey,
|
||||
azureOpenAIApiInstanceName,
|
||||
azureOpenAIApiDeploymentName,
|
||||
azureOpenAIApiVersion,
|
||||
model: this.settings.defaultModel,
|
||||
modelDisplayName: this.settings.defaultModelDisplayName,
|
||||
temperature: Number(temperature),
|
||||
maxTokens: Number(maxTokens),
|
||||
systemMessage: DEFAULT_SYSTEM_PROMPT || this.settings.userSystemPrompt,
|
||||
chatContextTurns: Number(contextTurns),
|
||||
embeddingProvider: embeddingProvider,
|
||||
chainType: LLM_CHAIN,
|
||||
chainType: ChainType.LLM_CHAIN, // Set LLM_CHAIN as default ChainType
|
||||
options: { forceNewCreation: true } as SetChainOptions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
148
src/settings.ts
148
src/settings.ts
|
|
@ -1,4 +1,11 @@
|
|||
import { COHEREAI, DEFAULT_SETTINGS, HUGGINGFACE, OPENAI } from "@/constants";
|
||||
import {
|
||||
COHEREAI,
|
||||
ChatModelDisplayNames,
|
||||
ChatModels,
|
||||
DEFAULT_SETTINGS,
|
||||
HUGGINGFACE,
|
||||
OPENAI
|
||||
} from "@/constants";
|
||||
import CopilotPlugin from "@/main";
|
||||
import { App, DropdownComponent, Notice, PluginSettingTab, Setting } from "obsidian";
|
||||
|
||||
|
|
@ -30,7 +37,37 @@ export class CopilotSettingTab extends PluginSettingTab {
|
|||
{ text: 'Please reload the plugin when you change any setting below.' }
|
||||
);
|
||||
|
||||
containerEl.createEl('h4', { text: 'OpenAI API Settings' });
|
||||
new Setting(containerEl)
|
||||
.setName("Default Model")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("The default model to use, only takes effect when you ");
|
||||
frag.createEl('strong', { text: "restart the plugin" });
|
||||
})
|
||||
)
|
||||
.addDropdown((dropdown: DropdownComponent) => {
|
||||
dropdown
|
||||
.addOption(ChatModels.GPT_35_TURBO, ChatModelDisplayNames.GPT_35_TURBO)
|
||||
.addOption(ChatModels.GPT_35_TURBO_16K, ChatModelDisplayNames.GPT_35_TURBO_16K)
|
||||
.addOption(ChatModels.GPT_4, ChatModelDisplayNames.GPT_4)
|
||||
.addOption(ChatModels.GPT_4_32K, ChatModelDisplayNames.GPT_4_32K)
|
||||
// .addOption(ChatModels.CLAUDE_1, ChatModelDisplayNames.CLAUDE_1)
|
||||
// .addOption(ChatModels.CLAUDE_1_100K, ChatModelDisplayNames.CLAUDE_1_100K)
|
||||
// .addOption(ChatModels.CLAUDE_INSTANT_1, ChatModelDisplayNames.CLAUDE_INSTANT_1)
|
||||
// .addOption(ChatModels.CLAUDE_INSTANT_1_100K, ChatModelDisplayNames.CLAUDE_INSTANT_1_100K)
|
||||
.addOption(ChatModels.AZURE_GPT_35_TURBO, ChatModelDisplayNames.AZURE_GPT_35_TURBO)
|
||||
.addOption(ChatModels.GPT_4, ChatModelDisplayNames.AZURE_GPT_4)
|
||||
.addOption(ChatModels.GPT_4_32K, ChatModelDisplayNames.AZURE_GPT_4_32K)
|
||||
.setValue(this.plugin.settings.defaultModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultModel = value;
|
||||
this.plugin.settings.defaultModelDisplayName = ChatModelDisplayNames[value as keyof typeof ChatModels];
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
containerEl.createEl('h4', { text: 'API Settings' });
|
||||
containerEl.createEl('h6', { text: 'OpenAI API' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Your OpenAI API key")
|
||||
|
|
@ -57,32 +94,103 @@ export class CopilotSettingTab extends PluginSettingTab {
|
|||
text.inputEl.style.width = "100%";
|
||||
text
|
||||
.setPlaceholder("OpenAI API key")
|
||||
.setValue(this.plugin.settings.openAiApiKey)
|
||||
.setValue(this.plugin.settings.openAIApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openAiApiKey = value;
|
||||
this.plugin.settings.openAIApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
// 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: '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("Default Model")
|
||||
.setDesc(
|
||||
createFragment((frag) => {
|
||||
frag.appendText("The default model to use, only takes effect when you ");
|
||||
frag.createEl('strong', { text: "restart the plugin" });
|
||||
})
|
||||
)
|
||||
.addDropdown((dropdown: DropdownComponent) => {
|
||||
dropdown
|
||||
.addOption('gpt-3.5-turbo', 'GPT-3.5')
|
||||
.addOption('gpt-4', 'GPT-4')
|
||||
.setValue(this.plugin.settings.defaultModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultModel = value;
|
||||
.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();
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
containerEl.createEl(
|
||||
'h6',
|
||||
|
|
|
|||
62
src/utils.ts
62
src/utils.ts
|
|
@ -1,9 +1,69 @@
|
|||
import { DEFAULT_SETTINGS, USER_SENDER } from '@/constants';
|
||||
import { ChainType } from '@/chainFactory';
|
||||
import {
|
||||
ANTHROPIC,
|
||||
AZURE_MODELS,
|
||||
AZURE_OPENAI,
|
||||
CLAUDE_MODELS,
|
||||
DEFAULT_SETTINGS,
|
||||
DISPLAY_NAME_TO_MODEL,
|
||||
OPENAI,
|
||||
OPENAI_MODELS,
|
||||
USER_SENDER,
|
||||
} from '@/constants';
|
||||
import { CopilotSettings } from '@/main';
|
||||
import { ChatMessage } from '@/sharedState';
|
||||
import {
|
||||
BaseChain,
|
||||
LLMChain,
|
||||
RetrievalQAChain
|
||||
} from "langchain/chains";
|
||||
import moment from 'moment';
|
||||
import { TFile } from 'obsidian';
|
||||
|
||||
export const stringToChainType = (chain: string): ChainType => {
|
||||
switch(chain) {
|
||||
case 'llm_chain':
|
||||
return ChainType.LLM_CHAIN;
|
||||
case 'retrieval_qa':
|
||||
return ChainType.RETRIEVAL_QA_CHAIN;
|
||||
default:
|
||||
throw new Error(`Unknown chain type: ${chain}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const isLLMChain = (chain: BaseChain): chain is LLMChain => {
|
||||
return 'llm' in chain && chain.llm !== undefined;
|
||||
}
|
||||
|
||||
export const isRetrievalQAChain = (chain: BaseChain): chain is RetrievalQAChain => {
|
||||
return 'retriever' in chain && chain.retriever !== undefined;
|
||||
}
|
||||
|
||||
export const isSupportedChain = (chain: BaseChain): chain is BaseChain => {
|
||||
return isLLMChain(chain) || isRetrievalQAChain(chain);
|
||||
}
|
||||
|
||||
export const getModelName = (modelDisplayName: string): string => {
|
||||
return DISPLAY_NAME_TO_MODEL[modelDisplayName];
|
||||
}
|
||||
|
||||
export const getModelVendorMap = (): Record<string, string> => {
|
||||
const model_to_vendor: Record<string, string> = {};
|
||||
|
||||
for (const model of OPENAI_MODELS) {
|
||||
model_to_vendor[model] = OPENAI;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Returns the last N messages from the chat history,
|
||||
// last one being the newest ai message
|
||||
export const getChatContext = (chatHistory: ChatMessage[], contextSize: number) => {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ If your plugin does not need CSS, delete this file.
|
|||
padding: 8px 8px 0;
|
||||
order: 1;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.icon-scaler {
|
||||
|
|
|
|||
Loading…
Reference in a new issue