prettier formatting

This commit is contained in:
John Mavrick 2023-11-26 12:19:45 -08:00
parent 1a61117aa9
commit 96ac84eb5c
29 changed files with 4449 additions and 4382 deletions

View File

@ -2,9 +2,7 @@
"root": true, "root": true,
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"env": { "node": true }, "env": { "node": true },
"plugins": [ "plugins": ["@typescript-eslint"],
"@typescript-eslint"
],
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
@ -20,4 +18,4 @@
"no-prototype-builtins": "off", "no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off" "@typescript-eslint/no-empty-function": "off"
} }
} }

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"printWidth": 80,
"useTabs": false,
"singleQuote": true
}

View File

@ -1,43 +1,43 @@
import esbuild from "esbuild"; import esbuild from 'esbuild';
import process from "process"; import process from 'process';
import builtins from "builtin-modules"; import builtins from 'builtin-modules';
const banner = const banner = `/*
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin if you want to view the source, please visit the github repository of this plugin
*/ */
`; `;
const prod = (process.argv[2] === "production"); const prod = process.argv[2] === 'production';
const context = await esbuild.context({ const context = await esbuild.context({
banner: { banner: {
js: banner, js: banner,
}, },
entryPoints: ["main.ts"], entryPoints: ['main.ts'],
bundle: true, bundle: true,
external: [ external: [
"obsidian", 'obsidian',
"electron", 'electron',
"@codemirror/autocomplete", '@codemirror/autocomplete',
"@codemirror/collab", '@codemirror/collab',
"@codemirror/commands", '@codemirror/commands',
"@codemirror/language", '@codemirror/language',
"@codemirror/lint", '@codemirror/lint',
"@codemirror/search", '@codemirror/search',
"@codemirror/state", '@codemirror/state',
"@codemirror/view", '@codemirror/view',
"@lezer/common", '@lezer/common',
"@lezer/highlight", '@lezer/highlight',
"@lezer/lr", '@lezer/lr',
...builtins], ...builtins,
format: "cjs", ],
target: "es2018", format: 'cjs',
logLevel: "info", target: 'es2018',
sourcemap: prod ? false : "inline", logLevel: 'info',
sourcemap: prod ? false : 'inline',
treeShaking: true, treeShaking: true,
outfile: "main.js", outfile: 'main.js',
}); });
if (prod) { if (prod) {

43
main.ts
View File

@ -17,7 +17,7 @@ const DEFAULT_SETTINGS: ObsidianIntelligenceSettings = {
activeThread: undefined, activeThread: undefined,
activeAssistant: undefined, activeAssistant: undefined,
activeAssistantFiles: undefined, activeAssistantFiles: undefined,
} };
export default class ObsidianIntelligence extends Plugin { export default class ObsidianIntelligence extends Plugin {
settings: ObsidianIntelligenceSettings; settings: ObsidianIntelligenceSettings;
@ -25,14 +25,15 @@ export default class ObsidianIntelligence extends Plugin {
async onload() { async onload() {
await this.loadSettings(); await this.loadSettings();
this.registerView( this.registerView(VIEW_TYPE, (leaf) => new AppView(leaf, this));
VIEW_TYPE,
(leaf) => (new AppView(leaf, this))
);
const ribbonIconEl = this.addRibbonIcon('bot', 'Open Obsidian Intelligence', (evt: MouseEvent) => { const ribbonIconEl = this.addRibbonIcon(
'bot',
'Open Obsidian Intelligence',
(evt: MouseEvent) => {
this.activateView(); this.activateView();
}); },
);
// Perform additional things with the ribbon // Perform additional things with the ribbon
ribbonIconEl.addClass('my-plugin-ribbon-class'); ribbonIconEl.addClass('my-plugin-ribbon-class');
@ -41,24 +42,26 @@ export default class ObsidianIntelligence extends Plugin {
statusBarItemEl.setText('Status Bar Text'); statusBarItemEl.setText('Status Bar Text');
this.addCommand({ this.addCommand({
id: "obsidian-intelligence-view-open", id: 'obsidian-intelligence-view-open',
name: "Open Obsidian Intelligence", name: 'Open Obsidian Intelligence',
hotkeys: [{ modifiers: ["Mod", "Shift"], key: "I"}], hotkeys: [{ modifiers: ['Mod', 'Shift'], key: 'I' }],
callback: () => { callback: () => {
this.activateView(); this.activateView();
} },
}); });
// This adds a settings tab so the user can configure various aspects of the plugin // This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new OISettingTab(this.app, this)); this.addSettingTab(new OISettingTab(this.app, this));
} }
onunload() { onunload() {}
}
async loadSettings() { async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData(),
);
} }
async saveSettings() { async saveSettings() {
@ -74,7 +77,7 @@ export default class ObsidianIntelligence extends Plugin {
}); });
this.app.workspace.revealLeaf( this.app.workspace.revealLeaf(
this.app.workspace.getLeavesOfType(VIEW_TYPE)[0] this.app.workspace.getLeavesOfType(VIEW_TYPE)[0],
); );
} }
} }
@ -88,19 +91,21 @@ class OISettingTab extends PluginSettingTab {
} }
display(): void { display(): void {
const {containerEl} = this; const { containerEl } = this;
containerEl.empty(); containerEl.empty();
new Setting(containerEl) new Setting(containerEl)
.setName('OpenAI Key') .setName('OpenAI Key')
.setDesc('Can find it https://platform.openai.com/api-keys') .setDesc('Can find it https://platform.openai.com/api-keys')
.addText(text => text .addText((text) =>
text
.setPlaceholder('Enter your API Key') .setPlaceholder('Enter your API Key')
.setValue(this.plugin.settings.openaiKey) .setValue(this.plugin.settings.openaiKey)
.onChange(async (value) => { .onChange(async (value) => {
this.plugin.settings.openaiKey = value; this.plugin.settings.openaiKey = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
})); }),
);
} }
} }

16
package-lock.json generated
View File

@ -28,6 +28,7 @@
"builtin-modules": "3.3.0", "builtin-modules": "3.3.0",
"esbuild": "0.17.3", "esbuild": "0.17.3",
"obsidian": "latest", "obsidian": "latest",
"prettier": "^3.1.0",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4" "typescript": "4.7.4"
} }
@ -2524,6 +2525,21 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/property-expr": { "node_modules/property-expr": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",

View File

@ -6,7 +6,8 @@
"scripts": { "scripts": {
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json" "version": "node version-bump.mjs && git add manifest.json versions.json",
"prettier": "prettier --write ."
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -18,6 +19,7 @@
"builtin-modules": "3.3.0", "builtin-modules": "3.3.0",
"esbuild": "0.17.3", "esbuild": "0.17.3",
"obsidian": "latest", "obsidian": "latest",
"prettier": "^3.1.0",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4" "typescript": "4.7.4"
}, },

View File

@ -1,2 +1,3 @@
# This is a test file # This is a test file
wow wow

View File

@ -1,18 +1,18 @@
import { ItemView, WorkspaceLeaf } from "obsidian"; import { ItemView, WorkspaceLeaf } from 'obsidian';
import * as React from "react"; import * as React from 'react';
import { Root, createRoot } from "react-dom/client"; import { Root, createRoot } from 'react-dom/client';
import PluginView from "./PluginView"; import PluginView from './PluginView';
import { App } from "obsidian"; import { App } from 'obsidian';
import ObsidianIntelligence from "../../main"; import ObsidianIntelligence from '../../main';
import OpenAI from "openai"; import OpenAI from 'openai';
export const VIEW_TYPE = "example-view"; export const VIEW_TYPE = 'example-view';
export const AppContext = React.createContext<App | undefined>(undefined); export const AppContext = React.createContext<App | undefined>(undefined);
export const PluginContext = React.createContext<ObsidianIntelligence | undefined>( export const PluginContext = React.createContext<
undefined ObsidianIntelligence | undefined
); >(undefined);
export const OpenAIContext = React.createContext<OpenAI | undefined>(undefined); export const OpenAIContext = React.createContext<OpenAI | undefined>(undefined);
@ -57,10 +57,10 @@ export class AppView extends ItemView {
} }
getDisplayText() { getDisplayText() {
return "Obsidian Intelligence"; return 'Obsidian Intelligence';
} }
getIcon(): string { getIcon(): string {
return "bot"; return 'bot';
} }
async onOpen() { async onOpen() {
@ -74,7 +74,7 @@ export class AppView extends ItemView {
{/* </CommandsContext.Provider> */} {/* </CommandsContext.Provider> */}
</OpenAIContext.Provider> </OpenAIContext.Provider>
</PluginContext.Provider> </PluginContext.Provider>
</AppContext.Provider> </AppContext.Provider>,
); );
} }
async onClose() { async onClose() {

View File

@ -1,31 +1,40 @@
import OpenAI from 'openai'; import OpenAI from 'openai';
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import Chatbox from './components/Chatbox'; import Chatbox from './components/Chatbox';
import { useOpenAI, usePlugin, } from './AppView'; import { useOpenAI, usePlugin } from './AppView';
import MessageInput from './components/MessageInput'; import MessageInput from './components/MessageInput';
// import FilesUploadUI from './components/FilesUploadUI'; // import FilesUploadUI from './components/FilesUploadUI';
import AssistantManager from './components/AssistantManager'; import AssistantManager from './components/AssistantManager';
import { IThread, ThreadAnnotationFile } from './types'; import { IThread, ThreadAnnotationFile } from './types';
import { createNotice } from '@/utils/Logs'; import { createNotice } from '@/utils/Logs';
const listQueryOptions: OpenAI.Beta.Threads.MessageListParams = { order: 'asc'}; const listQueryOptions: OpenAI.Beta.Threads.MessageListParams = {
order: 'asc',
};
const PluginView = () => { const PluginView = () => {
const plugin = usePlugin(); const plugin = usePlugin();
const openaiInstance = useOpenAI(); const openaiInstance = useOpenAI();
const [messages, setMessages] = useState<OpenAI.Beta.Threads.ThreadMessage[]>([]); const [messages, setMessages] = useState<
OpenAI.Beta.Threads.ThreadMessage[]
>([]);
// const [files, setFiles] = useState<string[]>([]); // const [files, setFiles] = useState<string[]>([]);
const [assistants, setAssistants] = useState<OpenAI.Beta.Assistant[]>([]); const [assistants, setAssistants] = useState<OpenAI.Beta.Assistant[]>([]);
const [threads, setThreads] = useState<IThread[]>([]); const [threads, setThreads] = useState<IThread[]>([]);
const [activeAssistant, setActiveAssistant] = useState<OpenAI.Beta.Assistant | undefined>(undefined); const [activeAssistant, setActiveAssistant] = useState<
const [activeThread, setActiveThread] = useState<IThread | undefined>(undefined); OpenAI.Beta.Assistant | undefined
const [activeAssistantFiles, setActiveAssistantFiles] = useState<OpenAI.Files.FileObject[] | undefined>(undefined); >(undefined);
const [activeThread, setActiveThread] = useState<IThread | undefined>(
undefined,
);
const [activeAssistantFiles, setActiveAssistantFiles] = useState<
OpenAI.Files.FileObject[] | undefined
>(undefined);
const [isResponding, setIsResponding] = useState(false); const [isResponding, setIsResponding] = useState(false);
useEffect(() => { useEffect(() => {
fetchThreads(); fetchThreads();
if (assistants.length < 1) { if (assistants.length < 1) {
fetchAssistants(); fetchAssistants();
fetchActiveConfiguration(); fetchActiveConfiguration();
} }
@ -48,7 +57,6 @@ const PluginView = () => {
return; return;
} }
await openaiInstance.beta.assistants.list().then((res) => { await openaiInstance.beta.assistants.list().then((res) => {
// sort by name // sort by name
const sortedAssistants = res.data.sort((a, b) => { const sortedAssistants = res.data.sort((a, b) => {
if (a.name && b.name && a.name < b.name) { if (a.name && b.name && a.name < b.name) {
@ -65,7 +73,7 @@ const PluginView = () => {
if (plugin) { if (plugin) {
setThreads(plugin.settings.threads); setThreads(plugin.settings.threads);
} }
} };
const fetchActiveConfiguration = () => { const fetchActiveConfiguration = () => {
if (plugin) { if (plugin) {
@ -78,7 +86,7 @@ const PluginView = () => {
setActiveThread(savedActiveThread); setActiveThread(savedActiveThread);
} }
} }
} };
const updateActiveAssistant = async (assistant: OpenAI.Beta.Assistant) => { const updateActiveAssistant = async (assistant: OpenAI.Beta.Assistant) => {
if (plugin) { if (plugin) {
@ -88,7 +96,7 @@ const PluginView = () => {
plugin.saveSettings(); plugin.saveSettings();
setActiveAssistant(assistant); setActiveAssistant(assistant);
} }
} };
const updateActiveAssistantFiles = (files: OpenAI.Files.FileObject[]) => { const updateActiveAssistantFiles = (files: OpenAI.Files.FileObject[]) => {
if (plugin) { if (plugin) {
@ -96,7 +104,7 @@ const PluginView = () => {
plugin.saveSettings(); plugin.saveSettings();
setActiveAssistantFiles(files); setActiveAssistantFiles(files);
} }
} };
const updateThreads = (threads: IThread[]) => { const updateThreads = (threads: IThread[]) => {
if (plugin) { if (plugin) {
@ -104,30 +112,34 @@ const PluginView = () => {
plugin.saveSettings(); plugin.saveSettings();
setThreads(threads); setThreads(threads);
} }
} };
const fetchAssistantFiles = async (): Promise<
const fetchAssistantFiles = async (): Promise<OpenAI.Files.FileObject[] | []> => { OpenAI.Files.FileObject[] | []
> => {
if (!openaiInstance || !plugin || !activeAssistant) { if (!openaiInstance || !plugin || !activeAssistant) {
return []; return [];
} }
try { try {
const assistantFilesResponse = await openaiInstance.beta.assistants.files.list(activeAssistant.id); const assistantFilesResponse =
await openaiInstance.beta.assistants.files.list(
activeAssistant.id,
);
const innerFiles: OpenAI.Files.FileObject[] = []; const innerFiles: OpenAI.Files.FileObject[] = [];
await Promise.all( await Promise.all(
assistantFilesResponse.data.map(async (file) => { assistantFilesResponse.data.map(async (file) => {
try { try {
const fileInfoResponse = await openaiInstance.files.retrieve(file.id); const fileInfoResponse =
await openaiInstance.files.retrieve(file.id);
innerFiles.push(fileInfoResponse); innerFiles.push(fileInfoResponse);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}) }),
); );
return innerFiles; return innerFiles;
@ -139,24 +151,27 @@ const PluginView = () => {
const updateActiveThread = (thread: IThread) => { const updateActiveThread = (thread: IThread) => {
if (plugin) { if (plugin) {
plugin.settings.activeThread = thread; plugin.settings.activeThread = thread;
plugin.saveSettings(); plugin.saveSettings();
setActiveThread(thread); setActiveThread(thread);
} }
} };
const fetchMessages = async () => { const fetchMessages = async () => {
if (!openaiInstance || !activeThread) { if (!openaiInstance || !activeThread) {
return; return;
} }
try { try {
const messages = await openaiInstance.beta.threads.messages.list(activeThread.id, listQueryOptions); const messages = await openaiInstance.beta.threads.messages.list(
activeThread.id,
listQueryOptions,
);
setMessages(messages.data); setMessages(messages.data);
} catch (e) { } catch (e) {
createNotice('Thread expired or not found. Please select a new thread.'); createNotice(
'Thread expired or not found. Please select a new thread.',
);
//remove thread from list //remove thread from list
const newThreads = threads.filter((t) => t.id !== activeThread.id); const newThreads = threads.filter((t) => t.id !== activeThread.id);
setThreads(newThreads); setThreads(newThreads);
@ -175,26 +190,30 @@ const PluginView = () => {
fetchMessages(); fetchMessages();
}, [activeThread]); }, [activeThread]);
const onMessageSend = useCallback(async (message: string) => { const onMessageSend = useCallback(
async (message: string) => {
if (openaiInstance && activeThread && activeAssistant) { if (openaiInstance && activeThread && activeAssistant) {
const messageObject =
const messageObject = await openaiInstance.beta.threads.messages.create(activeThread.id, { await openaiInstance.beta.threads.messages.create(
role: "user", activeThread.id,
{
role: 'user',
content: message, content: message,
}); },
);
setMessages([...messages, messageObject]); setMessages([...messages, messageObject]);
const run = await openaiInstance.beta.threads.runs.create(
activeThread.id,
const run = await openaiInstance.beta.threads.runs.create(activeThread.id, { {
assistant_id: activeAssistant.id, assistant_id: activeAssistant.id,
}); },
);
let runStatus = await openaiInstance.beta.threads.runs.retrieve( let runStatus = await openaiInstance.beta.threads.runs.retrieve(
activeThread.id, activeThread.id,
run.id run.id,
); );
// Initialize a counter and max attempts for the polling logic, and how long to wait each try // Initialize a counter and max attempts for the polling logic, and how long to wait each try
@ -203,14 +222,24 @@ const PluginView = () => {
const timoutWaitTimeMs = 2000; const timoutWaitTimeMs = 2000;
setIsResponding(true); setIsResponding(true);
while (runStatus.status !== "completed" && attempts < maxAttempts) { while (
await new Promise((resolve) => setTimeout(resolve, timoutWaitTimeMs)); runStatus.status !== 'completed' &&
runStatus = await openaiInstance.beta.threads.runs.retrieve(activeThread.id, run.id); attempts < maxAttempts
) {
await new Promise((resolve) =>
setTimeout(resolve, timoutWaitTimeMs),
);
runStatus = await openaiInstance.beta.threads.runs.retrieve(
activeThread.id,
run.id,
);
attempts++; attempts++;
} }
// Get latest messages // Get latest messages
await openaiInstance.beta.threads.messages.list(activeThread.id, listQueryOptions).then((res) => { await openaiInstance.beta.threads.messages
.list(activeThread.id, listQueryOptions)
.then((res) => {
setMessages(res.data); setMessages(res.data);
const files: ThreadAnnotationFile[] = []; const files: ThreadAnnotationFile[] = [];
//for each message in messages, if it has content.type = text, then access content.annotations.file_citation.file_id and get the file name from the active files list //for each message in messages, if it has content.type = text, then access content.annotations.file_citation.file_id and get the file name from the active files list
@ -218,20 +247,30 @@ const PluginView = () => {
if (message.content) { if (message.content) {
message.content.forEach((content) => { message.content.forEach((content) => {
if (content.type === 'text') { if (content.type === 'text') {
content.text.annotations.forEach((annotation) => { content.text.annotations.forEach(
(annotation) => {
// @ts-ignore // @ts-ignore
if (annotation.file_citation) { if (annotation.file_citation) {
const fileId: string =
// @ts-ignore // @ts-ignore
const fileId: string = annotation.file_citation.file_id; annotation.file_citation
const file = activeAssistantFiles?.find((file) => file.id === fileId); .file_id;
const file =
activeAssistantFiles?.find(
(file) =>
file.id ===
fileId,
);
if (file) { if (file) {
files.push({ files.push({
fileId: fileId, fileId: fileId,
fileName: file.filename, fileName:
file.filename,
}); });
} }
} }
}); },
);
} }
}); });
} }
@ -242,9 +281,11 @@ const PluginView = () => {
metadata: { metadata: {
...activeThread.metadata, ...activeThread.metadata,
annotationFiles: files, annotationFiles: files,
} },
}; };
const newThreads = threads.map((t) => t.id === activeThread.id ? newThread : t); const newThreads = threads.map((t) =>
t.id === activeThread.id ? newThread : t,
);
updateThreads(newThreads); updateThreads(newThreads);
updateActiveThread(newThread); updateActiveThread(newThread);
} }
@ -252,7 +293,9 @@ const PluginView = () => {
setIsResponding(false); setIsResponding(false);
}); });
} }
}, [openaiInstance, activeThread, activeAssistant, messages]); },
[openaiInstance, activeThread, activeAssistant, messages],
);
return ( return (
<div className="agent-view-container"> <div className="agent-view-container">
@ -264,7 +307,8 @@ const PluginView = () => {
threads={threads} threads={threads}
updateThreads={updateThreads} updateThreads={updateThreads}
activeThread={activeThread} activeThread={activeThread}
updateActiveThread={updateActiveThread}/> updateActiveThread={updateActiveThread}
/>
<Chatbox <Chatbox
messages={messages} messages={messages}
isResponding={isResponding} isResponding={isResponding}

View File

@ -1,21 +1,21 @@
import React, { useEffect, useMemo } from "react"; import React, { useEffect, useMemo } from 'react';
import OpenAI from "openai"; import OpenAI from 'openai';
import { useApp, useOpenAI, usePlugin } from "../AppView"; import { useApp, useOpenAI, usePlugin } from '../AppView';
import { IThread } from "../types"; import { IThread } from '../types';
import DropdownSelect from "./DropdownSelect"; import DropdownSelect from './DropdownSelect';
import { MarkdownView } from "obsidian"; import { MarkdownView } from 'obsidian';
import { Bot, MessageSquare, Plus, Pencil, Trash2 } from "lucide-react"; import { Bot, MessageSquare, Plus, Pencil, Trash2 } from 'lucide-react';
import { import {
AssistantEditModal, AssistantEditModal,
IAssistantEditModalValues, IAssistantEditModalValues,
} from "./modals/AssistantEditModal"; } from './modals/AssistantEditModal';
import {} from "./modals/AssistantEditModal"; import {} from './modals/AssistantEditModal';
import { import {
ThreadEditModal, ThreadEditModal,
IThreadEditModalValues, IThreadEditModalValues,
} from "./modals/ThreadEditModal"; } from './modals/ThreadEditModal';
import { createNotice } from "@/utils/Logs"; import { createNotice } from '@/utils/Logs';
import { defaultAssistantInstructions } from "@/utils/templates"; import { defaultAssistantInstructions } from '@/utils/templates';
interface AssistantManagerProps { interface AssistantManagerProps {
assistants: OpenAI.Beta.Assistant[]; assistants: OpenAI.Beta.Assistant[];
@ -48,8 +48,8 @@ const AssistantManager = ({
} }
plugin.addCommand({ plugin.addCommand({
id: "create-assistant-from-active-note", id: 'create-assistant-from-active-note',
name: "Create Assistant from Active Note", name: 'Create Assistant from Active Note',
checkCallback: (checking: boolean) => { checkCallback: (checking: boolean) => {
// Conditions to check // Conditions to check
const markdownView = const markdownView =
@ -62,16 +62,17 @@ const AssistantManager = ({
// (link) => addFileType(truncateLink(link.link)) // (link) => addFileType(truncateLink(link.link))
// ) || []; // ) || [];
const links = Object.keys( const links = Object.keys(
app.metadataCache.resolvedLinks[openFile.path] app.metadataCache.resolvedLinks[openFile.path],
); );
//@ts-ignore
const backlinks = Object.keys( const backlinks = Object.keys(
app.metadataCache.getBacklinksForFile(openFile).data //@ts-ignore
app.metadataCache.getBacklinksForFile(openFile)
.data,
).map((file) => file); ).map((file) => file);
console.log( console.log(
"file metadata cache", 'file metadata cache',
app.metadataCache.getCache(openFile.path)?.links app.metadataCache.getCache(openFile.path)?.links,
); );
const currentFile = openFile.path; const currentFile = openFile.path;
const filesToUpload = new Set([ const filesToUpload = new Set([
@ -84,7 +85,7 @@ const AssistantManager = ({
name: `${openFile.path} Assistant`, name: `${openFile.path} Assistant`,
instructions: defaultAssistantInstructions, instructions: defaultAssistantInstructions,
files: Array.from(filesToUpload) files: Array.from(filesToUpload)
.filter((file) => file.endsWith(".md")) .filter((file) => file.endsWith('.md'))
.map((file) => ({ .map((file) => ({
filename: file, filename: file,
})), })),
@ -104,7 +105,7 @@ const AssistantManager = ({
return; return;
} }
const newThreadName = "New Thread"; const newThreadName = 'New Thread';
await openaiInstance.beta.threads await openaiInstance.beta.threads
.create({ .create({
@ -150,7 +151,7 @@ const AssistantManager = ({
return newThread; return newThread;
} }
return thread; return thread;
}) }),
); );
updateActiveThread(newThread); updateActiveThread(newThread);
}); });
@ -164,14 +165,14 @@ const AssistantManager = ({
// Get the previous values of the thread // Get the previous values of the thread
const previousValues = { const previousValues = {
metadata: { metadata: {
name: activeThread.metadata.name || "", name: activeThread.metadata.name || '',
}, },
}; };
new ThreadEditModal({ new ThreadEditModal({
app, app,
title: "Edit Thread", title: 'Edit Thread',
submitButtonText: "Edit", submitButtonText: 'Edit',
previousValues, previousValues,
onSubmit: editThread, onSubmit: editThread,
}).open(); }).open();
@ -186,7 +187,7 @@ const AssistantManager = ({
.del(activeThread.id) .del(activeThread.id)
.then((res) => { .then((res) => {
const newThreadsList = threads.filter( const newThreadsList = threads.filter(
(thread) => thread.id !== activeThread.id (thread) => thread.id !== activeThread.id,
); );
updateThreads(newThreadsList); updateThreads(newThreadsList);
updateActiveThread(newThreadsList?.[0]); updateActiveThread(newThreadsList?.[0]);
@ -208,25 +209,23 @@ const AssistantManager = ({
values.files.map(async (file) => { values.files.map(async (file) => {
if (file.filename) { if (file.filename) {
const uploadedFile = await uploadFileToOpenAI( const uploadedFile = await uploadFileToOpenAI(
file?.filename file?.filename,
); );
if (uploadedFile) { if (uploadedFile) {
uploadedFiles.push(uploadedFile.id); uploadedFiles.push(uploadedFile.id);
} }
} }
}) }),
); );
await openaiInstance.beta.assistants await openaiInstance.beta.assistants
.create({ .create({
name: values.name, name: values.name,
description: values.description, description: values.description,
instructions: values.instructions, instructions: values.instructions,
tools: [{ type: "code_interpreter" }, { type: "retrieval" }], tools: [{ type: 'code_interpreter' }, { type: 'retrieval' }],
model: "gpt-4-1106-preview", model: 'gpt-4-1106-preview',
file_ids: uploadedFiles, file_ids: uploadedFiles,
}) })
.then((res) => { .then((res) => {
@ -237,7 +236,7 @@ const AssistantManager = ({
}; };
const handleCreateAssistant = async ( const handleCreateAssistant = async (
assistant?: IAssistantEditModalValues assistant?: IAssistantEditModalValues,
) => { ) => {
if (!openaiInstance || !app) { if (!openaiInstance || !app) {
return; return;
@ -253,8 +252,8 @@ const AssistantManager = ({
// setActiveAssistant(assistant); // setActiveAssistant(assistant);
new AssistantEditModal({ new AssistantEditModal({
app, app,
title: "Create New Assistant", title: 'Create New Assistant',
submitButtonText: "Create", submitButtonText: 'Create',
previousValues: assistant, previousValues: assistant,
onSubmit: createAssistant, onSubmit: createAssistant,
}).open(); }).open();
@ -270,7 +269,6 @@ const AssistantManager = ({
const assistantFiles: string[] = []; const assistantFiles: string[] = [];
values.files.forEach((file) => { values.files.forEach((file) => {
if (file.id) { if (file.id) {
assistantFiles.push(file.id); assistantFiles.push(file.id);
} else if (file.filename) { } else if (file.filename) {
@ -285,7 +283,7 @@ const AssistantManager = ({
if (uploadedFile) { if (uploadedFile) {
assistantFiles.push(uploadedFile.id); assistantFiles.push(uploadedFile.id);
} }
}) }),
); );
await openaiInstance.beta.assistants await openaiInstance.beta.assistants
@ -303,7 +301,7 @@ const AssistantManager = ({
return res; return res;
} }
return assistant; return assistant;
}) }),
); );
}); });
}; };
@ -316,16 +314,16 @@ const AssistantManager = ({
const files = await getAssistantFiles(); const files = await getAssistantFiles();
const previousValues = { const previousValues = {
name: activeAssistant.name || "", name: activeAssistant.name || '',
description: activeAssistant.description || "", description: activeAssistant.description || '',
instructions: activeAssistant.instructions || "", instructions: activeAssistant.instructions || '',
files: files || [], files: files || [],
}; };
new AssistantEditModal({ new AssistantEditModal({
app, app,
title: "Edit Assistant", title: 'Edit Assistant',
submitButtonText: "Edit", submitButtonText: 'Edit',
previousValues, previousValues,
onSubmit: editAssistant, onSubmit: editAssistant,
}).open(); }).open();
@ -340,7 +338,7 @@ const AssistantManager = ({
.del(activeAssistant.id) .del(activeAssistant.id)
.then((res) => { .then((res) => {
const newAssistantsList = assistants.filter( const newAssistantsList = assistants.filter(
(assistant) => assistant.id !== activeAssistant.id (assistant) => assistant.id !== activeAssistant.id,
); );
updateAssistants(newAssistantsList); updateAssistants(newAssistantsList);
@ -349,18 +347,18 @@ const AssistantManager = ({
}; };
const uploadFileToOpenAI = async ( const uploadFileToOpenAI = async (
fileName: string fileName: string,
): Promise<OpenAI.Files.FileObject | undefined> => { ): Promise<OpenAI.Files.FileObject | undefined> => {
if (!openaiInstance || !plugin || !app) { if (!openaiInstance || !plugin || !app) {
return undefined; return undefined;
} }
const file = await app.vault.adapter.read(fileName); const file = await app.vault.adapter.read(fileName);
const blob = new File([file], fileName, { type: "text/markdown" }); const blob = new File([file], fileName, { type: 'text/markdown' });
const returnedFile = await openaiInstance.files const returnedFile = await openaiInstance.files
.create({ .create({
purpose: "assistants", purpose: 'assistants',
file: blob, file: blob,
}) })
.then((res) => { .then((res) => {
@ -404,7 +402,7 @@ const AssistantManager = ({
try { try {
const assistantFilesResponse = const assistantFilesResponse =
await openaiInstance.beta.assistants.files.list( await openaiInstance.beta.assistants.files.list(
activeAssistant?.id activeAssistant?.id,
); );
const innerFiles: OpenAI.Files.FileObject[] = []; const innerFiles: OpenAI.Files.FileObject[] = [];
@ -419,7 +417,7 @@ const AssistantManager = ({
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}) }),
); );
return innerFiles; return innerFiles;
@ -433,7 +431,7 @@ const AssistantManager = ({
const assistantOptions = useMemo(() => { const assistantOptions = useMemo(() => {
return assistants.map((assistant) => { return assistants.map((assistant) => {
return { return {
label: assistant.name || "", label: assistant.name || '',
value: assistant.id, value: assistant.id,
}; };
}); });
@ -454,7 +452,7 @@ const AssistantManager = ({
} }
const assistant = assistants.find( const assistant = assistants.find(
(assistant) => assistant.id === assistantId (assistant) => assistant.id === assistantId,
); );
updateActiveAssistant(assistant ?? assistants?.[0]); updateActiveAssistant(assistant ?? assistants?.[0]);
@ -465,13 +463,11 @@ const AssistantManager = ({
return; return;
} }
const newActiveThread = threads.find( const newActiveThread = threads.find(
(thread) => thread.id === threadId (thread) => thread.id === threadId,
); );
updateActiveThread(newActiveThread ?? activeThread); updateActiveThread(newActiveThread ?? activeThread);
}; };
return ( return (
<div className="chat-top-section-container"> <div className="chat-top-section-container">
<div className="dropdowns-container"> <div className="dropdowns-container">
@ -480,7 +476,7 @@ const AssistantManager = ({
<DropdownSelect <DropdownSelect
items={assistantOptions} items={assistantOptions}
onChange={onUpdateActiveAssistant} onChange={onUpdateActiveAssistant}
activeItem={activeAssistant?.id || ""} activeItem={activeAssistant?.id || ''}
/> />
<div className="dropdown-buttons-container"> <div className="dropdown-buttons-container">
<button className="create" onClick={deleteAssistant}> <button className="create" onClick={deleteAssistant}>
@ -505,7 +501,7 @@ const AssistantManager = ({
<DropdownSelect <DropdownSelect
items={threadOptions} items={threadOptions}
onChange={onUpdateActiveThread} onChange={onUpdateActiveThread}
activeItem={activeThread?.id || ""} activeItem={activeThread?.id || ''}
/> />
<div className="dropdown-buttons-container"> <div className="dropdown-buttons-container">
<button className="create" onClick={deleteThread}> <button className="create" onClick={deleteThread}>

View File

@ -1,18 +1,18 @@
import React, { CSSProperties, useEffect, useRef, useState } from "react"; import React, { CSSProperties, useEffect, useRef, useState } from 'react';
import OpenAI from "openai"; import OpenAI from 'openai';
import { ChevronDown, ClipboardCopy } from "lucide-react"; import { ChevronDown, ClipboardCopy } from 'lucide-react';
import BeatLoader from "react-spinners/BeatLoader"; import BeatLoader from 'react-spinners/BeatLoader';
import { Tooltip } from "react-tooltip"; import { Tooltip } from 'react-tooltip';
import Markdown from "react-markdown"; import Markdown from 'react-markdown';
import { useApp } from "../AppView"; import { useApp } from '../AppView';
import { createNotice } from "@/utils/Logs"; import { createNotice } from '@/utils/Logs';
import { TFile } from "obsidian"; import { TFile } from 'obsidian';
import { ThreadAnnotationFile } from "../types"; import { ThreadAnnotationFile } from '../types';
const override: CSSProperties = { const override: CSSProperties = {
display: "block", display: 'block',
margin: "0 auto", margin: '0 auto',
borderColor: "white", borderColor: 'white',
}; };
interface ChatboxProps { interface ChatboxProps {
@ -40,16 +40,16 @@ const Chatbox = ({ annotationFiles, isResponding, messages }: ChatboxProps) => {
if (messagesContainerRef.current) { if (messagesContainerRef.current) {
scrollToBottom(); scrollToBottom();
messagesContainerRef.current.addEventListener( messagesContainerRef.current.addEventListener(
"scroll", 'scroll',
handleScroll handleScroll,
); );
} }
return () => { return () => {
if (messagesContainerRef.current) { if (messagesContainerRef.current) {
messagesContainerRef.current.removeEventListener( messagesContainerRef.current.removeEventListener(
"scroll", 'scroll',
handleScroll handleScroll,
); );
} }
}; };
@ -67,11 +67,11 @@ const Chatbox = ({ annotationFiles, isResponding, messages }: ChatboxProps) => {
messages.map((message, index) => ( messages.map((message, index) => (
<div key={index} className={`chat-message ${message.role}`}> <div key={index} className={`chat-message ${message.role}`}>
{message.content.map((content, index) => { {message.content.map((content, index) => {
if (content.type === "text") { if (content.type === 'text') {
const getMessageText = () => { const getMessageText = () => {
const annotationsTexts = const annotationsTexts =
content.text.annotations.map( content.text.annotations.map(
(annotation: any) => annotation.text (annotation: any) => annotation.text,
); );
let text = content.text.value; let text = content.text.value;
@ -80,13 +80,13 @@ const Chatbox = ({ annotationFiles, isResponding, messages }: ChatboxProps) => {
const annotationIndex = index; const annotationIndex = index;
const regex = new RegExp( const regex = new RegExp(
annotationText, annotationText,
"g" 'g',
); );
text = text.replace( text = text.replace(
regex, regex,
`[^${annotationIndex}]` `[^${annotationIndex}]`,
); );
} },
); );
return text; return text;
@ -94,31 +94,31 @@ const Chatbox = ({ annotationFiles, isResponding, messages }: ChatboxProps) => {
const renderAnnotation = ( const renderAnnotation = (
annotation: any, annotation: any,
index: number index: number,
) => { ) => {
const { file_citation } = annotation; const { file_citation } = annotation;
const fileId = file_citation?.file_id; const fileId = file_citation?.file_id;
const fileName = annotationFiles?.find( const fileName = annotationFiles?.find(
(file) => file.fileId === fileId (file) => file.fileId === fileId,
)?.fileName; )?.fileName;
let quote = file_citation?.quote; let quote = file_citation?.quote;
// Check if quote has list markdown syntax // Check if quote has list markdown syntax
if (quote && quote.includes("- ")) { if (quote && quote.includes('- ')) {
quote = quote.replace(/- /g, "\n- "); quote = quote.replace(/- /g, '\n- ');
} }
const handleAnnotationClick = () => { const handleAnnotationClick = () => {
// open new tab and then navigate to fil // open new tab and then navigate to fil
console.log( console.log(
"handleAnnotationClick", 'handleAnnotationClick',
app, app,
fileName fileName,
); );
if (app && fileName) { if (app && fileName) {
const file = const file =
app.vault.getAbstractFileByPath( app.vault.getAbstractFileByPath(
fileName fileName,
); );
if (file && file instanceof TFile) { if (file && file instanceof TFile) {
app.workspace.getLeaf().openFile(file); app.workspace.getLeaf().openFile(file);
@ -150,7 +150,7 @@ const Chatbox = ({ annotationFiles, isResponding, messages }: ChatboxProps) => {
const text = getMessageText(); const text = getMessageText();
const copyText = () => { const copyText = () => {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
createNotice("Copied to clipboard!", 2000); createNotice('Copied to clipboard!', 2000);
}; };
return ( return (
<div key={index} className="message-content"> <div key={index} className="message-content">
@ -160,11 +160,11 @@ const Chatbox = ({ annotationFiles, isResponding, messages }: ChatboxProps) => {
</Markdown> </Markdown>
} }
<div className="message-footer"> <div className="message-footer">
{message.role === "assistant" && ( {message.role === 'assistant' && (
<div className="copy-icon-container"> <div className="copy-icon-container">
<ClipboardCopy <ClipboardCopy
className="copy-icon" className="copy-icon"
color={"#ffffff"} color={'#ffffff'}
size={16} size={16}
onClick={copyText} onClick={copyText}
/> />
@ -174,8 +174,8 @@ const Chatbox = ({ annotationFiles, isResponding, messages }: ChatboxProps) => {
(annotation: any, index: number) => (annotation: any, index: number) =>
renderAnnotation( renderAnnotation(
annotation, annotation,
index index,
) ),
)} )}
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from 'react';
import { DropdownComponent } from "obsidian"; import { DropdownComponent } from 'obsidian';
interface ISelectOption { interface ISelectOption {
label: string; label: string;
@ -29,7 +29,7 @@ const DropdownSelect: React.FC<DropdownSelectProps> = ({
ref={selectElementRef} ref={selectElementRef}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
value={activeItem} value={activeItem}
className={"dropdown-select"} className={'dropdown-select'}
> >
{items.map((item) => ( {items.map((item) => (
<option className="" key={item.value} value={item.value}> <option className="" key={item.value} value={item.value}>

View File

@ -1,16 +1,16 @@
import { SendHorizontal } from "lucide-react"; import { SendHorizontal } from 'lucide-react';
import React, { useState } from "react"; import React, { useState } from 'react';
interface MessageInputProps { interface MessageInputProps {
onMessageSend: (message: string) => void; onMessageSend: (message: string) => void;
} }
const MessageInput: React.FC<MessageInputProps> = ({ onMessageSend }) => { const MessageInput: React.FC<MessageInputProps> = ({ onMessageSend }) => {
const [newMessage, setNewMessage] = useState(""); const [newMessage, setNewMessage] = useState('');
const handleSend = () => { const handleSend = () => {
onMessageSend(newMessage); onMessageSend(newMessage);
setNewMessage(""); setNewMessage('');
}; };
return ( return (

View File

@ -2,14 +2,14 @@ import { App, ButtonComponent, Modal, Notice, Setting } from 'obsidian';
import OpenAI from 'openai'; import OpenAI from 'openai';
import { FileSuggest } from '../suggesters/FileSuggester'; import { FileSuggest } from '../suggesters/FileSuggester';
import * as yup from 'yup'; import * as yup from 'yup';
import { defaultAssistantInstructions } from '../../../utils/templates' import { defaultAssistantInstructions } from '../../../utils/templates';
interface AssistantEditModalProps { interface AssistantEditModalProps {
app: App, app: App;
title: string, title: string;
submitButtonText?: string, submitButtonText?: string;
previousValues?: IAssistantEditModalValues, previousValues?: IAssistantEditModalValues;
onSubmit: (values: IAssistantEditModalValues) => void, onSubmit: (values: IAssistantEditModalValues) => void;
} }
export interface IAssistantEditModalValues { export interface IAssistantEditModalValues {
@ -60,7 +60,7 @@ export class AssistantEditModal extends Modal {
new Setting(contentEl) new Setting(contentEl)
.setName('Name (required)') .setName('Name (required)')
.setDesc('The name of the assistant') .setDesc('The name of the assistant')
.addText(text => { .addText((text) => {
text.setPlaceholder('Enter name...') text.setPlaceholder('Enter name...')
.onChange((value) => { .onChange((value) => {
this.values.name = value; this.values.name = value;
@ -74,7 +74,7 @@ export class AssistantEditModal extends Modal {
.setName('Description') .setName('Description')
.setDesc('The description of the assistant') .setDesc('The description of the assistant')
.setClass('form-setting-textarea') .setClass('form-setting-textarea')
.addTextArea(text => { .addTextArea((text) => {
text.setPlaceholder('Enter description...') text.setPlaceholder('Enter description...')
.onChange((value) => { .onChange((value) => {
this.values.description = value; this.values.description = value;
@ -88,7 +88,7 @@ export class AssistantEditModal extends Modal {
.setName('Instructions (required)') .setName('Instructions (required)')
.setDesc('The instructions you want the assistant to follow') .setDesc('The instructions you want the assistant to follow')
.setClass('form-setting-textarea') .setClass('form-setting-textarea')
.addTextArea(text => { .addTextArea((text) => {
text.setPlaceholder('Enter instructions...') text.setPlaceholder('Enter instructions...')
.onChange((value) => { .onChange((value) => {
this.values.instructions = value; this.values.instructions = value;
@ -101,10 +101,12 @@ export class AssistantEditModal extends Modal {
// Function to add a file to the list // Function to add a file to the list
const addFileToList = (fileName: string) => { const addFileToList = (fileName: string) => {
// if filename already is in values, replace it. this prevents duplicates and allows for re-uploading // if filename already is in values, replace it. this prevents duplicates and allows for re-uploading
const fileIndex: number = this.values.files && this.values.files.findIndex(file => file.filename === fileName); const fileIndex: number =
this.values.files &&
this.values.files.findIndex(
(file) => file.filename === fileName,
);
if (fileIndex !== -1) { if (fileIndex !== -1) {
this.values.files[fileIndex] = { this.values.files[fileIndex] = {
filename: fileName, filename: fileName,
@ -120,21 +122,24 @@ export class AssistantEditModal extends Modal {
}; };
const updateFileCountText = () => { const updateFileCountText = () => {
this.fileCountText.setText(`Files Uploaded (${this.values.files.length}/20)`); this.fileCountText.setText(
} `Files Uploaded (${this.values.files.length}/20)`,
);
};
const createFileListElement = (fileName: string) => { const createFileListElement = (fileName: string) => {
const fileDiv = this.fileListDiv.createDiv({ cls: 'file-item' }); const fileDiv = this.fileListDiv.createDiv({ cls: 'file-item' });
fileDiv.createEl('span', { text: fileName }); fileDiv.createEl('span', { text: fileName });
new ButtonComponent(fileDiv) new ButtonComponent(fileDiv)
.setIcon('trash-2') .setIcon('trash-2')
.setClass('remove-button') .setClass('remove-button')
.onClick(() => { .onClick(() => {
fileDiv.remove(); fileDiv.remove();
// Remove the file from the values object // Remove the file from the values object
const file = this.values.files.find(file => file.filename === fileName); const file = this.values.files.find(
(file) => file.filename === fileName,
);
if (file) { if (file) {
this.values.files.remove(file); this.values.files.remove(file);
updateFileCountText(); updateFileCountText();
@ -144,9 +149,11 @@ export class AssistantEditModal extends Modal {
new Setting(contentEl) new Setting(contentEl)
.setName(`Files`) .setName(`Files`)
.setDesc('The files uploaded to the assistant (not real-time synced, so you will need to re-upload). Max 20.') .setDesc(
.addSearch(search => { 'The files uploaded to the assistant (not real-time synced, so you will need to re-upload). Max 20.',
search.setPlaceholder('Enter file IDs separated by commas...') )
.addSearch((search) => {
search.setPlaceholder('Enter file IDs separated by commas...');
// .onChange((value) => { // .onChange((value) => {
// this.values.file_ids.push(value); // this.values.file_ids.push(value);
@ -160,10 +167,8 @@ export class AssistantEditModal extends Modal {
updateFileCountText(); updateFileCountText();
this.fileListDiv = contentEl.createDiv({ cls: 'file-list' }); this.fileListDiv = contentEl.createDiv({ cls: 'file-list' });
// Add the files that were already selected // Add the files that were already selected
this.values.files.forEach(file => { this.values.files.forEach((file) => {
if (file.filename) { if (file.filename) {
createFileListElement(file.filename); createFileListElement(file.filename);
} }
@ -171,7 +176,6 @@ export class AssistantEditModal extends Modal {
} }
addSubmitButton(contentEl: HTMLElement) { addSubmitButton(contentEl: HTMLElement) {
const validationSchema = yup.object().shape({ const validationSchema = yup.object().shape({
name: yup.string().required('Name is required'), name: yup.string().required('Name is required'),
instructions: yup.string().required('Instructions are required'), instructions: yup.string().required('Instructions are required'),
@ -180,7 +184,9 @@ export class AssistantEditModal extends Modal {
const checkRequiredFields = async (): Promise<string[]> => { const checkRequiredFields = async (): Promise<string[]> => {
try { try {
await validationSchema.validate(this.values, { abortEarly: false }); await validationSchema.validate(this.values, {
abortEarly: false,
});
return []; return [];
} catch (error) { } catch (error) {
if (error instanceof yup.ValidationError) { if (error instanceof yup.ValidationError) {
@ -201,7 +207,7 @@ export class AssistantEditModal extends Modal {
this.onClose(); this.onClose();
this.close(); this.close();
} };
const modalFooterEl = contentEl.createDiv({ cls: 'modal-footer' }); const modalFooterEl = contentEl.createDiv({ cls: 'modal-footer' });
new ButtonComponent(modalFooterEl) new ButtonComponent(modalFooterEl)
.setButtonText(this.submitButtonText) .setButtonText(this.submitButtonText)
@ -211,6 +217,5 @@ export class AssistantEditModal extends Modal {
}); });
} }
onClose() { onClose() {}
}
} }

View File

@ -2,18 +2,18 @@ import { App, ButtonComponent, Modal, Notice, Setting } from 'obsidian';
import * as yup from 'yup'; import * as yup from 'yup';
interface ThreadEditModalProps { interface ThreadEditModalProps {
app: App, app: App;
title: string, title: string;
submitButtonText?: string, submitButtonText?: string;
previousValues?: IThreadEditModalValues, previousValues?: IThreadEditModalValues;
onSubmit: (values: IThreadEditModalValues) => void, onSubmit: (values: IThreadEditModalValues) => void;
} }
export interface IThreadEditModalValues { export interface IThreadEditModalValues {
metadata: { metadata: {
name: string; name: string;
[key: string]: unknown; [key: string]: unknown;
}, };
[key: string]: unknown; [key: string]: unknown;
} }
@ -46,7 +46,7 @@ export class ThreadEditModal extends Modal {
new Setting(contentEl) new Setting(contentEl)
.setName('Name') .setName('Name')
.setDesc('The name of the thread') .setDesc('The name of the thread')
.addText(text => { .addText((text) => {
text.setPlaceholder('Enter thread name...') text.setPlaceholder('Enter thread name...')
.onChange((value) => { .onChange((value) => {
this.values.metadata.name = value; this.values.metadata.name = value;
@ -54,7 +54,6 @@ export class ThreadEditModal extends Modal {
.setValue(this.values.metadata.name); .setValue(this.values.metadata.name);
}); });
const validationSchema = yup.object().shape({ const validationSchema = yup.object().shape({
metadata: yup.object().shape({ metadata: yup.object().shape({
name: yup.string().required('Name is required'), name: yup.string().required('Name is required'),
@ -63,7 +62,9 @@ export class ThreadEditModal extends Modal {
const checkRequiredFields = async (): Promise<string[]> => { const checkRequiredFields = async (): Promise<string[]> => {
try { try {
await validationSchema.validate(this.values, { abortEarly: false }); await validationSchema.validate(this.values, {
abortEarly: false,
});
return []; return [];
} catch (error) { } catch (error) {
if (error instanceof yup.ValidationError) { if (error instanceof yup.ValidationError) {
@ -93,6 +94,5 @@ export class ThreadEditModal extends Modal {
}); });
} }
onClose() { onClose() {}
}
} }

View File

@ -1,8 +1,8 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes // Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
import { App, TAbstractFile, TFile } from "obsidian"; import { App, TAbstractFile, TFile } from 'obsidian';
import { TextInputSuggest } from "./suggest"; import { TextInputSuggest } from './suggest';
import { get_tfiles_from_folder } from "@/utils/utils"; import { get_tfiles_from_folder } from '@/utils/utils';
export class FileSuggest extends TextInputSuggest<TFile> { export class FileSuggest extends TextInputSuggest<TFile> {
private onSelect: (file: string) => void; private onSelect: (file: string) => void;
@ -10,8 +10,7 @@ export class FileSuggest extends TextInputSuggest<TFile> {
constructor( constructor(
app: App, app: App,
public inputEl: HTMLInputElement, public inputEl: HTMLInputElement,
onSelect: (file: string) => void onSelect: (file: string) => void,
) { ) {
super(app, inputEl); super(app, inputEl);
this.onSelect = onSelect; this.onSelect = onSelect;
@ -30,7 +29,7 @@ export class FileSuggest extends TextInputSuggest<TFile> {
all_files.forEach((file: TAbstractFile) => { all_files.forEach((file: TAbstractFile) => {
if ( if (
file instanceof TFile && file instanceof TFile &&
file.extension === "md" && file.extension === 'md' &&
file.path.toLowerCase().contains(lower_input_str) file.path.toLowerCase().contains(lower_input_str)
) { ) {
files.push(file); files.push(file);

View File

@ -1,8 +1,8 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes // Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
import { ISuggestOwner, Scope } from "obsidian"; import { ISuggestOwner, Scope } from 'obsidian';
import { createPopper, Instance as PopperInstance } from "@popperjs/core"; import { createPopper, Instance as PopperInstance } from '@popperjs/core';
import { App } from "obsidian"; import { App } from 'obsidian';
const wrapAround = (value: number, size: number): number => { const wrapAround = (value: number, size: number): number => {
return ((value % size) + size) % size; return ((value % size) + size) % size;
@ -18,37 +18,37 @@ class Suggest<T> {
constructor( constructor(
owner: ISuggestOwner<T>, owner: ISuggestOwner<T>,
containerEl: HTMLElement, containerEl: HTMLElement,
scope: Scope scope: Scope,
) { ) {
this.owner = owner; this.owner = owner;
this.containerEl = containerEl; this.containerEl = containerEl;
containerEl.on( containerEl.on(
"click", 'click',
".suggestion-item", '.suggestion-item',
this.onSuggestionClick.bind(this) this.onSuggestionClick.bind(this),
); );
containerEl.on( containerEl.on(
"mousemove", 'mousemove',
".suggestion-item", '.suggestion-item',
this.onSuggestionMouseover.bind(this) this.onSuggestionMouseover.bind(this),
); );
scope.register([], "ArrowUp", (event) => { scope.register([], 'ArrowUp', (event) => {
if (!event.isComposing) { if (!event.isComposing) {
this.setSelectedItem(this.selectedItem - 1, true); this.setSelectedItem(this.selectedItem - 1, true);
return false; return false;
} }
}); });
scope.register([], "ArrowDown", (event) => { scope.register([], 'ArrowDown', (event) => {
if (!event.isComposing) { if (!event.isComposing) {
this.setSelectedItem(this.selectedItem + 1, true); this.setSelectedItem(this.selectedItem + 1, true);
return false; return false;
} }
}); });
scope.register([], "Enter", (event) => { scope.register([], 'Enter', (event) => {
if (!event.isComposing) { if (!event.isComposing) {
this.useSelectedItem(event); this.useSelectedItem(event);
return false; return false;
@ -74,7 +74,7 @@ class Suggest<T> {
const suggestionEls: HTMLDivElement[] = []; const suggestionEls: HTMLDivElement[] = [];
values.forEach((value) => { values.forEach((value) => {
const suggestionEl = this.containerEl.createDiv("suggestion-item"); const suggestionEl = this.containerEl.createDiv('suggestion-item');
this.owner.renderSuggestion(value, suggestionEl); this.owner.renderSuggestion(value, suggestionEl);
suggestionEls.push(suggestionEl); suggestionEls.push(suggestionEl);
}); });
@ -94,13 +94,13 @@ class Suggest<T> {
setSelectedItem(selectedIndex: number, scrollIntoView: boolean) { setSelectedItem(selectedIndex: number, scrollIntoView: boolean) {
const normalizedIndex = wrapAround( const normalizedIndex = wrapAround(
selectedIndex, selectedIndex,
this.suggestions.length this.suggestions.length,
); );
const prevSelectedSuggestion = this.suggestions[this.selectedItem]; const prevSelectedSuggestion = this.suggestions[this.selectedItem];
const selectedSuggestion = this.suggestions[normalizedIndex]; const selectedSuggestion = this.suggestions[normalizedIndex];
prevSelectedSuggestion?.removeClass("is-selected"); prevSelectedSuggestion?.removeClass('is-selected');
selectedSuggestion?.addClass("is-selected"); selectedSuggestion?.addClass('is-selected');
this.selectedItem = normalizedIndex; this.selectedItem = normalizedIndex;
@ -124,21 +124,21 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
this.inputEl = inputEl; this.inputEl = inputEl;
this.scope = new Scope(); this.scope = new Scope();
this.suggestEl = createDiv("suggestion-container"); this.suggestEl = createDiv('suggestion-container');
const suggestion = this.suggestEl.createDiv("suggestion"); const suggestion = this.suggestEl.createDiv('suggestion');
this.suggest = new Suggest(this, suggestion, this.scope); this.suggest = new Suggest(this, suggestion, this.scope);
this.scope.register([], "Escape", this.close.bind(this)); this.scope.register([], 'Escape', this.close.bind(this));
this.inputEl.addEventListener("input", this.onInputChanged.bind(this)); this.inputEl.addEventListener('input', this.onInputChanged.bind(this));
this.inputEl.addEventListener("focus", this.onInputChanged.bind(this)); this.inputEl.addEventListener('focus', this.onInputChanged.bind(this));
this.inputEl.addEventListener("blur", this.close.bind(this)); this.inputEl.addEventListener('blur', this.close.bind(this));
this.suggestEl.on( this.suggestEl.on(
"mousedown", 'mousedown',
".suggestion-container", '.suggestion-container',
(event: MouseEvent) => { (event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
} },
); );
} }
@ -166,10 +166,10 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
container.appendChild(this.suggestEl); container.appendChild(this.suggestEl);
this.popper = createPopper(inputEl, this.suggestEl, { this.popper = createPopper(inputEl, this.suggestEl, {
placement: "bottom-start", placement: 'bottom-start',
modifiers: [ modifiers: [
{ {
name: "sameWidth", name: 'sameWidth',
enabled: true, enabled: true,
fn: ({ state, instance }) => { fn: ({ state, instance }) => {
// Note: positioning needs to be calculated twice - // Note: positioning needs to be calculated twice -
@ -183,8 +183,8 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
state.styles.popper.width = targetWidth; state.styles.popper.width = targetWidth;
instance.update(); instance.update();
}, },
phase: "beforeWrite", phase: 'beforeWrite',
requires: ["computeStyles"], requires: ['computeStyles'],
}, },
], ],
}); });

View File

@ -1,9 +1,9 @@
import OpenAI from "openai"; import OpenAI from 'openai';
export interface IThread extends OpenAI.Beta.Thread { export interface IThread extends OpenAI.Beta.Thread {
metadata: { metadata: {
name: string; name: string;
annotationFiles?: ThreadAnnotationFile[]; annotationFiles?: ThreadAnnotationFile[];
} };
} }
export interface ThreadAnnotationFile { export interface ThreadAnnotationFile {

View File

@ -1,4 +1,4 @@
import { Notice } from "obsidian"; import { Notice } from 'obsidian';
export const createNotice = (message: string, timeout = 5000): void => { export const createNotice = (message: string, timeout = 5000): void => {
new Notice(`Obsidian Intelligence: ${message}`, timeout); new Notice(`Obsidian Intelligence: ${message}`, timeout);

View File

@ -1,7 +1,10 @@
import { log_error } from "./log"; import { log_error } from './log';
export class TemplaterError extends Error { export class TemplaterError extends Error {
constructor(msg: string, public console_msg?: string) { constructor(
msg: string,
public console_msg?: string,
) {
super(msg); super(msg);
this.name = this.constructor.name; this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
@ -10,7 +13,7 @@ export class TemplaterError extends Error {
export async function errorWrapper<T>( export async function errorWrapper<T>(
fn: () => Promise<T>, fn: () => Promise<T>,
msg: string msg: string,
): Promise<T> { ): Promise<T> {
try { try {
return await fn(); return await fn();

View File

@ -1,20 +1,20 @@
import { Notice } from "obsidian"; import { Notice } from 'obsidian';
import { TemplaterError } from "./error"; import { TemplaterError } from './error';
export function log_update(msg: string): void { export function log_update(msg: string): void {
const notice = new Notice("", 15000); const notice = new Notice('', 15000);
// TODO: Find better way for this // TODO: Find better way for this
// @ts-ignore // @ts-ignore
notice.noticeEl.innerHTML = `<b>obsidian-agents update</b>:<br/>${msg}`; notice.noticeEl.innerHTML = `<b>obsidian-agents update</b>:<br/>${msg}`;
} }
export function log_error(e: Error | TemplaterError): void { export function log_error(e: Error | TemplaterError): void {
const notice = new Notice("", 8000); const notice = new Notice('', 8000);
if (e instanceof TemplaterError && e.console_msg) { if (e instanceof TemplaterError && e.console_msg) {
// TODO: Find a better way for this // TODO: Find a better way for this
// @ts-ignore // @ts-ignore
notice.noticeEl.innerHTML = `<b>obsidian-agents Error</b>:<br/>${e.message}<br/>Check console for more information`; notice.noticeEl.innerHTML = `<b>obsidian-agents Error</b>:<br/>${e.message}<br/>Check console for more information`;
console.error(`obsidian-agents Error:`, e.message, "\n", e.console_msg); console.error(`obsidian-agents Error:`, e.message, '\n', e.console_msg);
} else { } else {
// @ts-ignore // @ts-ignore
notice.noticeEl.innerHTML = `<b>obsidian-agents Error</b>:<br/>${e.message}`; notice.noticeEl.innerHTML = `<b>obsidian-agents Error</b>:<br/>${e.message}`;

View File

@ -1,4 +1,4 @@
import { TemplaterError } from "./error"; import { TemplaterError } from './error';
import { import {
App, App,
normalizePath, normalizePath,
@ -6,14 +6,14 @@ import {
TFile, TFile,
TFolder, TFolder,
Vault, Vault,
} from "obsidian"; } from 'obsidian';
export function delay(ms: number): Promise<void> { export function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
export function escape_RegExp(str: string): string { export function escape_RegExp(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
} }
export function generate_command_regex(): RegExp { export function generate_command_regex(): RegExp {
@ -52,13 +52,19 @@ export function resolve_tfile(file_str: string): TFile {
return file; return file;
} }
export function get_tfiles_from_folder(folder_str: string, extension?: string): Array<TFile> { export function get_tfiles_from_folder(
folder_str: string,
extension?: string,
): Array<TFile> {
const folder = resolve_tfolder(folder_str); const folder = resolve_tfolder(folder_str);
const files: Array<TFile> = []; const files: Array<TFile> = [];
Vault.recurseChildren(folder, (file: TAbstractFile) => { Vault.recurseChildren(folder, (file: TAbstractFile) => {
if (
if (file instanceof TFile && (extension && file.extension === extension)) { file instanceof TFile &&
extension &&
file.extension === extension
) {
files.push(file); files.push(file);
} }
}); });
@ -73,7 +79,7 @@ export function get_tfiles_from_folder(folder_str: string, extension?: string):
export function arraymove<T>( export function arraymove<T>(
arr: T[], arr: T[],
fromIndex: number, fromIndex: number,
toIndex: number toIndex: number,
): void { ): void {
if (toIndex < 0 || toIndex === arr.length) { if (toIndex < 0 || toIndex === arr.length) {
return; return;

View File

@ -35,7 +35,6 @@
justify-content: center; justify-content: center;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.scroll-to-bottom-button { .scroll-to-bottom-button {
@ -88,7 +87,6 @@
padding-bottom: 8px; padding-bottom: 8px;
} }
.copy-icon-container { .copy-icon-container {
padding-top: 8px; padding-top: 8px;
} }
@ -125,7 +123,6 @@
width: 15%; width: 15%;
} }
.collapsible-container { .collapsible-container {
width: 100%; width: 100%;
max-height: 25%; max-height: 25%;
@ -160,7 +157,6 @@
flex-grow: 1; flex-grow: 1;
} }
.files-list { .files-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -181,7 +177,6 @@
gap: 4px; gap: 4px;
} }
/* AssistantManager.tsx */ /* AssistantManager.tsx */
.chat-top-section-container { .chat-top-section-container {

View File

@ -13,18 +13,10 @@
"isolatedModules": true, "isolatedModules": true,
"jsx": "react", "jsx": "react",
"strictNullChecks": true, "strictNullChecks": true,
"lib": [ "lib": ["DOM", "ES5", "ES6", "ES7"],
"DOM",
"ES5",
"ES6",
"ES7"
],
"paths": { "paths": {
"@/*": ["src/*"] "@/*": ["src/*"]
} }
}, },
"include": [ "include": ["**/*.ts", "src/ui/AppView.tsx"]
"**/*.ts",
"src/ui/AppView.tsx"
]
} }

View File

@ -1,14 +1,14 @@
import { readFileSync, writeFileSync } from "fs"; import { readFileSync, writeFileSync } from 'fs';
const targetVersion = process.env.npm_package_version; const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version // read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); let manifest = JSON.parse(readFileSync('manifest.json', 'utf8'));
const { minAppVersion } = manifest; const { minAppVersion } = manifest;
manifest.version = targetVersion; manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); writeFileSync('manifest.json', JSON.stringify(manifest, null, '\t'));
// update versions.json with target version and minAppVersion from manifest.json // update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8")); let versions = JSON.parse(readFileSync('versions.json', 'utf8'));
versions[targetVersion] = minAppVersion; versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));