prettier formatting
This commit is contained in:
parent
1a61117aa9
commit
96ac84eb5c
22
.eslintrc
22
.eslintrc
|
@ -2,22 +2,20 @@
|
|||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": { "node": true },
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"printWidth": 80,
|
||||
"useTabs": false,
|
||||
"singleQuote": true
|
||||
}
|
|
@ -1,48 +1,48 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import esbuild from 'esbuild';
|
||||
import process from 'process';
|
||||
import builtins from 'builtin-modules';
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
const banner = `/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
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({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtins],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ['main.ts'],
|
||||
bundle: true,
|
||||
external: [
|
||||
'obsidian',
|
||||
'electron',
|
||||
'@codemirror/autocomplete',
|
||||
'@codemirror/collab',
|
||||
'@codemirror/commands',
|
||||
'@codemirror/language',
|
||||
'@codemirror/lint',
|
||||
'@codemirror/search',
|
||||
'@codemirror/state',
|
||||
'@codemirror/view',
|
||||
'@lezer/common',
|
||||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
...builtins,
|
||||
],
|
||||
format: 'cjs',
|
||||
target: 'es2018',
|
||||
logLevel: 'info',
|
||||
sourcemap: prod ? false : 'inline',
|
||||
treeShaking: true,
|
||||
outfile: 'main.js',
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
} else {
|
||||
await context.watch();
|
||||
await context.watch();
|
||||
}
|
139
main.ts
139
main.ts
|
@ -4,103 +4,108 @@ import OpenAI from 'openai';
|
|||
import { IThread } from './src/ui/types';
|
||||
|
||||
interface ObsidianIntelligenceSettings {
|
||||
openaiKey: string;
|
||||
threads: IThread[];
|
||||
activeThread: IThread | undefined;
|
||||
activeAssistant: OpenAI.Beta.Assistant | undefined;
|
||||
activeAssistantFiles: OpenAI.Files.FileObject[] | undefined;
|
||||
openaiKey: string;
|
||||
threads: IThread[];
|
||||
activeThread: IThread | undefined;
|
||||
activeAssistant: OpenAI.Beta.Assistant | undefined;
|
||||
activeAssistantFiles: OpenAI.Files.FileObject[] | undefined;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: ObsidianIntelligenceSettings = {
|
||||
openaiKey: '',
|
||||
threads: [],
|
||||
activeThread: undefined,
|
||||
activeAssistant: undefined,
|
||||
activeAssistantFiles: undefined,
|
||||
}
|
||||
openaiKey: '',
|
||||
threads: [],
|
||||
activeThread: undefined,
|
||||
activeAssistant: undefined,
|
||||
activeAssistantFiles: undefined,
|
||||
};
|
||||
|
||||
export default class ObsidianIntelligence extends Plugin {
|
||||
settings: ObsidianIntelligenceSettings;
|
||||
view: AppView;
|
||||
settings: ObsidianIntelligenceSettings;
|
||||
view: AppView;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.registerView(
|
||||
VIEW_TYPE,
|
||||
(leaf) => (new AppView(leaf, this))
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.registerView(VIEW_TYPE, (leaf) => new AppView(leaf, this));
|
||||
|
||||
const ribbonIconEl = this.addRibbonIcon(
|
||||
'bot',
|
||||
'Open Obsidian Intelligence',
|
||||
(evt: MouseEvent) => {
|
||||
this.activateView();
|
||||
},
|
||||
);
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
|
||||
const ribbonIconEl = this.addRibbonIcon('bot', 'Open Obsidian Intelligence', (evt: MouseEvent) => {
|
||||
this.activateView();
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||
const statusBarItemEl = this.addStatusBarItem();
|
||||
statusBarItemEl.setText('Status Bar Text');
|
||||
|
||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||
const statusBarItemEl = this.addStatusBarItem();
|
||||
statusBarItemEl.setText('Status Bar Text');
|
||||
|
||||
this.addCommand({
|
||||
id: "obsidian-intelligence-view-open",
|
||||
name: "Open Obsidian Intelligence",
|
||||
hotkeys: [{ modifiers: ["Mod", "Shift"], key: "I"}],
|
||||
this.addCommand({
|
||||
id: 'obsidian-intelligence-view-open',
|
||||
name: 'Open Obsidian Intelligence',
|
||||
hotkeys: [{ modifiers: ['Mod', 'Shift'], key: 'I' }],
|
||||
callback: () => {
|
||||
this.activateView();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new OISettingTab(this.app, this));
|
||||
}
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new OISettingTab(this.app, this));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
onunload() {}
|
||||
|
||||
}
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData(),
|
||||
);
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
async activateView() {
|
||||
async activateView() {
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE);
|
||||
|
||||
await this.app.workspace.getRightLeaf(false).setViewState({
|
||||
type: VIEW_TYPE,
|
||||
active: true,
|
||||
type: VIEW_TYPE,
|
||||
active: true,
|
||||
});
|
||||
|
||||
this.app.workspace.revealLeaf(
|
||||
this.app.workspace.getLeavesOfType(VIEW_TYPE)[0]
|
||||
this.app.workspace.getLeavesOfType(VIEW_TYPE)[0],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OISettingTab extends PluginSettingTab {
|
||||
plugin: ObsidianIntelligence;
|
||||
plugin: ObsidianIntelligence;
|
||||
|
||||
constructor(app: App, plugin: ObsidianIntelligence) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
constructor(app: App, plugin: ObsidianIntelligence) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('OpenAI Key')
|
||||
.setDesc('Can find it https://platform.openai.com/api-keys')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your API Key')
|
||||
.setValue(this.plugin.settings.openaiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openaiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
new Setting(containerEl)
|
||||
.setName('OpenAI Key')
|
||||
.setDesc('Can find it https://platform.openai.com/api-keys')
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder('Enter your API Key')
|
||||
.setValue(this.plugin.settings.openaiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openaiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "obsidian-intelligence",
|
||||
"name": "Obsidian Intelligence",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "AI-powered assistants inside Obsidian",
|
||||
"author": "John Mavrick",
|
||||
"authorUrl": "https://beacons.ai/johnmavrick",
|
||||
"fundingUrl": "https://patreon.com/johnmavrick",
|
||||
"isDesktopOnly": false
|
||||
"id": "obsidian-intelligence",
|
||||
"name": "Obsidian Intelligence",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "AI-powered assistants inside Obsidian",
|
||||
"author": "John Mavrick",
|
||||
"authorUrl": "https://beacons.ai/johnmavrick",
|
||||
"fundingUrl": "https://patreon.com/johnmavrick",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
72
package.json
72
package.json
|
@ -1,37 +1,39 @@
|
|||
{
|
||||
"name": "obsidian-intelligence",
|
||||
"version": "1.0.0",
|
||||
"description": "AI-powered assistants inside Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"lucide-react": "^0.292.0",
|
||||
"openai": "^4.16.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-spinners": "^0.13.8",
|
||||
"react-tooltip": "^5.23.0",
|
||||
"yup": "^1.3.2"
|
||||
}
|
||||
"name": "obsidian-intelligence",
|
||||
"version": "1.0.0",
|
||||
"description": "AI-powered assistants inside Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"prettier": "prettier --write ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"prettier": "^3.1.0",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"lucide-react": "^0.292.0",
|
||||
"openai": "^4.16.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-spinners": "^0.13.8",
|
||||
"react-tooltip": "^5.23.0",
|
||||
"yup": "^1.3.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
# This is a test file
|
||||
|
||||
wow
|
|
@ -1,33 +1,33 @@
|
|||
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||
import * as React from "react";
|
||||
import { Root, createRoot } from "react-dom/client";
|
||||
import PluginView from "./PluginView";
|
||||
import { App } from "obsidian";
|
||||
import ObsidianIntelligence from "../../main";
|
||||
import OpenAI from "openai";
|
||||
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||
import * as React from 'react';
|
||||
import { Root, createRoot } from 'react-dom/client';
|
||||
import PluginView from './PluginView';
|
||||
import { App } from 'obsidian';
|
||||
import ObsidianIntelligence from '../../main';
|
||||
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 PluginContext = React.createContext<ObsidianIntelligence | undefined>(
|
||||
undefined
|
||||
);
|
||||
export const PluginContext = React.createContext<
|
||||
ObsidianIntelligence | undefined
|
||||
>(undefined);
|
||||
|
||||
export const OpenAIContext = React.createContext<OpenAI | undefined>(undefined);
|
||||
|
||||
// export const CommandsContext = React.createContext<ICommandPayload | undefined>(undefined);
|
||||
|
||||
export const useApp = (): App | undefined => {
|
||||
return React.useContext(AppContext);
|
||||
return React.useContext(AppContext);
|
||||
};
|
||||
|
||||
export const usePlugin = (): ObsidianIntelligence | undefined => {
|
||||
return React.useContext(PluginContext);
|
||||
return React.useContext(PluginContext);
|
||||
};
|
||||
|
||||
export const useOpenAI = (): OpenAI | undefined => {
|
||||
return React.useContext(OpenAIContext);
|
||||
return React.useContext(OpenAIContext);
|
||||
};
|
||||
|
||||
// export const useCommands = (): ICommandPayload | undefined => {
|
||||
|
@ -35,49 +35,49 @@ export const useOpenAI = (): OpenAI | undefined => {
|
|||
// };
|
||||
|
||||
export class AppView extends ItemView {
|
||||
root: Root | null = null;
|
||||
plugin: ObsidianIntelligence;
|
||||
openAI: OpenAI;
|
||||
// commands: ICommandPayload | undefined;
|
||||
root: Root | null = null;
|
||||
plugin: ObsidianIntelligence;
|
||||
openAI: OpenAI;
|
||||
// commands: ICommandPayload | undefined;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: ObsidianIntelligence) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
const openaiKey = plugin.settings.openaiKey;
|
||||
const openAIInstance = new OpenAI({
|
||||
apiKey: openaiKey,
|
||||
dangerouslyAllowBrowser: true,
|
||||
});
|
||||
this.openAI = openAIInstance;
|
||||
// this.addCommands();
|
||||
}
|
||||
constructor(leaf: WorkspaceLeaf, plugin: ObsidianIntelligence) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
const openaiKey = plugin.settings.openaiKey;
|
||||
const openAIInstance = new OpenAI({
|
||||
apiKey: openaiKey,
|
||||
dangerouslyAllowBrowser: true,
|
||||
});
|
||||
this.openAI = openAIInstance;
|
||||
// this.addCommands();
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
return VIEW_TYPE;
|
||||
}
|
||||
getViewType() {
|
||||
return VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return "Obsidian Intelligence";
|
||||
}
|
||||
getIcon(): string {
|
||||
return "bot";
|
||||
}
|
||||
getDisplayText() {
|
||||
return 'Obsidian Intelligence';
|
||||
}
|
||||
getIcon(): string {
|
||||
return 'bot';
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
this.root = createRoot(this.containerEl.children[1]);
|
||||
this.root.render(
|
||||
<AppContext.Provider value={this.app}>
|
||||
<PluginContext.Provider value={this.plugin}>
|
||||
<OpenAIContext.Provider value={this.openAI}>
|
||||
{/* <CommandsContext.Provider value={this.commands}> */}
|
||||
<PluginView />
|
||||
{/* </CommandsContext.Provider> */}
|
||||
</OpenAIContext.Provider>
|
||||
</PluginContext.Provider>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
async onClose() {
|
||||
this.root?.unmount();
|
||||
}
|
||||
async onOpen() {
|
||||
this.root = createRoot(this.containerEl.children[1]);
|
||||
this.root.render(
|
||||
<AppContext.Provider value={this.app}>
|
||||
<PluginContext.Provider value={this.plugin}>
|
||||
<OpenAIContext.Provider value={this.openAI}>
|
||||
{/* <CommandsContext.Provider value={this.commands}> */}
|
||||
<PluginView />
|
||||
{/* </CommandsContext.Provider> */}
|
||||
</OpenAIContext.Provider>
|
||||
</PluginContext.Provider>
|
||||
</AppContext.Provider>,
|
||||
);
|
||||
}
|
||||
async onClose() {
|
||||
this.root?.unmount();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,40 @@
|
|||
import OpenAI from 'openai';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import Chatbox from './components/Chatbox';
|
||||
import { useOpenAI, usePlugin, } from './AppView';
|
||||
import { useOpenAI, usePlugin } from './AppView';
|
||||
import MessageInput from './components/MessageInput';
|
||||
// import FilesUploadUI from './components/FilesUploadUI';
|
||||
import AssistantManager from './components/AssistantManager';
|
||||
import { IThread, ThreadAnnotationFile } from './types';
|
||||
import { createNotice } from '@/utils/Logs';
|
||||
|
||||
const listQueryOptions: OpenAI.Beta.Threads.MessageListParams = { order: 'asc'};
|
||||
const listQueryOptions: OpenAI.Beta.Threads.MessageListParams = {
|
||||
order: 'asc',
|
||||
};
|
||||
|
||||
const PluginView = () => {
|
||||
const plugin = usePlugin();
|
||||
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 [assistants, setAssistants] = useState<OpenAI.Beta.Assistant[]>([]);
|
||||
const [threads, setThreads] = useState<IThread[]>([]);
|
||||
const [activeAssistant, setActiveAssistant] = useState<OpenAI.Beta.Assistant | undefined>(undefined);
|
||||
const [activeThread, setActiveThread] = useState<IThread | undefined>(undefined);
|
||||
const [activeAssistantFiles, setActiveAssistantFiles] = useState<OpenAI.Files.FileObject[] | undefined>(undefined);
|
||||
const [activeAssistant, setActiveAssistant] = useState<
|
||||
OpenAI.Beta.Assistant | undefined
|
||||
>(undefined);
|
||||
const [activeThread, setActiveThread] = useState<IThread | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [activeAssistantFiles, setActiveAssistantFiles] = useState<
|
||||
OpenAI.Files.FileObject[] | undefined
|
||||
>(undefined);
|
||||
const [isResponding, setIsResponding] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchThreads();
|
||||
if (assistants.length < 1) {
|
||||
|
||||
fetchAssistants();
|
||||
fetchActiveConfiguration();
|
||||
}
|
||||
|
@ -48,7 +57,6 @@ const PluginView = () => {
|
|||
return;
|
||||
}
|
||||
await openaiInstance.beta.assistants.list().then((res) => {
|
||||
|
||||
// sort by name
|
||||
const sortedAssistants = res.data.sort((a, b) => {
|
||||
if (a.name && b.name && a.name < b.name) {
|
||||
|
@ -65,7 +73,7 @@ const PluginView = () => {
|
|||
if (plugin) {
|
||||
setThreads(plugin.settings.threads);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchActiveConfiguration = () => {
|
||||
if (plugin) {
|
||||
|
@ -78,7 +86,7 @@ const PluginView = () => {
|
|||
setActiveThread(savedActiveThread);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateActiveAssistant = async (assistant: OpenAI.Beta.Assistant) => {
|
||||
if (plugin) {
|
||||
|
@ -88,7 +96,7 @@ const PluginView = () => {
|
|||
plugin.saveSettings();
|
||||
setActiveAssistant(assistant);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateActiveAssistantFiles = (files: OpenAI.Files.FileObject[]) => {
|
||||
if (plugin) {
|
||||
|
@ -96,7 +104,7 @@ const PluginView = () => {
|
|||
plugin.saveSettings();
|
||||
setActiveAssistantFiles(files);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateThreads = (threads: IThread[]) => {
|
||||
if (plugin) {
|
||||
|
@ -104,30 +112,34 @@ const PluginView = () => {
|
|||
plugin.saveSettings();
|
||||
setThreads(threads);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const fetchAssistantFiles = async (): Promise<OpenAI.Files.FileObject[] | []> => {
|
||||
const fetchAssistantFiles = async (): Promise<
|
||||
OpenAI.Files.FileObject[] | []
|
||||
> => {
|
||||
if (!openaiInstance || !plugin || !activeAssistant) {
|
||||
return [];
|
||||
}
|
||||
|
||||
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[] = [];
|
||||
|
||||
await Promise.all(
|
||||
assistantFilesResponse.data.map(async (file) => {
|
||||
try {
|
||||
const fileInfoResponse = await openaiInstance.files.retrieve(file.id);
|
||||
const fileInfoResponse =
|
||||
await openaiInstance.files.retrieve(file.id);
|
||||
|
||||
innerFiles.push(fileInfoResponse);
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
return innerFiles;
|
||||
|
@ -139,24 +151,27 @@ const PluginView = () => {
|
|||
|
||||
const updateActiveThread = (thread: IThread) => {
|
||||
if (plugin) {
|
||||
|
||||
plugin.settings.activeThread = thread;
|
||||
|
||||
plugin.saveSettings();
|
||||
setActiveThread(thread);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMessages = async () => {
|
||||
|
||||
if (!openaiInstance || !activeThread) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
} 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
|
||||
const newThreads = threads.filter((t) => t.id !== activeThread.id);
|
||||
setThreads(newThreads);
|
||||
|
@ -175,84 +190,112 @@ const PluginView = () => {
|
|||
fetchMessages();
|
||||
}, [activeThread]);
|
||||
|
||||
const onMessageSend = useCallback(async (message: string) => {
|
||||
const onMessageSend = useCallback(
|
||||
async (message: string) => {
|
||||
if (openaiInstance && activeThread && activeAssistant) {
|
||||
const messageObject =
|
||||
await openaiInstance.beta.threads.messages.create(
|
||||
activeThread.id,
|
||||
{
|
||||
role: 'user',
|
||||
content: message,
|
||||
},
|
||||
);
|
||||
|
||||
if (openaiInstance && activeThread && activeAssistant) {
|
||||
setMessages([...messages, messageObject]);
|
||||
|
||||
const messageObject = await openaiInstance.beta.threads.messages.create(activeThread.id, {
|
||||
role: "user",
|
||||
content: message,
|
||||
});
|
||||
const run = await openaiInstance.beta.threads.runs.create(
|
||||
activeThread.id,
|
||||
{
|
||||
assistant_id: activeAssistant.id,
|
||||
},
|
||||
);
|
||||
|
||||
setMessages([...messages, messageObject]);
|
||||
let runStatus = await openaiInstance.beta.threads.runs.retrieve(
|
||||
activeThread.id,
|
||||
run.id,
|
||||
);
|
||||
|
||||
// Initialize a counter and max attempts for the polling logic, and how long to wait each try
|
||||
let attempts = 0;
|
||||
const maxAttempts = 20;
|
||||
const timoutWaitTimeMs = 2000;
|
||||
setIsResponding(true);
|
||||
|
||||
while (
|
||||
runStatus.status !== 'completed' &&
|
||||
attempts < maxAttempts
|
||||
) {
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, timoutWaitTimeMs),
|
||||
);
|
||||
runStatus = await openaiInstance.beta.threads.runs.retrieve(
|
||||
activeThread.id,
|
||||
run.id,
|
||||
);
|
||||
attempts++;
|
||||
}
|
||||
|
||||
const run = await openaiInstance.beta.threads.runs.create(activeThread.id, {
|
||||
assistant_id: activeAssistant.id,
|
||||
});
|
||||
|
||||
let runStatus = await openaiInstance.beta.threads.runs.retrieve(
|
||||
activeThread.id,
|
||||
run.id
|
||||
);
|
||||
|
||||
// Initialize a counter and max attempts for the polling logic, and how long to wait each try
|
||||
let attempts = 0;
|
||||
const maxAttempts = 20;
|
||||
const timoutWaitTimeMs = 2000;
|
||||
setIsResponding(true);
|
||||
|
||||
while (runStatus.status !== "completed" && attempts < maxAttempts) {
|
||||
await new Promise((resolve) => setTimeout(resolve, timoutWaitTimeMs));
|
||||
runStatus = await openaiInstance.beta.threads.runs.retrieve(activeThread.id, run.id);
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// Get latest messages
|
||||
await openaiInstance.beta.threads.messages.list(activeThread.id, listQueryOptions).then((res) => {
|
||||
setMessages(res.data);
|
||||
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
|
||||
res.data.forEach((message) => {
|
||||
if (message.content) {
|
||||
message.content.forEach((content) => {
|
||||
if (content.type === 'text') {
|
||||
content.text.annotations.forEach((annotation) => {
|
||||
// @ts-ignore
|
||||
if (annotation.file_citation) {
|
||||
// @ts-ignore
|
||||
const fileId: string = annotation.file_citation.file_id;
|
||||
const file = activeAssistantFiles?.find((file) => file.id === fileId);
|
||||
if (file) {
|
||||
files.push({
|
||||
fileId: fileId,
|
||||
fileName: file.filename,
|
||||
});
|
||||
}
|
||||
// Get latest messages
|
||||
await openaiInstance.beta.threads.messages
|
||||
.list(activeThread.id, listQueryOptions)
|
||||
.then((res) => {
|
||||
setMessages(res.data);
|
||||
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
|
||||
res.data.forEach((message) => {
|
||||
if (message.content) {
|
||||
message.content.forEach((content) => {
|
||||
if (content.type === 'text') {
|
||||
content.text.annotations.forEach(
|
||||
(annotation) => {
|
||||
// @ts-ignore
|
||||
if (annotation.file_citation) {
|
||||
const fileId: string =
|
||||
// @ts-ignore
|
||||
annotation.file_citation
|
||||
.file_id;
|
||||
const file =
|
||||
activeAssistantFiles?.find(
|
||||
(file) =>
|
||||
file.id ===
|
||||
fileId,
|
||||
);
|
||||
if (file) {
|
||||
files.push({
|
||||
fileId: fileId,
|
||||
fileName:
|
||||
file.filename,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (files.length > 0) {
|
||||
const newThread: IThread = {
|
||||
...activeThread,
|
||||
metadata: {
|
||||
...activeThread.metadata,
|
||||
annotationFiles: files,
|
||||
if (files.length > 0) {
|
||||
const newThread: IThread = {
|
||||
...activeThread,
|
||||
metadata: {
|
||||
...activeThread.metadata,
|
||||
annotationFiles: files,
|
||||
},
|
||||
};
|
||||
const newThreads = threads.map((t) =>
|
||||
t.id === activeThread.id ? newThread : t,
|
||||
);
|
||||
updateThreads(newThreads);
|
||||
updateActiveThread(newThread);
|
||||
}
|
||||
};
|
||||
const newThreads = threads.map((t) => t.id === activeThread.id ? newThread : t);
|
||||
updateThreads(newThreads);
|
||||
updateActiveThread(newThread);
|
||||
}
|
||||
|
||||
setIsResponding(false);
|
||||
});
|
||||
}
|
||||
}, [openaiInstance, activeThread, activeAssistant, messages]);
|
||||
setIsResponding(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
[openaiInstance, activeThread, activeAssistant, messages],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="agent-view-container">
|
||||
|
@ -264,12 +307,13 @@ const PluginView = () => {
|
|||
threads={threads}
|
||||
updateThreads={updateThreads}
|
||||
activeThread={activeThread}
|
||||
updateActiveThread={updateActiveThread}/>
|
||||
updateActiveThread={updateActiveThread}
|
||||
/>
|
||||
<Chatbox
|
||||
messages={messages}
|
||||
isResponding={isResponding}
|
||||
annotationFiles={activeThread?.metadata?.annotationFiles}
|
||||
/>
|
||||
/>
|
||||
<MessageInput onMessageSend={onMessageSend} />
|
||||
{/* <FilesUploadUI files={files} onAddFile={onAddFile} onRemoveFile={onRemoveFile} /> */}
|
||||
</div>
|
||||
|
|
|
@ -1,527 +1,523 @@
|
|||
import React, { useEffect, useMemo } from "react";
|
||||
import OpenAI from "openai";
|
||||
import { useApp, useOpenAI, usePlugin } from "../AppView";
|
||||
import { IThread } from "../types";
|
||||
import DropdownSelect from "./DropdownSelect";
|
||||
import { MarkdownView } from "obsidian";
|
||||
import { Bot, MessageSquare, Plus, Pencil, Trash2 } from "lucide-react";
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import OpenAI from 'openai';
|
||||
import { useApp, useOpenAI, usePlugin } from '../AppView';
|
||||
import { IThread } from '../types';
|
||||
import DropdownSelect from './DropdownSelect';
|
||||
import { MarkdownView } from 'obsidian';
|
||||
import { Bot, MessageSquare, Plus, Pencil, Trash2 } from 'lucide-react';
|
||||
import {
|
||||
AssistantEditModal,
|
||||
IAssistantEditModalValues,
|
||||
} from "./modals/AssistantEditModal";
|
||||
import {} from "./modals/AssistantEditModal";
|
||||
AssistantEditModal,
|
||||
IAssistantEditModalValues,
|
||||
} from './modals/AssistantEditModal';
|
||||
import {} from './modals/AssistantEditModal';
|
||||
import {
|
||||
ThreadEditModal,
|
||||
IThreadEditModalValues,
|
||||
} from "./modals/ThreadEditModal";
|
||||
import { createNotice } from "@/utils/Logs";
|
||||
import { defaultAssistantInstructions } from "@/utils/templates";
|
||||
ThreadEditModal,
|
||||
IThreadEditModalValues,
|
||||
} from './modals/ThreadEditModal';
|
||||
import { createNotice } from '@/utils/Logs';
|
||||
import { defaultAssistantInstructions } from '@/utils/templates';
|
||||
|
||||
interface AssistantManagerProps {
|
||||
assistants: OpenAI.Beta.Assistant[];
|
||||
updateAssistants: (assistants: OpenAI.Beta.Assistant[]) => void;
|
||||
threads: IThread[];
|
||||
updateThreads: (threads: IThread[]) => void;
|
||||
activeAssistant: OpenAI.Beta.Assistant | undefined;
|
||||
updateActiveAssistant: (assistant: OpenAI.Beta.Assistant) => void;
|
||||
activeThread: IThread | undefined;
|
||||
updateActiveThread: (thread: IThread) => void;
|
||||
assistants: OpenAI.Beta.Assistant[];
|
||||
updateAssistants: (assistants: OpenAI.Beta.Assistant[]) => void;
|
||||
threads: IThread[];
|
||||
updateThreads: (threads: IThread[]) => void;
|
||||
activeAssistant: OpenAI.Beta.Assistant | undefined;
|
||||
updateActiveAssistant: (assistant: OpenAI.Beta.Assistant) => void;
|
||||
activeThread: IThread | undefined;
|
||||
updateActiveThread: (thread: IThread) => void;
|
||||
}
|
||||
|
||||
const AssistantManager = ({
|
||||
assistants,
|
||||
updateAssistants,
|
||||
threads,
|
||||
updateThreads,
|
||||
activeAssistant,
|
||||
activeThread,
|
||||
updateActiveAssistant,
|
||||
updateActiveThread,
|
||||
assistants,
|
||||
updateAssistants,
|
||||
threads,
|
||||
updateThreads,
|
||||
activeAssistant,
|
||||
activeThread,
|
||||
updateActiveAssistant,
|
||||
updateActiveThread,
|
||||
}: AssistantManagerProps) => {
|
||||
const app = useApp();
|
||||
const plugin = usePlugin();
|
||||
const openaiInstance = useOpenAI();
|
||||
const app = useApp();
|
||||
const plugin = usePlugin();
|
||||
const openaiInstance = useOpenAI();
|
||||
|
||||
useEffect(() => {
|
||||
if (!plugin || !app) {
|
||||
return;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!plugin || !app) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.addCommand({
|
||||
id: "create-assistant-from-active-note",
|
||||
name: "Create Assistant from Active Note",
|
||||
checkCallback: (checking: boolean) => {
|
||||
// Conditions to check
|
||||
const markdownView =
|
||||
app.workspace.getActiveViewOfType(MarkdownView);
|
||||
const openFile = markdownView?.file;
|
||||
plugin.addCommand({
|
||||
id: 'create-assistant-from-active-note',
|
||||
name: 'Create Assistant from Active Note',
|
||||
checkCallback: (checking: boolean) => {
|
||||
// Conditions to check
|
||||
const markdownView =
|
||||
app.workspace.getActiveViewOfType(MarkdownView);
|
||||
const openFile = markdownView?.file;
|
||||
|
||||
if (openFile) {
|
||||
if (!checking) {
|
||||
// const links = app.metadataCache.getFileCache(openFile)?.links?.map(
|
||||
// (link) => addFileType(truncateLink(link.link))
|
||||
// ) || [];
|
||||
const links = Object.keys(
|
||||
app.metadataCache.resolvedLinks[openFile.path]
|
||||
);
|
||||
if (openFile) {
|
||||
if (!checking) {
|
||||
// const links = app.metadataCache.getFileCache(openFile)?.links?.map(
|
||||
// (link) => addFileType(truncateLink(link.link))
|
||||
// ) || [];
|
||||
const links = Object.keys(
|
||||
app.metadataCache.resolvedLinks[openFile.path],
|
||||
);
|
||||
|
||||
//@ts-ignore
|
||||
const backlinks = Object.keys(
|
||||
app.metadataCache.getBacklinksForFile(openFile).data
|
||||
).map((file) => file);
|
||||
console.log(
|
||||
"file metadata cache",
|
||||
app.metadataCache.getCache(openFile.path)?.links
|
||||
);
|
||||
const currentFile = openFile.path;
|
||||
const filesToUpload = new Set([
|
||||
currentFile,
|
||||
...links,
|
||||
...backlinks,
|
||||
]);
|
||||
const backlinks = Object.keys(
|
||||
//@ts-ignore
|
||||
app.metadataCache.getBacklinksForFile(openFile)
|
||||
.data,
|
||||
).map((file) => file);
|
||||
console.log(
|
||||
'file metadata cache',
|
||||
app.metadataCache.getCache(openFile.path)?.links,
|
||||
);
|
||||
const currentFile = openFile.path;
|
||||
const filesToUpload = new Set([
|
||||
currentFile,
|
||||
...links,
|
||||
...backlinks,
|
||||
]);
|
||||
|
||||
handleCreateAssistant({
|
||||
name: `${openFile.path} Assistant`,
|
||||
instructions: defaultAssistantInstructions,
|
||||
files: Array.from(filesToUpload)
|
||||
.filter((file) => file.endsWith(".md"))
|
||||
.map((file) => ({
|
||||
filename: file,
|
||||
})),
|
||||
});
|
||||
}
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
}
|
||||
},
|
||||
});
|
||||
}, [plugin]);
|
||||
handleCreateAssistant({
|
||||
name: `${openFile.path} Assistant`,
|
||||
instructions: defaultAssistantInstructions,
|
||||
files: Array.from(filesToUpload)
|
||||
.filter((file) => file.endsWith('.md'))
|
||||
.map((file) => ({
|
||||
filename: file,
|
||||
})),
|
||||
});
|
||||
}
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
}
|
||||
},
|
||||
});
|
||||
}, [plugin]);
|
||||
|
||||
useEffect(() => {}, [plugin]);
|
||||
useEffect(() => {}, [plugin]);
|
||||
|
||||
const createThread = async () => {
|
||||
if (!openaiInstance || !plugin) {
|
||||
return;
|
||||
}
|
||||
const createThread = async () => {
|
||||
if (!openaiInstance || !plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newThreadName = "New Thread";
|
||||
const newThreadName = 'New Thread';
|
||||
|
||||
await openaiInstance.beta.threads
|
||||
.create({
|
||||
metadata: {
|
||||
name: newThreadName,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
const newThread = {
|
||||
...res,
|
||||
metadata: {
|
||||
name: newThreadName,
|
||||
},
|
||||
};
|
||||
updateThreads([...threads, newThread]);
|
||||
updateActiveThread(newThread);
|
||||
plugin.saveSettings();
|
||||
});
|
||||
};
|
||||
await openaiInstance.beta.threads
|
||||
.create({
|
||||
metadata: {
|
||||
name: newThreadName,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
const newThread = {
|
||||
...res,
|
||||
metadata: {
|
||||
name: newThreadName,
|
||||
},
|
||||
};
|
||||
updateThreads([...threads, newThread]);
|
||||
updateActiveThread(newThread);
|
||||
plugin.saveSettings();
|
||||
});
|
||||
};
|
||||
|
||||
const editThread = async (values: IThreadEditModalValues) => {
|
||||
if (!openaiInstance || !app || !activeThread) {
|
||||
return;
|
||||
}
|
||||
const editThread = async (values: IThreadEditModalValues) => {
|
||||
if (!openaiInstance || !app || !activeThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newThread = {
|
||||
...activeThread,
|
||||
metadata: {
|
||||
name: values.metadata.name,
|
||||
},
|
||||
};
|
||||
const newThread = {
|
||||
...activeThread,
|
||||
metadata: {
|
||||
name: values.metadata.name,
|
||||
},
|
||||
};
|
||||
|
||||
await openaiInstance.beta.threads
|
||||
.update(activeThread.id, {
|
||||
metadata: {
|
||||
name: values.metadata.name,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
updateThreads(
|
||||
threads.map((thread) => {
|
||||
if (thread.id === activeThread.id) {
|
||||
return newThread;
|
||||
}
|
||||
return thread;
|
||||
})
|
||||
);
|
||||
updateActiveThread(newThread);
|
||||
});
|
||||
};
|
||||
await openaiInstance.beta.threads
|
||||
.update(activeThread.id, {
|
||||
metadata: {
|
||||
name: values.metadata.name,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
updateThreads(
|
||||
threads.map((thread) => {
|
||||
if (thread.id === activeThread.id) {
|
||||
return newThread;
|
||||
}
|
||||
return thread;
|
||||
}),
|
||||
);
|
||||
updateActiveThread(newThread);
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditThread = async () => {
|
||||
if (!openaiInstance || !app || !activeThread) {
|
||||
return;
|
||||
}
|
||||
const handleEditThread = async () => {
|
||||
if (!openaiInstance || !app || !activeThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the previous values of the thread
|
||||
const previousValues = {
|
||||
metadata: {
|
||||
name: activeThread.metadata.name || "",
|
||||
},
|
||||
};
|
||||
// Get the previous values of the thread
|
||||
const previousValues = {
|
||||
metadata: {
|
||||
name: activeThread.metadata.name || '',
|
||||
},
|
||||
};
|
||||
|
||||
new ThreadEditModal({
|
||||
app,
|
||||
title: "Edit Thread",
|
||||
submitButtonText: "Edit",
|
||||
previousValues,
|
||||
onSubmit: editThread,
|
||||
}).open();
|
||||
};
|
||||
new ThreadEditModal({
|
||||
app,
|
||||
title: 'Edit Thread',
|
||||
submitButtonText: 'Edit',
|
||||
previousValues,
|
||||
onSubmit: editThread,
|
||||
}).open();
|
||||
};
|
||||
|
||||
const deleteThread = async () => {
|
||||
if (!openaiInstance || !plugin || !activeThread) {
|
||||
return;
|
||||
}
|
||||
const deleteThread = async () => {
|
||||
if (!openaiInstance || !plugin || !activeThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
await openaiInstance.beta.threads
|
||||
.del(activeThread.id)
|
||||
.then((res) => {
|
||||
const newThreadsList = threads.filter(
|
||||
(thread) => thread.id !== activeThread.id
|
||||
);
|
||||
updateThreads(newThreadsList);
|
||||
updateActiveThread(newThreadsList?.[0]);
|
||||
plugin.saveSettings();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
await openaiInstance.beta.threads
|
||||
.del(activeThread.id)
|
||||
.then((res) => {
|
||||
const newThreadsList = threads.filter(
|
||||
(thread) => thread.id !== activeThread.id,
|
||||
);
|
||||
updateThreads(newThreadsList);
|
||||
updateActiveThread(newThreadsList?.[0]);
|
||||
plugin.saveSettings();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
const createAssistant = async (values: IAssistantEditModalValues) => {
|
||||
if (!openaiInstance || !app) {
|
||||
return;
|
||||
}
|
||||
const createAssistant = async (values: IAssistantEditModalValues) => {
|
||||
if (!openaiInstance || !app) {
|
||||
return;
|
||||
}
|
||||
|
||||
// use uploadFileToOpenAI to upload files to openai
|
||||
const uploadedFiles: string[] = [];
|
||||
await Promise.all(
|
||||
values.files.map(async (file) => {
|
||||
if (file.filename) {
|
||||
const uploadedFile = await uploadFileToOpenAI(
|
||||
file?.filename
|
||||
);
|
||||
// use uploadFileToOpenAI to upload files to openai
|
||||
const uploadedFiles: string[] = [];
|
||||
await Promise.all(
|
||||
values.files.map(async (file) => {
|
||||
if (file.filename) {
|
||||
const uploadedFile = await uploadFileToOpenAI(
|
||||
file?.filename,
|
||||
);
|
||||
|
||||
if (uploadedFile) {
|
||||
if (uploadedFile) {
|
||||
uploadedFiles.push(uploadedFile.id);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
uploadedFiles.push(uploadedFile.id);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
await openaiInstance.beta.assistants
|
||||
.create({
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
instructions: values.instructions,
|
||||
tools: [{ type: 'code_interpreter' }, { type: 'retrieval' }],
|
||||
model: 'gpt-4-1106-preview',
|
||||
file_ids: uploadedFiles,
|
||||
})
|
||||
.then((res) => {
|
||||
createNotice(`Assistant "${values.name}" created`);
|
||||
updateAssistants([...assistants, res]);
|
||||
updateActiveAssistant(res);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateAssistant = async (
|
||||
assistant?: IAssistantEditModalValues,
|
||||
) => {
|
||||
if (!openaiInstance || !app) {
|
||||
return;
|
||||
}
|
||||
|
||||
await openaiInstance.beta.assistants
|
||||
.create({
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
instructions: values.instructions,
|
||||
tools: [{ type: "code_interpreter" }, { type: "retrieval" }],
|
||||
model: "gpt-4-1106-preview",
|
||||
file_ids: uploadedFiles,
|
||||
})
|
||||
.then((res) => {
|
||||
createNotice(`Assistant "${values.name}" created`);
|
||||
updateAssistants([...assistants, res]);
|
||||
updateActiveAssistant(res);
|
||||
});
|
||||
};
|
||||
// const assistant = await openaiInstance.beta.assistants.create({
|
||||
// name: "Math Tutor",
|
||||
// instructions:
|
||||
// "You are a personal math tutor. Write and run code to answer math questions.",
|
||||
// tools: [{ type: "code_interpreter" }],
|
||||
// model: "gpt-4-1106-preview",
|
||||
// });
|
||||
// setActiveAssistant(assistant);
|
||||
new AssistantEditModal({
|
||||
app,
|
||||
title: 'Create New Assistant',
|
||||
submitButtonText: 'Create',
|
||||
previousValues: assistant,
|
||||
onSubmit: createAssistant,
|
||||
}).open();
|
||||
};
|
||||
|
||||
const handleCreateAssistant = async (
|
||||
assistant?: IAssistantEditModalValues
|
||||
) => {
|
||||
if (!openaiInstance || !app) {
|
||||
return;
|
||||
}
|
||||
const editAssistant = async (values: IAssistantEditModalValues) => {
|
||||
if (!openaiInstance || !app || !activeAssistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
// const assistant = await openaiInstance.beta.assistants.create({
|
||||
// name: "Math Tutor",
|
||||
// instructions:
|
||||
// "You are a personal math tutor. Write and run code to answer math questions.",
|
||||
// tools: [{ type: "code_interpreter" }],
|
||||
// model: "gpt-4-1106-preview",
|
||||
// });
|
||||
// setActiveAssistant(assistant);
|
||||
new AssistantEditModal({
|
||||
app,
|
||||
title: "Create New Assistant",
|
||||
submitButtonText: "Create",
|
||||
previousValues: assistant,
|
||||
onSubmit: createAssistant,
|
||||
}).open();
|
||||
};
|
||||
// Check each values.files element to see if there is an id. If there is, then it is already uploaded to openai and we don't need to upload it again.
|
||||
const filesToUpload: string[] = [];
|
||||
const assistantFiles: string[] = [];
|
||||
|
||||
const editAssistant = async (values: IAssistantEditModalValues) => {
|
||||
if (!openaiInstance || !app || !activeAssistant) {
|
||||
return;
|
||||
}
|
||||
values.files.forEach((file) => {
|
||||
if (file.id) {
|
||||
assistantFiles.push(file.id);
|
||||
} else if (file.filename) {
|
||||
filesToUpload.push(file.filename);
|
||||
}
|
||||
});
|
||||
|
||||
// Check each values.files element to see if there is an id. If there is, then it is already uploaded to openai and we don't need to upload it again.
|
||||
const filesToUpload: string[] = [];
|
||||
const assistantFiles: string[] = [];
|
||||
await Promise.all(
|
||||
filesToUpload.map(async (file) => {
|
||||
const uploadedFile = await uploadFileToOpenAI(file);
|
||||
|
||||
values.files.forEach((file) => {
|
||||
if (uploadedFile) {
|
||||
assistantFiles.push(uploadedFile.id);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (file.id) {
|
||||
assistantFiles.push(file.id);
|
||||
} else if (file.filename) {
|
||||
filesToUpload.push(file.filename);
|
||||
}
|
||||
});
|
||||
await openaiInstance.beta.assistants
|
||||
.update(activeAssistant.id, {
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
instructions: values.instructions,
|
||||
file_ids: assistantFiles,
|
||||
})
|
||||
.then((res) => {
|
||||
updateActiveAssistant(res);
|
||||
updateAssistants(
|
||||
assistants.map((assistant) => {
|
||||
if (assistant.id === activeAssistant.id) {
|
||||
return res;
|
||||
}
|
||||
return assistant;
|
||||
}),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
await Promise.all(
|
||||
filesToUpload.map(async (file) => {
|
||||
const uploadedFile = await uploadFileToOpenAI(file);
|
||||
const handleEditAssistant = async () => {
|
||||
if (!openaiInstance || !app || !activeAssistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uploadedFile) {
|
||||
assistantFiles.push(uploadedFile.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
const files = await getAssistantFiles();
|
||||
|
||||
await openaiInstance.beta.assistants
|
||||
.update(activeAssistant.id, {
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
instructions: values.instructions,
|
||||
file_ids: assistantFiles,
|
||||
})
|
||||
.then((res) => {
|
||||
updateActiveAssistant(res);
|
||||
updateAssistants(
|
||||
assistants.map((assistant) => {
|
||||
if (assistant.id === activeAssistant.id) {
|
||||
return res;
|
||||
}
|
||||
return assistant;
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
const previousValues = {
|
||||
name: activeAssistant.name || '',
|
||||
description: activeAssistant.description || '',
|
||||
instructions: activeAssistant.instructions || '',
|
||||
files: files || [],
|
||||
};
|
||||
|
||||
const handleEditAssistant = async () => {
|
||||
if (!openaiInstance || !app || !activeAssistant) {
|
||||
return;
|
||||
}
|
||||
new AssistantEditModal({
|
||||
app,
|
||||
title: 'Edit Assistant',
|
||||
submitButtonText: 'Edit',
|
||||
previousValues,
|
||||
onSubmit: editAssistant,
|
||||
}).open();
|
||||
};
|
||||
|
||||
const files = await getAssistantFiles();
|
||||
const deleteAssistant = async () => {
|
||||
if (!openaiInstance || !activeAssistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousValues = {
|
||||
name: activeAssistant.name || "",
|
||||
description: activeAssistant.description || "",
|
||||
instructions: activeAssistant.instructions || "",
|
||||
files: files || [],
|
||||
};
|
||||
await openaiInstance.beta.assistants
|
||||
.del(activeAssistant.id)
|
||||
.then((res) => {
|
||||
const newAssistantsList = assistants.filter(
|
||||
(assistant) => assistant.id !== activeAssistant.id,
|
||||
);
|
||||
|
||||
new AssistantEditModal({
|
||||
app,
|
||||
title: "Edit Assistant",
|
||||
submitButtonText: "Edit",
|
||||
previousValues,
|
||||
onSubmit: editAssistant,
|
||||
}).open();
|
||||
};
|
||||
updateAssistants(newAssistantsList);
|
||||
updateActiveAssistant(newAssistantsList?.[0]);
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAssistant = async () => {
|
||||
if (!openaiInstance || !activeAssistant) {
|
||||
return;
|
||||
}
|
||||
const uploadFileToOpenAI = async (
|
||||
fileName: string,
|
||||
): Promise<OpenAI.Files.FileObject | undefined> => {
|
||||
if (!openaiInstance || !plugin || !app) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await openaiInstance.beta.assistants
|
||||
.del(activeAssistant.id)
|
||||
.then((res) => {
|
||||
const newAssistantsList = assistants.filter(
|
||||
(assistant) => assistant.id !== activeAssistant.id
|
||||
);
|
||||
const file = await app.vault.adapter.read(fileName);
|
||||
const blob = new File([file], fileName, { type: 'text/markdown' });
|
||||
|
||||
updateAssistants(newAssistantsList);
|
||||
updateActiveAssistant(newAssistantsList?.[0]);
|
||||
});
|
||||
};
|
||||
const returnedFile = await openaiInstance.files
|
||||
.create({
|
||||
purpose: 'assistants',
|
||||
file: blob,
|
||||
})
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const uploadFileToOpenAI = async (
|
||||
fileName: string
|
||||
): Promise<OpenAI.Files.FileObject | undefined> => {
|
||||
if (!openaiInstance || !plugin || !app) {
|
||||
return undefined;
|
||||
}
|
||||
return returnedFile;
|
||||
};
|
||||
|
||||
const file = await app.vault.adapter.read(fileName);
|
||||
const blob = new File([file], fileName, { type: "text/markdown" });
|
||||
// const attachFileToAssistant = async (assistantId: string, fileId: string) => {
|
||||
// //file id is file-CFBYJh1WUxRWdAtdaScVZHS7
|
||||
// //assistant id is asst_0HHHYCL2dgImUlXbZKDRjac0
|
||||
// if (!openaiInstance || !plugin) {
|
||||
// return;
|
||||
// }
|
||||
// await openaiInstance.beta.assistants.files.create(
|
||||
// assistantId,
|
||||
// {
|
||||
// file_id: fileId,
|
||||
// }
|
||||
// ).then((res) => {
|
||||
// // Handle the response
|
||||
|
||||
const returnedFile = await openaiInstance.files
|
||||
.create({
|
||||
purpose: "assistants",
|
||||
file: blob,
|
||||
})
|
||||
.then((res) => {
|
||||
return res;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return undefined;
|
||||
});
|
||||
// }).catch((error) => {
|
||||
// // Handle the error
|
||||
// console.error(error);
|
||||
// });
|
||||
// };
|
||||
|
||||
return returnedFile;
|
||||
};
|
||||
const getAssistantFiles = async (): Promise<
|
||||
OpenAI.Files.FileObject[] | []
|
||||
> => {
|
||||
if (!openaiInstance || !plugin || !activeAssistant) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// const attachFileToAssistant = async (assistantId: string, fileId: string) => {
|
||||
// //file id is file-CFBYJh1WUxRWdAtdaScVZHS7
|
||||
// //assistant id is asst_0HHHYCL2dgImUlXbZKDRjac0
|
||||
// if (!openaiInstance || !plugin) {
|
||||
// return;
|
||||
// }
|
||||
// await openaiInstance.beta.assistants.files.create(
|
||||
// assistantId,
|
||||
// {
|
||||
// file_id: fileId,
|
||||
// }
|
||||
// ).then((res) => {
|
||||
// // Handle the response
|
||||
try {
|
||||
const assistantFilesResponse =
|
||||
await openaiInstance.beta.assistants.files.list(
|
||||
activeAssistant?.id,
|
||||
);
|
||||
|
||||
// }).catch((error) => {
|
||||
// // Handle the error
|
||||
// console.error(error);
|
||||
// });
|
||||
// };
|
||||
const innerFiles: OpenAI.Files.FileObject[] = [];
|
||||
|
||||
const getAssistantFiles = async (): Promise<
|
||||
OpenAI.Files.FileObject[] | []
|
||||
> => {
|
||||
if (!openaiInstance || !plugin || !activeAssistant) {
|
||||
return [];
|
||||
}
|
||||
await Promise.all(
|
||||
assistantFilesResponse.data.map(async (file) => {
|
||||
try {
|
||||
const fileInfoResponse =
|
||||
await openaiInstance.files.retrieve(file.id);
|
||||
|
||||
try {
|
||||
const assistantFilesResponse =
|
||||
await openaiInstance.beta.assistants.files.list(
|
||||
activeAssistant?.id
|
||||
);
|
||||
innerFiles.push(fileInfoResponse);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const innerFiles: OpenAI.Files.FileObject[] = [];
|
||||
return innerFiles;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all(
|
||||
assistantFilesResponse.data.map(async (file) => {
|
||||
try {
|
||||
const fileInfoResponse =
|
||||
await openaiInstance.files.retrieve(file.id);
|
||||
//format assistants into ISelectOption
|
||||
const assistantOptions = useMemo(() => {
|
||||
return assistants.map((assistant) => {
|
||||
return {
|
||||
label: assistant.name || '',
|
||||
value: assistant.id,
|
||||
};
|
||||
});
|
||||
}, [assistants]);
|
||||
|
||||
innerFiles.push(fileInfoResponse);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
const threadOptions = useMemo(() => {
|
||||
return threads.map((thread) => {
|
||||
return {
|
||||
label: thread.metadata.name,
|
||||
value: thread.id,
|
||||
};
|
||||
});
|
||||
}, [threads]);
|
||||
|
||||
return innerFiles;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
const onUpdateActiveAssistant = (assistantId: string) => {
|
||||
if (!openaiInstance || !activeAssistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
//format assistants into ISelectOption
|
||||
const assistantOptions = useMemo(() => {
|
||||
return assistants.map((assistant) => {
|
||||
return {
|
||||
label: assistant.name || "",
|
||||
value: assistant.id,
|
||||
};
|
||||
});
|
||||
}, [assistants]);
|
||||
const assistant = assistants.find(
|
||||
(assistant) => assistant.id === assistantId,
|
||||
);
|
||||
|
||||
const threadOptions = useMemo(() => {
|
||||
return threads.map((thread) => {
|
||||
return {
|
||||
label: thread.metadata.name,
|
||||
value: thread.id,
|
||||
};
|
||||
});
|
||||
}, [threads]);
|
||||
updateActiveAssistant(assistant ?? assistants?.[0]);
|
||||
};
|
||||
|
||||
const onUpdateActiveAssistant = (assistantId: string) => {
|
||||
if (!openaiInstance || !activeAssistant) {
|
||||
return;
|
||||
}
|
||||
const onUpdateActiveThread = (threadId: string) => {
|
||||
if (!openaiInstance || !activeThread) {
|
||||
return;
|
||||
}
|
||||
const newActiveThread = threads.find(
|
||||
(thread) => thread.id === threadId,
|
||||
);
|
||||
updateActiveThread(newActiveThread ?? activeThread);
|
||||
};
|
||||
|
||||
const assistant = assistants.find(
|
||||
(assistant) => assistant.id === assistantId
|
||||
);
|
||||
|
||||
updateActiveAssistant(assistant ?? assistants?.[0]);
|
||||
};
|
||||
|
||||
const onUpdateActiveThread = (threadId: string) => {
|
||||
if (!openaiInstance || !activeThread) {
|
||||
return;
|
||||
}
|
||||
const newActiveThread = threads.find(
|
||||
(thread) => thread.id === threadId
|
||||
);
|
||||
updateActiveThread(newActiveThread ?? activeThread);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="chat-top-section-container">
|
||||
<div className="dropdowns-container">
|
||||
<div className="dropdown-container">
|
||||
<Bot size={16} />
|
||||
<DropdownSelect
|
||||
items={assistantOptions}
|
||||
onChange={onUpdateActiveAssistant}
|
||||
activeItem={activeAssistant?.id || ""}
|
||||
/>
|
||||
<div className="dropdown-buttons-container">
|
||||
<button className="create" onClick={deleteAssistant}>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
<button
|
||||
className="create"
|
||||
onClick={handleEditAssistant}
|
||||
>
|
||||
<Pencil size={16} />
|
||||
</button>
|
||||
<button
|
||||
className="create"
|
||||
onClick={() => handleCreateAssistant()}
|
||||
>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dropdown-container">
|
||||
<MessageSquare size={16} />
|
||||
<DropdownSelect
|
||||
items={threadOptions}
|
||||
onChange={onUpdateActiveThread}
|
||||
activeItem={activeThread?.id || ""}
|
||||
/>
|
||||
<div className="dropdown-buttons-container">
|
||||
<button className="create" onClick={deleteThread}>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
<button className="create" onClick={handleEditThread}>
|
||||
<Pencil size={16} />
|
||||
</button>
|
||||
<button className="create" onClick={createThread}>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="chat-top-section-container">
|
||||
<div className="dropdowns-container">
|
||||
<div className="dropdown-container">
|
||||
<Bot size={16} />
|
||||
<DropdownSelect
|
||||
items={assistantOptions}
|
||||
onChange={onUpdateActiveAssistant}
|
||||
activeItem={activeAssistant?.id || ''}
|
||||
/>
|
||||
<div className="dropdown-buttons-container">
|
||||
<button className="create" onClick={deleteAssistant}>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
<button
|
||||
className="create"
|
||||
onClick={handleEditAssistant}
|
||||
>
|
||||
<Pencil size={16} />
|
||||
</button>
|
||||
<button
|
||||
className="create"
|
||||
onClick={() => handleCreateAssistant()}
|
||||
>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dropdown-container">
|
||||
<MessageSquare size={16} />
|
||||
<DropdownSelect
|
||||
items={threadOptions}
|
||||
onChange={onUpdateActiveThread}
|
||||
activeItem={activeThread?.id || ''}
|
||||
/>
|
||||
<div className="dropdown-buttons-container">
|
||||
<button className="create" onClick={deleteThread}>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
<button className="create" onClick={handleEditThread}>
|
||||
<Pencil size={16} />
|
||||
</button>
|
||||
<button className="create" onClick={createThread}>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssistantManager;
|
||||
|
|
|
@ -1,240 +1,240 @@
|
|||
import React, { CSSProperties, useEffect, useRef, useState } from "react";
|
||||
import OpenAI from "openai";
|
||||
import { ChevronDown, ClipboardCopy } from "lucide-react";
|
||||
import BeatLoader from "react-spinners/BeatLoader";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import Markdown from "react-markdown";
|
||||
import { useApp } from "../AppView";
|
||||
import { createNotice } from "@/utils/Logs";
|
||||
import { TFile } from "obsidian";
|
||||
import { ThreadAnnotationFile } from "../types";
|
||||
import React, { CSSProperties, useEffect, useRef, useState } from 'react';
|
||||
import OpenAI from 'openai';
|
||||
import { ChevronDown, ClipboardCopy } from 'lucide-react';
|
||||
import BeatLoader from 'react-spinners/BeatLoader';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import Markdown from 'react-markdown';
|
||||
import { useApp } from '../AppView';
|
||||
import { createNotice } from '@/utils/Logs';
|
||||
import { TFile } from 'obsidian';
|
||||
import { ThreadAnnotationFile } from '../types';
|
||||
|
||||
const override: CSSProperties = {
|
||||
display: "block",
|
||||
margin: "0 auto",
|
||||
borderColor: "white",
|
||||
display: 'block',
|
||||
margin: '0 auto',
|
||||
borderColor: 'white',
|
||||
};
|
||||
|
||||
interface ChatboxProps {
|
||||
messages: OpenAI.Beta.Threads.ThreadMessage[];
|
||||
isResponding: boolean;
|
||||
annotationFiles?: ThreadAnnotationFile[];
|
||||
messages: OpenAI.Beta.Threads.ThreadMessage[];
|
||||
isResponding: boolean;
|
||||
annotationFiles?: ThreadAnnotationFile[];
|
||||
}
|
||||
|
||||
const Chatbox = ({ annotationFiles, isResponding, messages }: ChatboxProps) => {
|
||||
const app = useApp();
|
||||
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||
const app = useApp();
|
||||
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (messagesContainerRef.current) {
|
||||
messagesContainerRef.current.scrollTop =
|
||||
messagesContainerRef.current.scrollHeight;
|
||||
checkScrollButtonVisibility();
|
||||
}
|
||||
useEffect(() => {
|
||||
if (messagesContainerRef.current) {
|
||||
messagesContainerRef.current.scrollTop =
|
||||
messagesContainerRef.current.scrollHeight;
|
||||
checkScrollButtonVisibility();
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
checkScrollButtonVisibility();
|
||||
};
|
||||
const handleScroll = () => {
|
||||
checkScrollButtonVisibility();
|
||||
};
|
||||
|
||||
if (messagesContainerRef.current) {
|
||||
scrollToBottom();
|
||||
messagesContainerRef.current.addEventListener(
|
||||
"scroll",
|
||||
handleScroll
|
||||
);
|
||||
}
|
||||
if (messagesContainerRef.current) {
|
||||
scrollToBottom();
|
||||
messagesContainerRef.current.addEventListener(
|
||||
'scroll',
|
||||
handleScroll,
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (messagesContainerRef.current) {
|
||||
messagesContainerRef.current.removeEventListener(
|
||||
"scroll",
|
||||
handleScroll
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [isResponding, messagesContainerRef.current]);
|
||||
return () => {
|
||||
if (messagesContainerRef.current) {
|
||||
messagesContainerRef.current.removeEventListener(
|
||||
'scroll',
|
||||
handleScroll,
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [isResponding, messagesContainerRef.current]);
|
||||
|
||||
useEffect(() => {
|
||||
// scroll to bottom when switching to different thread
|
||||
if (showScrollButton) {
|
||||
scrollToBottom();
|
||||
setShowScrollButton(false);
|
||||
}
|
||||
}, [messages]);
|
||||
useEffect(() => {
|
||||
// scroll to bottom when switching to different thread
|
||||
if (showScrollButton) {
|
||||
scrollToBottom();
|
||||
setShowScrollButton(false);
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
const getGroupMessages = () =>
|
||||
messages.map((message, index) => (
|
||||
<div key={index} className={`chat-message ${message.role}`}>
|
||||
{message.content.map((content, index) => {
|
||||
if (content.type === "text") {
|
||||
const getMessageText = () => {
|
||||
const annotationsTexts =
|
||||
content.text.annotations.map(
|
||||
(annotation: any) => annotation.text
|
||||
);
|
||||
let text = content.text.value;
|
||||
const getGroupMessages = () =>
|
||||
messages.map((message, index) => (
|
||||
<div key={index} className={`chat-message ${message.role}`}>
|
||||
{message.content.map((content, index) => {
|
||||
if (content.type === 'text') {
|
||||
const getMessageText = () => {
|
||||
const annotationsTexts =
|
||||
content.text.annotations.map(
|
||||
(annotation: any) => annotation.text,
|
||||
);
|
||||
let text = content.text.value;
|
||||
|
||||
annotationsTexts.forEach(
|
||||
(annotationText: string, index: number) => {
|
||||
const annotationIndex = index;
|
||||
const regex = new RegExp(
|
||||
annotationText,
|
||||
"g"
|
||||
);
|
||||
text = text.replace(
|
||||
regex,
|
||||
`[^${annotationIndex}]`
|
||||
);
|
||||
}
|
||||
);
|
||||
annotationsTexts.forEach(
|
||||
(annotationText: string, index: number) => {
|
||||
const annotationIndex = index;
|
||||
const regex = new RegExp(
|
||||
annotationText,
|
||||
'g',
|
||||
);
|
||||
text = text.replace(
|
||||
regex,
|
||||
`[^${annotationIndex}]`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return text;
|
||||
};
|
||||
return text;
|
||||
};
|
||||
|
||||
const renderAnnotation = (
|
||||
annotation: any,
|
||||
index: number
|
||||
) => {
|
||||
const { file_citation } = annotation;
|
||||
const fileId = file_citation?.file_id;
|
||||
const fileName = annotationFiles?.find(
|
||||
(file) => file.fileId === fileId
|
||||
)?.fileName;
|
||||
let quote = file_citation?.quote;
|
||||
const renderAnnotation = (
|
||||
annotation: any,
|
||||
index: number,
|
||||
) => {
|
||||
const { file_citation } = annotation;
|
||||
const fileId = file_citation?.file_id;
|
||||
const fileName = annotationFiles?.find(
|
||||
(file) => file.fileId === fileId,
|
||||
)?.fileName;
|
||||
let quote = file_citation?.quote;
|
||||
|
||||
// Check if quote has list markdown syntax
|
||||
if (quote && quote.includes("- ")) {
|
||||
quote = quote.replace(/- /g, "\n- ");
|
||||
}
|
||||
// Check if quote has list markdown syntax
|
||||
if (quote && quote.includes('- ')) {
|
||||
quote = quote.replace(/- /g, '\n- ');
|
||||
}
|
||||
|
||||
const handleAnnotationClick = () => {
|
||||
// open new tab and then navigate to fil
|
||||
console.log(
|
||||
"handleAnnotationClick",
|
||||
app,
|
||||
fileName
|
||||
);
|
||||
if (app && fileName) {
|
||||
const file =
|
||||
app.vault.getAbstractFileByPath(
|
||||
fileName
|
||||
);
|
||||
if (file && file instanceof TFile) {
|
||||
app.workspace.getLeaf().openFile(file);
|
||||
}
|
||||
// add leaf to parent
|
||||
}
|
||||
};
|
||||
const handleAnnotationClick = () => {
|
||||
// open new tab and then navigate to fil
|
||||
console.log(
|
||||
'handleAnnotationClick',
|
||||
app,
|
||||
fileName,
|
||||
);
|
||||
if (app && fileName) {
|
||||
const file =
|
||||
app.vault.getAbstractFileByPath(
|
||||
fileName,
|
||||
);
|
||||
if (file && file instanceof TFile) {
|
||||
app.workspace.getLeaf().openFile(file);
|
||||
}
|
||||
// add leaf to parent
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={index}>
|
||||
<a
|
||||
className="annotation"
|
||||
data-tooltip-id={`tooltip-${index}`}
|
||||
onClick={handleAnnotationClick}
|
||||
>
|
||||
[^{index}]
|
||||
</a>
|
||||
<Tooltip id={`tooltip-${index}`}>
|
||||
<div className="annotation-tooltip-container">
|
||||
<strong>{fileName}</strong>
|
||||
<Markdown>{quote}</Markdown>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div key={index}>
|
||||
<a
|
||||
className="annotation"
|
||||
data-tooltip-id={`tooltip-${index}`}
|
||||
onClick={handleAnnotationClick}
|
||||
>
|
||||
[^{index}]
|
||||
</a>
|
||||
<Tooltip id={`tooltip-${index}`}>
|
||||
<div className="annotation-tooltip-container">
|
||||
<strong>{fileName}</strong>
|
||||
<Markdown>{quote}</Markdown>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
const text = getMessageText();
|
||||
const copyText = () => {
|
||||
navigator.clipboard.writeText(text);
|
||||
createNotice("Copied to clipboard!", 2000);
|
||||
};
|
||||
return (
|
||||
<div key={index} className="message-content">
|
||||
{
|
||||
<Markdown className="message-text">
|
||||
{text}
|
||||
</Markdown>
|
||||
}
|
||||
<div className="message-footer">
|
||||
{message.role === "assistant" && (
|
||||
<div className="copy-icon-container">
|
||||
<ClipboardCopy
|
||||
className="copy-icon"
|
||||
color={"#ffffff"}
|
||||
size={16}
|
||||
onClick={copyText}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{content.text.annotations.map(
|
||||
(annotation: any, index: number) =>
|
||||
renderAnnotation(
|
||||
annotation,
|
||||
index
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const renderContent = () => {
|
||||
const text = getMessageText();
|
||||
const copyText = () => {
|
||||
navigator.clipboard.writeText(text);
|
||||
createNotice('Copied to clipboard!', 2000);
|
||||
};
|
||||
return (
|
||||
<div key={index} className="message-content">
|
||||
{
|
||||
<Markdown className="message-text">
|
||||
{text}
|
||||
</Markdown>
|
||||
}
|
||||
<div className="message-footer">
|
||||
{message.role === 'assistant' && (
|
||||
<div className="copy-icon-container">
|
||||
<ClipboardCopy
|
||||
className="copy-icon"
|
||||
color={'#ffffff'}
|
||||
size={16}
|
||||
onClick={copyText}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{content.text.annotations.map(
|
||||
(annotation: any, index: number) =>
|
||||
renderAnnotation(
|
||||
annotation,
|
||||
index,
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={index} className="message-content">
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div key={index} className="message-content">
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
));
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (messagesContainerRef.current) {
|
||||
messagesContainerRef.current.scrollTop =
|
||||
messagesContainerRef.current.scrollHeight;
|
||||
checkScrollButtonVisibility();
|
||||
}
|
||||
};
|
||||
const scrollToBottom = () => {
|
||||
if (messagesContainerRef.current) {
|
||||
messagesContainerRef.current.scrollTop =
|
||||
messagesContainerRef.current.scrollHeight;
|
||||
checkScrollButtonVisibility();
|
||||
}
|
||||
};
|
||||
|
||||
const checkScrollButtonVisibility = () => {
|
||||
if (messagesContainerRef.current) {
|
||||
const containerHeight = messagesContainerRef.current.offsetHeight;
|
||||
const scrollHeight = messagesContainerRef.current.scrollHeight;
|
||||
const scrollTop = messagesContainerRef.current.scrollTop;
|
||||
const gap = scrollHeight - scrollTop - containerHeight;
|
||||
setShowScrollButton(gap > containerHeight);
|
||||
}
|
||||
};
|
||||
const checkScrollButtonVisibility = () => {
|
||||
if (messagesContainerRef.current) {
|
||||
const containerHeight = messagesContainerRef.current.offsetHeight;
|
||||
const scrollHeight = messagesContainerRef.current.scrollHeight;
|
||||
const scrollTop = messagesContainerRef.current.scrollTop;
|
||||
const gap = scrollHeight - scrollTop - containerHeight;
|
||||
setShowScrollButton(gap > containerHeight);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chatbox-container">
|
||||
<div ref={messagesContainerRef} className="messages-container">
|
||||
{getGroupMessages()}
|
||||
{isResponding && (
|
||||
<div className="loader-container">
|
||||
<BeatLoader
|
||||
color="#ffffff"
|
||||
loading={true}
|
||||
cssOverride={override}
|
||||
size={12}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showScrollButton && (
|
||||
<button
|
||||
className="scroll-to-bottom-button"
|
||||
onClick={scrollToBottom}
|
||||
>
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="chatbox-container">
|
||||
<div ref={messagesContainerRef} className="messages-container">
|
||||
{getGroupMessages()}
|
||||
{isResponding && (
|
||||
<div className="loader-container">
|
||||
<BeatLoader
|
||||
color="#ffffff"
|
||||
loading={true}
|
||||
cssOverride={override}
|
||||
size={12}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showScrollButton && (
|
||||
<button
|
||||
className="scroll-to-bottom-button"
|
||||
onClick={scrollToBottom}
|
||||
>
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Chatbox;
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
import React, { useEffect, useRef } from "react";
|
||||
import { DropdownComponent } from "obsidian";
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { DropdownComponent } from 'obsidian';
|
||||
|
||||
interface ISelectOption {
|
||||
label: string;
|
||||
value: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
interface DropdownSelectProps {
|
||||
items: ISelectOption[];
|
||||
onChange: (item: string) => void;
|
||||
activeItem: string;
|
||||
items: ISelectOption[];
|
||||
onChange: (item: string) => void;
|
||||
activeItem: string;
|
||||
}
|
||||
|
||||
const DropdownSelect: React.FC<DropdownSelectProps> = ({
|
||||
items,
|
||||
onChange,
|
||||
activeItem,
|
||||
items,
|
||||
onChange,
|
||||
activeItem,
|
||||
}) => {
|
||||
const selectElementRef = useRef<HTMLSelectElement>(null);
|
||||
const selectElementRef = useRef<HTMLSelectElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectElementRef.current) {
|
||||
new DropdownComponent(selectElementRef.current);
|
||||
}
|
||||
}, [selectElementRef.current]);
|
||||
useEffect(() => {
|
||||
if (selectElementRef.current) {
|
||||
new DropdownComponent(selectElementRef.current);
|
||||
}
|
||||
}, [selectElementRef.current]);
|
||||
|
||||
return (
|
||||
<select
|
||||
ref={selectElementRef}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={activeItem}
|
||||
className={"dropdown-select"}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<option className="" key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
return (
|
||||
<select
|
||||
ref={selectElementRef}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={activeItem}
|
||||
className={'dropdown-select'}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<option className="" key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownSelect;
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
import { SendHorizontal } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { SendHorizontal } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface MessageInputProps {
|
||||
onMessageSend: (message: string) => void;
|
||||
onMessageSend: (message: string) => void;
|
||||
}
|
||||
|
||||
const MessageInput: React.FC<MessageInputProps> = ({ onMessageSend }) => {
|
||||
const [newMessage, setNewMessage] = useState("");
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
|
||||
const handleSend = () => {
|
||||
onMessageSend(newMessage);
|
||||
setNewMessage("");
|
||||
};
|
||||
const handleSend = () => {
|
||||
onMessageSend(newMessage);
|
||||
setNewMessage('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="message-input container">
|
||||
<textarea
|
||||
className="message-input input"
|
||||
placeholder="Type your message here..."
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
/>
|
||||
<button className="message-input send" onClick={handleSend}>
|
||||
<SendHorizontal size={16} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="message-input container">
|
||||
<textarea
|
||||
className="message-input input"
|
||||
placeholder="Type your message here..."
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
/>
|
||||
<button className="message-input send" onClick={handleSend}>
|
||||
<SendHorizontal size={16} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageInput;
|
||||
|
|
|
@ -2,14 +2,14 @@ import { App, ButtonComponent, Modal, Notice, Setting } from 'obsidian';
|
|||
import OpenAI from 'openai';
|
||||
import { FileSuggest } from '../suggesters/FileSuggester';
|
||||
import * as yup from 'yup';
|
||||
import { defaultAssistantInstructions } from '../../../utils/templates'
|
||||
import { defaultAssistantInstructions } from '../../../utils/templates';
|
||||
|
||||
interface AssistantEditModalProps {
|
||||
app: App,
|
||||
title: string,
|
||||
submitButtonText?: string,
|
||||
previousValues?: IAssistantEditModalValues,
|
||||
onSubmit: (values: IAssistantEditModalValues) => void,
|
||||
app: App;
|
||||
title: string;
|
||||
submitButtonText?: string;
|
||||
previousValues?: IAssistantEditModalValues;
|
||||
onSubmit: (values: IAssistantEditModalValues) => void;
|
||||
}
|
||||
|
||||
export interface IAssistantEditModalValues {
|
||||
|
@ -60,7 +60,7 @@ export class AssistantEditModal extends Modal {
|
|||
new Setting(contentEl)
|
||||
.setName('Name (required)')
|
||||
.setDesc('The name of the assistant')
|
||||
.addText(text => {
|
||||
.addText((text) => {
|
||||
text.setPlaceholder('Enter name...')
|
||||
.onChange((value) => {
|
||||
this.values.name = value;
|
||||
|
@ -74,7 +74,7 @@ export class AssistantEditModal extends Modal {
|
|||
.setName('Description')
|
||||
.setDesc('The description of the assistant')
|
||||
.setClass('form-setting-textarea')
|
||||
.addTextArea(text => {
|
||||
.addTextArea((text) => {
|
||||
text.setPlaceholder('Enter description...')
|
||||
.onChange((value) => {
|
||||
this.values.description = value;
|
||||
|
@ -88,7 +88,7 @@ export class AssistantEditModal extends Modal {
|
|||
.setName('Instructions (required)')
|
||||
.setDesc('The instructions you want the assistant to follow')
|
||||
.setClass('form-setting-textarea')
|
||||
.addTextArea(text => {
|
||||
.addTextArea((text) => {
|
||||
text.setPlaceholder('Enter instructions...')
|
||||
.onChange((value) => {
|
||||
this.values.instructions = value;
|
||||
|
@ -101,10 +101,12 @@ export class AssistantEditModal extends Modal {
|
|||
// Function to add a file to the list
|
||||
|
||||
const addFileToList = (fileName: string) => {
|
||||
|
||||
|
||||
// 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) {
|
||||
this.values.files[fileIndex] = {
|
||||
filename: fileName,
|
||||
|
@ -120,21 +122,24 @@ export class AssistantEditModal extends Modal {
|
|||
};
|
||||
|
||||
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 fileDiv = this.fileListDiv.createDiv({ cls: 'file-item' });
|
||||
fileDiv.createEl('span', { text: fileName });
|
||||
|
||||
|
||||
new ButtonComponent(fileDiv)
|
||||
.setIcon('trash-2')
|
||||
.setClass('remove-button')
|
||||
.onClick(() => {
|
||||
fileDiv.remove();
|
||||
// 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) {
|
||||
this.values.files.remove(file);
|
||||
updateFileCountText();
|
||||
|
@ -144,9 +149,11 @@ export class AssistantEditModal extends Modal {
|
|||
|
||||
new Setting(contentEl)
|
||||
.setName(`Files`)
|
||||
.setDesc('The files uploaded to the assistant (not real-time synced, so you will need to re-upload). Max 20.')
|
||||
.addSearch(search => {
|
||||
search.setPlaceholder('Enter file IDs separated by commas...')
|
||||
.setDesc(
|
||||
'The files uploaded to the assistant (not real-time synced, so you will need to re-upload). Max 20.',
|
||||
)
|
||||
.addSearch((search) => {
|
||||
search.setPlaceholder('Enter file IDs separated by commas...');
|
||||
// .onChange((value) => {
|
||||
|
||||
// this.values.file_ids.push(value);
|
||||
|
@ -160,10 +167,8 @@ export class AssistantEditModal extends Modal {
|
|||
updateFileCountText();
|
||||
this.fileListDiv = contentEl.createDiv({ cls: 'file-list' });
|
||||
|
||||
|
||||
// Add the files that were already selected
|
||||
this.values.files.forEach(file => {
|
||||
|
||||
this.values.files.forEach((file) => {
|
||||
if (file.filename) {
|
||||
createFileListElement(file.filename);
|
||||
}
|
||||
|
@ -171,7 +176,6 @@ export class AssistantEditModal extends Modal {
|
|||
}
|
||||
|
||||
addSubmitButton(contentEl: HTMLElement) {
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
name: yup.string().required('Name is required'),
|
||||
instructions: yup.string().required('Instructions are required'),
|
||||
|
@ -180,7 +184,9 @@ export class AssistantEditModal extends Modal {
|
|||
|
||||
const checkRequiredFields = async (): Promise<string[]> => {
|
||||
try {
|
||||
await validationSchema.validate(this.values, { abortEarly: false });
|
||||
await validationSchema.validate(this.values, {
|
||||
abortEarly: false,
|
||||
});
|
||||
return [];
|
||||
} catch (error) {
|
||||
if (error instanceof yup.ValidationError) {
|
||||
|
@ -201,7 +207,7 @@ export class AssistantEditModal extends Modal {
|
|||
|
||||
this.onClose();
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
const modalFooterEl = contentEl.createDiv({ cls: 'modal-footer' });
|
||||
new ButtonComponent(modalFooterEl)
|
||||
.setButtonText(this.submitButtonText)
|
||||
|
@ -211,6 +217,5 @@ export class AssistantEditModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
}
|
||||
onClose() {}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,18 @@ import { App, ButtonComponent, Modal, Notice, Setting } from 'obsidian';
|
|||
import * as yup from 'yup';
|
||||
|
||||
interface ThreadEditModalProps {
|
||||
app: App,
|
||||
title: string,
|
||||
submitButtonText?: string,
|
||||
previousValues?: IThreadEditModalValues,
|
||||
onSubmit: (values: IThreadEditModalValues) => void,
|
||||
app: App;
|
||||
title: string;
|
||||
submitButtonText?: string;
|
||||
previousValues?: IThreadEditModalValues;
|
||||
onSubmit: (values: IThreadEditModalValues) => void;
|
||||
}
|
||||
|
||||
export interface IThreadEditModalValues {
|
||||
metadata: {
|
||||
name: string;
|
||||
[key: string]: unknown;
|
||||
},
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ export class ThreadEditModal extends Modal {
|
|||
new Setting(contentEl)
|
||||
.setName('Name')
|
||||
.setDesc('The name of the thread')
|
||||
.addText(text => {
|
||||
.addText((text) => {
|
||||
text.setPlaceholder('Enter thread name...')
|
||||
.onChange((value) => {
|
||||
this.values.metadata.name = value;
|
||||
|
@ -54,7 +54,6 @@ export class ThreadEditModal extends Modal {
|
|||
.setValue(this.values.metadata.name);
|
||||
});
|
||||
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
metadata: yup.object().shape({
|
||||
name: yup.string().required('Name is required'),
|
||||
|
@ -63,7 +62,9 @@ export class ThreadEditModal extends Modal {
|
|||
|
||||
const checkRequiredFields = async (): Promise<string[]> => {
|
||||
try {
|
||||
await validationSchema.validate(this.values, { abortEarly: false });
|
||||
await validationSchema.validate(this.values, {
|
||||
abortEarly: false,
|
||||
});
|
||||
return [];
|
||||
} catch (error) {
|
||||
if (error instanceof yup.ValidationError) {
|
||||
|
@ -93,6 +94,5 @@ export class ThreadEditModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
}
|
||||
onClose() {}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
||||
|
||||
import { App, TAbstractFile, TFile } from "obsidian";
|
||||
import { TextInputSuggest } from "./suggest";
|
||||
import { get_tfiles_from_folder } from "@/utils/utils";
|
||||
import { App, TAbstractFile, TFile } from 'obsidian';
|
||||
import { TextInputSuggest } from './suggest';
|
||||
import { get_tfiles_from_folder } from '@/utils/utils';
|
||||
|
||||
export class FileSuggest extends TextInputSuggest<TFile> {
|
||||
private onSelect: (file: string) => void;
|
||||
|
@ -10,8 +10,7 @@ export class FileSuggest extends TextInputSuggest<TFile> {
|
|||
constructor(
|
||||
app: App,
|
||||
public inputEl: HTMLInputElement,
|
||||
onSelect: (file: string) => void
|
||||
|
||||
onSelect: (file: string) => void,
|
||||
) {
|
||||
super(app, inputEl);
|
||||
this.onSelect = onSelect;
|
||||
|
@ -30,7 +29,7 @@ export class FileSuggest extends TextInputSuggest<TFile> {
|
|||
all_files.forEach((file: TAbstractFile) => {
|
||||
if (
|
||||
file instanceof TFile &&
|
||||
file.extension === "md" &&
|
||||
file.extension === 'md' &&
|
||||
file.path.toLowerCase().contains(lower_input_str)
|
||||
) {
|
||||
files.push(file);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
||||
|
||||
import { ISuggestOwner, Scope } from "obsidian";
|
||||
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
|
||||
import { App } from "obsidian";
|
||||
import { ISuggestOwner, Scope } from 'obsidian';
|
||||
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
|
||||
import { App } from 'obsidian';
|
||||
|
||||
const wrapAround = (value: number, size: number): number => {
|
||||
return ((value % size) + size) % size;
|
||||
|
@ -18,37 +18,37 @@ class Suggest<T> {
|
|||
constructor(
|
||||
owner: ISuggestOwner<T>,
|
||||
containerEl: HTMLElement,
|
||||
scope: Scope
|
||||
scope: Scope,
|
||||
) {
|
||||
this.owner = owner;
|
||||
this.containerEl = containerEl;
|
||||
|
||||
containerEl.on(
|
||||
"click",
|
||||
".suggestion-item",
|
||||
this.onSuggestionClick.bind(this)
|
||||
'click',
|
||||
'.suggestion-item',
|
||||
this.onSuggestionClick.bind(this),
|
||||
);
|
||||
containerEl.on(
|
||||
"mousemove",
|
||||
".suggestion-item",
|
||||
this.onSuggestionMouseover.bind(this)
|
||||
'mousemove',
|
||||
'.suggestion-item',
|
||||
this.onSuggestionMouseover.bind(this),
|
||||
);
|
||||
|
||||
scope.register([], "ArrowUp", (event) => {
|
||||
scope.register([], 'ArrowUp', (event) => {
|
||||
if (!event.isComposing) {
|
||||
this.setSelectedItem(this.selectedItem - 1, true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
scope.register([], "ArrowDown", (event) => {
|
||||
scope.register([], 'ArrowDown', (event) => {
|
||||
if (!event.isComposing) {
|
||||
this.setSelectedItem(this.selectedItem + 1, true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
scope.register([], "Enter", (event) => {
|
||||
scope.register([], 'Enter', (event) => {
|
||||
if (!event.isComposing) {
|
||||
this.useSelectedItem(event);
|
||||
return false;
|
||||
|
@ -74,7 +74,7 @@ class Suggest<T> {
|
|||
const suggestionEls: HTMLDivElement[] = [];
|
||||
|
||||
values.forEach((value) => {
|
||||
const suggestionEl = this.containerEl.createDiv("suggestion-item");
|
||||
const suggestionEl = this.containerEl.createDiv('suggestion-item');
|
||||
this.owner.renderSuggestion(value, suggestionEl);
|
||||
suggestionEls.push(suggestionEl);
|
||||
});
|
||||
|
@ -94,13 +94,13 @@ class Suggest<T> {
|
|||
setSelectedItem(selectedIndex: number, scrollIntoView: boolean) {
|
||||
const normalizedIndex = wrapAround(
|
||||
selectedIndex,
|
||||
this.suggestions.length
|
||||
this.suggestions.length,
|
||||
);
|
||||
const prevSelectedSuggestion = this.suggestions[this.selectedItem];
|
||||
const selectedSuggestion = this.suggestions[normalizedIndex];
|
||||
|
||||
prevSelectedSuggestion?.removeClass("is-selected");
|
||||
selectedSuggestion?.addClass("is-selected");
|
||||
prevSelectedSuggestion?.removeClass('is-selected');
|
||||
selectedSuggestion?.addClass('is-selected');
|
||||
|
||||
this.selectedItem = normalizedIndex;
|
||||
|
||||
|
@ -124,21 +124,21 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
|||
this.inputEl = inputEl;
|
||||
this.scope = new Scope();
|
||||
|
||||
this.suggestEl = createDiv("suggestion-container");
|
||||
const suggestion = this.suggestEl.createDiv("suggestion");
|
||||
this.suggestEl = createDiv('suggestion-container');
|
||||
const suggestion = this.suggestEl.createDiv('suggestion');
|
||||
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("focus", this.onInputChanged.bind(this));
|
||||
this.inputEl.addEventListener("blur", this.close.bind(this));
|
||||
this.inputEl.addEventListener('input', this.onInputChanged.bind(this));
|
||||
this.inputEl.addEventListener('focus', this.onInputChanged.bind(this));
|
||||
this.inputEl.addEventListener('blur', this.close.bind(this));
|
||||
this.suggestEl.on(
|
||||
"mousedown",
|
||||
".suggestion-container",
|
||||
'mousedown',
|
||||
'.suggestion-container',
|
||||
(event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -166,10 +166,10 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
|||
|
||||
container.appendChild(this.suggestEl);
|
||||
this.popper = createPopper(inputEl, this.suggestEl, {
|
||||
placement: "bottom-start",
|
||||
placement: 'bottom-start',
|
||||
modifiers: [
|
||||
{
|
||||
name: "sameWidth",
|
||||
name: 'sameWidth',
|
||||
enabled: true,
|
||||
fn: ({ state, instance }) => {
|
||||
// Note: positioning needs to be calculated twice -
|
||||
|
@ -183,8 +183,8 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
|||
state.styles.popper.width = targetWidth;
|
||||
instance.update();
|
||||
},
|
||||
phase: "beforeWrite",
|
||||
requires: ["computeStyles"],
|
||||
phase: 'beforeWrite',
|
||||
requires: ['computeStyles'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import OpenAI from "openai";
|
||||
import OpenAI from 'openai';
|
||||
export interface IThread extends OpenAI.Beta.Thread {
|
||||
metadata: {
|
||||
name: string;
|
||||
annotationFiles?: ThreadAnnotationFile[];
|
||||
}
|
||||
metadata: {
|
||||
name: string;
|
||||
annotationFiles?: ThreadAnnotationFile[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ThreadAnnotationFile {
|
||||
fileName: string;
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
fileId: string;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Notice } from "obsidian";
|
||||
import { Notice } from 'obsidian';
|
||||
|
||||
export const createNotice = (message: string, timeout = 5000): void => {
|
||||
new Notice(`Obsidian Intelligence: ${message}`, timeout);
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { log_error } from "./log";
|
||||
import { log_error } from './log';
|
||||
|
||||
export class TemplaterError extends Error {
|
||||
constructor(msg: string, public console_msg?: string) {
|
||||
constructor(
|
||||
msg: string,
|
||||
public console_msg?: string,
|
||||
) {
|
||||
super(msg);
|
||||
this.name = this.constructor.name;
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
|
@ -10,7 +13,7 @@ export class TemplaterError extends Error {
|
|||
|
||||
export async function errorWrapper<T>(
|
||||
fn: () => Promise<T>,
|
||||
msg: string
|
||||
msg: string,
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await fn();
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { Notice } from "obsidian";
|
||||
import { TemplaterError } from "./error";
|
||||
import { Notice } from 'obsidian';
|
||||
import { TemplaterError } from './error';
|
||||
|
||||
export function log_update(msg: string): void {
|
||||
const notice = new Notice("", 15000);
|
||||
const notice = new Notice('', 15000);
|
||||
// TODO: Find better way for this
|
||||
// @ts-ignore
|
||||
notice.noticeEl.innerHTML = `<b>obsidian-agents update</b>:<br/>${msg}`;
|
||||
}
|
||||
|
||||
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) {
|
||||
// TODO: Find a better way for this
|
||||
// @ts-ignore
|
||||
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 {
|
||||
// @ts-ignore
|
||||
notice.noticeEl.innerHTML = `<b>obsidian-agents Error</b>:<br/>${e.message}`;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TemplaterError } from "./error";
|
||||
import { TemplaterError } from './error';
|
||||
import {
|
||||
App,
|
||||
normalizePath,
|
||||
|
@ -6,14 +6,14 @@ import {
|
|||
TFile,
|
||||
TFolder,
|
||||
Vault,
|
||||
} from "obsidian";
|
||||
} from 'obsidian';
|
||||
|
||||
export function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -52,13 +52,19 @@ export function resolve_tfile(file_str: string): TFile {
|
|||
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 files: Array<TFile> = [];
|
||||
Vault.recurseChildren(folder, (file: TAbstractFile) => {
|
||||
|
||||
if (file instanceof TFile && (extension && file.extension === extension)) {
|
||||
if (
|
||||
file instanceof TFile &&
|
||||
extension &&
|
||||
file.extension === extension
|
||||
) {
|
||||
files.push(file);
|
||||
}
|
||||
});
|
||||
|
@ -73,7 +79,7 @@ export function get_tfiles_from_folder(folder_str: string, extension?: string):
|
|||
export function arraymove<T>(
|
||||
arr: T[],
|
||||
fromIndex: number,
|
||||
toIndex: number
|
||||
toIndex: number,
|
||||
): void {
|
||||
if (toIndex < 0 || toIndex === arr.length) {
|
||||
return;
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
.scroll-to-bottom-button {
|
||||
|
@ -88,7 +87,6 @@
|
|||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
.copy-icon-container {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
@ -125,7 +123,6 @@
|
|||
width: 15%;
|
||||
}
|
||||
|
||||
|
||||
.collapsible-container {
|
||||
width: 100%;
|
||||
max-height: 25%;
|
||||
|
@ -160,7 +157,6 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
.files-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -181,7 +177,6 @@
|
|||
gap: 4px;
|
||||
}
|
||||
|
||||
|
||||
/* AssistantManager.tsx */
|
||||
|
||||
.chat-top-section-container {
|
||||
|
|
|
@ -1,30 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"src/ui/AppView.tsx"
|
||||
]
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"strictNullChecks": true,
|
||||
"lib": ["DOM", "ES5", "ES6", "ES7"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "src/ui/AppView.tsx"]
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
|
||||
const targetVersion = process.env.npm_package_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;
|
||||
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
|
||||
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
|
||||
let versions = JSON.parse(readFileSync('versions.json', 'utf8'));
|
||||
versions[targetVersion] = minAppVersion;
|
||||
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
|
||||
writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"1.0.0": "0.15.0"
|
||||
"1.0.0": "0.15.0"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue