add claude

This commit is contained in:
socketteer 2024-04-04 17:50:49 -07:00
parent 5d537e57c1
commit 382ca9ac78
4 changed files with 296 additions and 29 deletions

View file

@ -1,4 +1,4 @@
export const PROVIDERS = ["cohere", "textsynth", "ocp", "openai", "openai-chat", "azure", "azure-chat"];
export const PROVIDERS = ["cohere", "textsynth", "ocp", "openai", "openai-chat", "azure", "azure-chat", "anthropic"];
export type Provider = (typeof PROVIDERS)[number];
type ProviderProps = {
@ -7,6 +7,7 @@ type ProviderProps = {
"ocp": { url: string };
"azure": { url: string };
"azure-chat": { url: string };
"anthropic": { url: string };
};
type SharedPresetSettings = {

146
main.ts
View file

@ -15,6 +15,10 @@ import {
getPreset,
} from './common';
// import {
// claudeCompletion,
// } from './claude';
import {
App,
Editor,
@ -32,6 +36,8 @@ import { ViewPlugin } from "@codemirror/view";
import { Configuration as AzureConfiguration, OpenAIApi as AzureOpenAIApi} from "azure-openai";
import { Configuration, OpenAIApi } from "openai";
import * as cohere from "cohere-ai";
import Anthropic from '@anthropic-ai/sdk';
import cl100k from "gpt-tokenizer";
import p50k from "gpt-tokenizer/esm/model/text-davinci-003";
@ -92,6 +98,8 @@ export default class LoomPlugin extends Plugin {
openai: OpenAIApi;
azure: AzureOpenAIApi;
anthropic: Anthropic;
anthropicApiKey: string;
rendering = false;
@ -137,29 +145,47 @@ export default class LoomPlugin extends Plugin {
initializeProviders() {
const preset = getPreset(this.settings);
if (preset === undefined) return;
if (preset.provider === "openai") {
this.openai = new OpenAIApi(new Configuration({
apiKey: preset.apiKey,
// @ts-expect-error TODO
organization: preset.organization,
}));
} else if (preset.provider == "cohere")
cohere.init(preset.apiKey);
else if (preset.provider == "azure") {
// @ts-expect-error TODO
const url = preset.url;
if (preset === undefined) return;
if (preset.provider === "openai") {
this.openai = new OpenAIApi(new Configuration({
apiKey: preset.apiKey,
// @ts-expect-error TODO
organization: preset.organization,
}));
} else if (preset.provider == "cohere")
cohere.init(preset.apiKey);
else if (preset.provider == "azure") {
// @ts-expect-error TODO
const url = preset.url;
if (!preset.apiKey || !url) return;
this.azure = new AzureOpenAIApi(new AzureConfiguration({
apiKey: preset.apiKey,
azure: {
apiKey: preset.apiKey,
endpoint: url,
},
}));
}
if (!preset.apiKey || !url) return;
this.azure = new AzureOpenAIApi(new AzureConfiguration({
apiKey: preset.apiKey,
azure: {
apiKey: preset.apiKey,
endpoint: url,
},
}));
} else if (preset.provider == "anthropic") {
//(property) ClientOptions.fetch?: Fetch | undefined
//Specify a custom fetch function implementation.
//If not provided, we use node-fetch on Node.js and otherwise expect that fetch is defined globally.
// expects Promise<Response> as return value
this.anthropicApiKey = preset.apiKey;
this.anthropic = new Anthropic({
apiKey: preset.apiKey,
// fetch:
defaultHeaders: {
'anthropic-version': '2023-06-01',
'anthropic-beta': 'messages-2023-12-15',
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Credentials": "true",
},
});
}
}
apiKeySet() {
@ -310,6 +336,17 @@ export default class LoomPlugin extends Plugin {
hotkeys: [{ modifiers: ["Ctrl", "Shift"], key: " " }],
});
this.addCommand({
id: "bookmark",
name: "Bookmark current node",
checkCallback: (checking: boolean) =>
withState(checking, (state) => {
this.app.workspace.trigger("loom:toggle-bookmark", state.current);
}),
hotkeys: [{ modifiers: ["Ctrl"], key: "b" }],
});
const withState = (checking: boolean, callback: (state: NoteState) => void) => {
const file = this.app.workspace.getActiveFile();
if (!file || file.extension !== "md") return false;
@ -1232,6 +1269,9 @@ export default class LoomPlugin extends Plugin {
// the tokenization and completion depend on the provider,
// so call a different method depending on the provider
console.log("prompt", prompt);
const completionMethods: Record<Provider, (prompt: string) => Promise<CompletionResult>> = {
cohere: this.completeCohere,
textsynth: this.completeTextSynth,
@ -1240,6 +1280,7 @@ export default class LoomPlugin extends Plugin {
"openai-chat": this.completeOpenAIChat,
azure: this.completeAzure,
"azure-chat": this.completeAzureChat,
anthropic: this.completeAnthropic,
};
let result;
try {
@ -1254,6 +1295,8 @@ export default class LoomPlugin extends Plugin {
}
const rawCompletions = result.completions;
console.log("rawCompletions", rawCompletions);
// escape and clean up the completions
const completions = rawCompletions.map((completion: string) => {
if (!completion) completion = ""; // empty completions are null, apparently
@ -1473,6 +1516,48 @@ export default class LoomPlugin extends Plugin {
return result;
}
async completeAnthropic(prompt: string) {
prompt = this.trimOpenAIPrompt(prompt);
// let result: CompletionResult;
try {
// console.log("prompt", prompt);
const response = await requestUrl({
url: "https://api.anthropic.com/v1/messages",
method: "POST",
headers: {
"content-type": "application/json",
"anthropic-version": "2023-06-01",
'x-api-key': this.anthropicApiKey,
},
body: JSON.stringify({
model: getPreset(this.settings).model,
max_tokens: this.settings.maxTokens,
temperature: this.settings.temperature,
system: "The assistant is in CLI simulation mode, and responds to the user's CLI commands only with the output of the command.",
messages: [
{"role": "user", "content": `<cmd>cat untitled.txt</cmd>`},
{"role": "assistant", "content": `${prompt}`}
],
}),
});
const result: CompletionResult = response.status === 200
? { ok: true, completions: [response.json.content[0]?.text || "<no text>"] }
: { ok: false, status: response.status, message: "" };
// console.log("response", result);
return result;
} catch (e) {
console.error(e)
const result: CompletionResult = { ok: false, status: 400, message: "an error was encountered" };
return result;
}
}
async loadSettings() {
const settings = (await this.loadData())?.settings || {};
this.settings = Object.assign({}, DEFAULT_SETTINGS, settings);
@ -1578,6 +1663,7 @@ class LoomSettingTab extends PluginSettingTab {
fillInModelDropdown.createEl("option", { text: "code-davinci-002", attr: { value: "code-davinci-002" } });
fillInModelDropdown.createEl("option", { text: "code-davinci-002 (Proxy)", attr: { value: "code-davinci-002-proxy" } });
fillInModelDropdown.createEl("option", { text: "gpt-4-base", attr: { value: "gpt-4-base" } });
fillInModelDropdown.createEl("option", { text: "claude-3-opus", attr: { value: "claude-3-opus" } });
fillInModelDropdown.addEventListener("change", (event) => {
const value = (event.target as HTMLSelectElement).value;
@ -1606,6 +1692,12 @@ class LoomSettingTab extends PluginSettingTab {
this.plugin.settings.modelPresets[this.plugin.settings.modelPreset].contextLength = 8192;
break;
}
case "claude-3-opus": {
this.plugin.settings.modelPresets[this.plugin.settings.modelPreset].provider = "anthropic";
this.plugin.settings.modelPresets[this.plugin.settings.modelPreset].model = "claude-3-opus-20240229";
this.plugin.settings.modelPresets[this.plugin.settings.modelPreset].contextLength = 20000;
break;
}
}
this.plugin.save();
updatePresetFields();
@ -1624,6 +1716,7 @@ class LoomSettingTab extends PluginSettingTab {
restoreApiKeyDropdown.createEl("option", { text: "Cohere", attr: { value: "cohere" } });
restoreApiKeyDropdown.createEl("option", { text: "TextSynth", attr: { value: "textsynth" } });
restoreApiKeyDropdown.createEl("option", { text: "Azure", attr: { value: "azure" } });
restoreApiKeyDropdown.createEl("option", { text: "Anthropic", attr: { value: "anthropic" } });
restoreApiKeyDropdown.addEventListener("change", (event) => {
const provider = (event.target as HTMLSelectElement).value as Provider;
@ -1675,6 +1768,14 @@ class LoomSettingTab extends PluginSettingTab {
};
break;
}
case "anthropic": {
preset = {
...preset,
// @ts-expect-error
apiKey: this.plugin.settings.anthropicApiKey || "",
};
break;
}
default: {
throw new Error(`Unknown provider: ${provider}`);
}
@ -1714,6 +1815,7 @@ class LoomSettingTab extends PluginSettingTab {
"openai-chat": "OpenAI (Chat)",
azure: "Azure",
"azure-chat": "Azure (Chat)",
anthropic: "Anthropic",
};
dropdown.addOptions(options);
dropdown.setValue(this.plugin.settings.modelPresets[this.plugin.settings.modelPreset].provider);

175
package-lock.json generated
View file

@ -1,14 +1,15 @@
{
"name": "obsidian-loom",
"version": "1.17.0",
"version": "1.20.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-loom",
"version": "1.17.0",
"version": "1.20.3",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.20.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.9.3",
"@types/lodash": "^4.14.191",
@ -34,6 +35,29 @@
"typescript": "4.7.4"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.20.0.tgz",
"integrity": "sha512-VpkVetFHQ31cendkcAGnZo08yWDJeo4jPRRtsNEgVKoF+NkzZDVktGvK4ZB7mhPrUQlCcjd4DzjijiWnZ4qIog==",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7",
"web-streams-polyfill": "^3.2.1"
}
},
"node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
"version": "18.19.29",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz",
"integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@codemirror/state": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz",
@ -194,8 +218,16 @@
"node_modules/@types/node": {
"version": "16.18.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.12.tgz",
"integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==",
"dev": true
"integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw=="
},
"node_modules/@types/node-fetch": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
"integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
}
},
"node_modules/@types/roman-numerals": {
"version": "0.3.0",
@ -411,6 +443,17 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@ -432,6 +475,17 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/agentkeepalive": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
"integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
"dependencies": {
"humanize-ms": "^1.2.1"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@ -937,6 +991,14 @@
"node": ">=0.10.0"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -1083,6 +1145,31 @@
"node": ">= 6"
}
},
"node_modules/form-data-encoder": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
},
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"dependencies": {
"node-domexception": "1.0.0",
"web-streams-polyfill": "4.0.0-beta.3"
},
"engines": {
"node": ">= 12.20"
}
},
"node_modules/formdata-node/node_modules/web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
"engines": {
"node": ">= 14"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -1179,6 +1266,14 @@
"node": ">=8"
}
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"dependencies": {
"ms": "^2.0.0"
}
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@ -1419,8 +1514,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/natural-compare": {
"version": "1.4.0",
@ -1434,6 +1528,43 @@
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/obsidian": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.1.1.tgz",
@ -1794,6 +1925,11 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@ -1858,6 +1994,11 @@
"node": ">=4.2.0"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@ -1888,6 +2029,28 @@
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg=="
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View file

@ -24,6 +24,7 @@
"typescript": "4.7.4"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.20.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.9.3",
"@types/lodash": "^4.14.191",