mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Implement spike work for plugin.
This commit is contained in:
parent
64831546f2
commit
0fb3e4632f
38 changed files with 5095 additions and 40 deletions
15
AIClasses/AIMemory.ts
Normal file
15
AIClasses/AIMemory.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
export class AIMemory {
|
||||
// private chatHistory: Part[][] = [];
|
||||
|
||||
// public addChat(userText: string, aiText: Part[]) {
|
||||
// this.chatHistory.push([
|
||||
// { text: userText },
|
||||
// ...aiText
|
||||
// ]);
|
||||
// }
|
||||
|
||||
// public getChatHistory(): Part[][] {
|
||||
// return this.chatHistory;
|
||||
// }
|
||||
|
||||
}
|
||||
76
AIClasses/Gemini/Gemini.ts
Normal file
76
AIClasses/Gemini/Gemini.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { AIProviderURL } from "Enums/ApiProvider";
|
||||
import { isValidJson } from "Helpers";
|
||||
import { request, type RequestUrlParam } from "obsidian";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import type { IActioner } from "Actioner/IActioner";
|
||||
import type { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefinitions";
|
||||
import { create_file } from "Actioner/Actions";
|
||||
import type { IAIClass } from "AIClasses/IAIClass";
|
||||
import type { IPrompt } from "AIClasses/IPrompt";
|
||||
|
||||
export class Gemini implements IAIClass {
|
||||
private readonly apiKey: string;
|
||||
private readonly aiPrompt: IPrompt;
|
||||
private readonly actionDefinitions: GeminiActionDefinitions;
|
||||
|
||||
|
||||
public constructor(apiKey: string) {
|
||||
this.apiKey = apiKey;
|
||||
|
||||
this.aiPrompt = Resolve(Services.IPrompt);
|
||||
this.actionDefinitions = Resolve(Services.IActionDefinitions);
|
||||
}
|
||||
|
||||
public async apiRequest(userInput: string, actioner: IActioner): Promise<Part[] | null> { //AIResponse
|
||||
let prompt: string = "The users prompt is: " + userInput;
|
||||
|
||||
let requestBody = JSON.stringify({
|
||||
contents: [
|
||||
{
|
||||
role: "user",
|
||||
parts: [
|
||||
{
|
||||
text: this.aiPrompt.instructions() + "\n" +
|
||||
this.aiPrompt.responseFormat() + "\n" +
|
||||
this.aiPrompt.getDirectories() + "\n" +
|
||||
prompt + "\n" +
|
||||
this.aiPrompt.instructionsReminder()
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
tools: [{
|
||||
functionDeclarations: [this.actionDefinitions[create_file]]
|
||||
}]
|
||||
});
|
||||
|
||||
let reqParam: RequestUrlParam = {
|
||||
url: AIProviderURL.Gemini,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-goog-api-key": this.apiKey,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: requestBody
|
||||
};
|
||||
|
||||
let response: GeminiApiResponse = JSON.parse(await request(reqParam));
|
||||
|
||||
console.log(response);
|
||||
|
||||
//TODO: tidy up this
|
||||
let ai_response: Part[] = response.candidates[0]?.content.parts ?? "{}";
|
||||
|
||||
// if (isValidJson(ai_response)) {
|
||||
// return JSON.parse(ai_response);
|
||||
// }
|
||||
|
||||
console.log(ai_response);
|
||||
|
||||
return ai_response;
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
5
AIClasses/IAIClass.ts
Normal file
5
AIClasses/IAIClass.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import type { IActioner } from "Actioner/IActioner";
|
||||
|
||||
export interface IAIClass {
|
||||
apiRequest(req: string, actioner: IActioner): Promise<Part[] | null>;
|
||||
}
|
||||
52
AIClasses/IPrompt.ts
Normal file
52
AIClasses/IPrompt.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import type DmsAssistantPlugin from "main";
|
||||
import type { Vault } from "obsidian";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
|
||||
export interface IPrompt {
|
||||
getDirectories(): string;
|
||||
instructions(): string;
|
||||
instructionsReminder(): string;
|
||||
responseFormat(): string;
|
||||
}
|
||||
|
||||
export class AIPrompt implements IPrompt {
|
||||
|
||||
private vault: Vault;
|
||||
|
||||
public constructor() {
|
||||
this.vault = Resolve<DmsAssistantPlugin>(Services.DmsAssistantPlugin).app.vault;
|
||||
}
|
||||
|
||||
public getDirectories(): string {
|
||||
let directories: string[] = this.vault.getAllFolders(true).map(folder => folder.path);
|
||||
return "Available user directories:" + "\n" + directories.join("\n");
|
||||
}
|
||||
|
||||
public readonly instructionsArr: string[] = [
|
||||
"You are an AI assistant for the Obsidian note taking app.",
|
||||
//"In addition to answering questions, you can execute helpful functions which are defined below.",
|
||||
"The user has provided extra context to your responsibilities:",
|
||||
"You are a DND expert and can provide detailed information about DND rules, character creation, and gameplay mechanics. Please give concise responses."
|
||||
];
|
||||
public instructions(): string {
|
||||
return this.instructionsArr.join("\n");
|
||||
}
|
||||
|
||||
public readonly instructionsReminderArr: string[] = [
|
||||
"Ensure your response is valid JSON following the format defined above."
|
||||
]
|
||||
public instructionsReminder(): string {
|
||||
return this.instructionsReminderArr.join("\n");
|
||||
}
|
||||
|
||||
public readonly responseFormatArr: string[] = [
|
||||
"All responses that are not function calls should be in JSON parsable format. The response should not be wrapped in ```json```. The following fields should be used:",
|
||||
"user_response - the response to be delivered to the user.",
|
||||
//"function_name - a string name for the desired function or null.",
|
||||
//"function_object - an object containing the function arguments or null."
|
||||
]
|
||||
public responseFormat(): string {
|
||||
return this.responseFormatArr.join("\n");
|
||||
}
|
||||
}
|
||||
3
AIClasses/Request/AIRequest.ts
Normal file
3
AIClasses/Request/AIRequest.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export class AIRequest {
|
||||
|
||||
}
|
||||
16
AIClasses/Response/AIResponse.ts
Normal file
16
AIClasses/Response/AIResponse.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Interfaces for requested custom API responses.
|
||||
*/
|
||||
|
||||
interface AIResponse {
|
||||
function_calls: FunctionCall[];
|
||||
|
||||
// function_name: string | null;
|
||||
// function_object: object | null;
|
||||
// user_response: string;
|
||||
}
|
||||
|
||||
interface CreateFileRequest {
|
||||
file_path: string;
|
||||
file_content: string;
|
||||
}
|
||||
80
AIClasses/Response/GeminiResponse.ts
Normal file
80
AIClasses/Response/GeminiResponse.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Interface for the top-level Gemini API response.
|
||||
*/
|
||||
interface GeminiApiResponse {
|
||||
candidates: Candidate[];
|
||||
usageMetadata: UsageMetadata;
|
||||
modelVersion: string;
|
||||
responseId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a single candidate in the response.
|
||||
*/
|
||||
interface Candidate {
|
||||
content: Content;
|
||||
finishReason: string;
|
||||
index: number;
|
||||
citationMetadata: CitationMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the content of a candidate.
|
||||
*/
|
||||
interface Content {
|
||||
parts: Part[];
|
||||
role: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a single part of the content.
|
||||
* The `text` property contains a JSON string, which needs to be parsed.
|
||||
*/
|
||||
interface Part {
|
||||
text: string;
|
||||
functionCall: FunctionCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a single function call.
|
||||
*/
|
||||
interface FunctionCall {
|
||||
name: string;
|
||||
args: object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the metadata about citations.
|
||||
*/
|
||||
interface CitationMetadata {
|
||||
citationSources: CitationSource[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a single citation source.
|
||||
*/
|
||||
interface CitationSource {
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
uri: string;
|
||||
license: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the usage metadata of the API call.
|
||||
*/
|
||||
interface UsageMetadata {
|
||||
promptTokenCount: number;
|
||||
candidatesTokenCount: number;
|
||||
totalTokenCount: number;
|
||||
promptTokensDetails: PromptTokensDetails[];
|
||||
thoughtsTokenCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the details of prompt tokens.
|
||||
*/
|
||||
interface PromptTokensDetails {
|
||||
modality: string;
|
||||
tokenCount: number;
|
||||
}
|
||||
21
Actioner/Actioner.ts
Normal file
21
Actioner/Actioner.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { createDirectories } from "Helpers";
|
||||
import { create_file } from "./Actions";
|
||||
import type { IActioner } from "./IActioner";
|
||||
import type { Vault } from "obsidian";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import type DmsAssistantPlugin from "main";
|
||||
|
||||
export class Actioner implements IActioner {
|
||||
|
||||
private vault: Vault;
|
||||
|
||||
public constructor() {
|
||||
this.vault = Resolve<DmsAssistantPlugin>(Services.DmsAssistantPlugin).app.vault;
|
||||
}
|
||||
|
||||
public async [create_file](action: CreateFileRequest) {
|
||||
await createDirectories(this.vault, action.file_path);
|
||||
await this.vault.create(action.file_path, JSON.stringify(action.file_content, null, 4));
|
||||
}
|
||||
}
|
||||
9
Actioner/Actions.ts
Normal file
9
Actioner/Actions.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
const request_directories: symbol = Symbol.for("request_directories");
|
||||
const request_contents: symbol = Symbol.for("request_contents");
|
||||
const create_schema: symbol = Symbol.for("create_schema");
|
||||
const create_file: symbol = Symbol.for("create_file");
|
||||
const delete_file: symbol = Symbol.for("delete_file");
|
||||
const edit_file: symbol = Symbol.for("edit_file");
|
||||
const rename_file: symbol = Symbol.for("rename_file");
|
||||
|
||||
export { request_directories, request_contents, create_schema, create_file, delete_file, edit_file, rename_file };
|
||||
131
Actioner/Gemini/GeminiActionDefinitions.ts
Normal file
131
Actioner/Gemini/GeminiActionDefinitions.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { Type } from "@google/genai";
|
||||
import { create_file, create_schema, delete_file, edit_file, rename_file, request_contents, request_directories } from "Actioner/Actions";
|
||||
import type { IActionDefinitions } from "Actioner/IActionDefinitions";
|
||||
|
||||
export class GeminiActionDefinitions implements IActionDefinitions {
|
||||
public [request_directories](): object {
|
||||
return {
|
||||
name: "request_directories",
|
||||
description: "Request the available user directories. Call this for further available functions",
|
||||
parameters: {}
|
||||
};
|
||||
}
|
||||
|
||||
public [request_contents](): object {
|
||||
return {
|
||||
name: "request_contents",
|
||||
description: "Request the contents of a file. The contents will be added using the Files API",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the file to be requested"
|
||||
}
|
||||
},
|
||||
requited: ["file_path"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [create_schema](): object {
|
||||
return {
|
||||
name: "create_schema",
|
||||
description: "Create a new data definition schema for the user",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the schema to be created"
|
||||
},
|
||||
schema_content: {
|
||||
type: Type.OBJECT,
|
||||
description: "The schema definition where each field is the property name and each value is the default property value. Nested properties are accepted"
|
||||
}
|
||||
},
|
||||
required: ["file_path", "file_content"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [create_file](): object {
|
||||
return {
|
||||
name: "create_file",
|
||||
description: "Create a new file for the user",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the file to be created"
|
||||
},
|
||||
file_content: {
|
||||
type: Type.STRING,
|
||||
description: "The content of the file to be created"
|
||||
}
|
||||
},
|
||||
required: ["file_path", "file_content"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [delete_file](): object {
|
||||
return {
|
||||
name: "delete_file",
|
||||
description: "Request a file to be deleted",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the file to be deleted"
|
||||
}
|
||||
},
|
||||
requited: ["file_path"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [edit_file](): object {
|
||||
return {
|
||||
name: "edit_file",
|
||||
description: "Edit the contents of an existing file",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the file to be edited"
|
||||
},
|
||||
file_content: {
|
||||
type: Type.STRING,
|
||||
description: "The new content of the file to be written"
|
||||
}
|
||||
},
|
||||
requited: ["file_path, file_content"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [rename_file](): object {
|
||||
return {
|
||||
name: "rename_file",
|
||||
description: "Rename or move a file",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
old_file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The old file path of the file"
|
||||
},
|
||||
new_file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The new file path of the file"
|
||||
}
|
||||
},
|
||||
requited: ["old_file_path", "new_file_path"]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
11
Actioner/IActionDefinitions.ts
Normal file
11
Actioner/IActionDefinitions.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import type { create_file, create_schema, delete_file, edit_file, rename_file, request_contents, request_directories } from "./Actions";
|
||||
|
||||
export interface IActionDefinitions {
|
||||
[request_directories](): object;
|
||||
[request_contents](): object;
|
||||
[create_schema](): object;
|
||||
[create_file](): object;
|
||||
[delete_file](): object;
|
||||
[edit_file](): object;
|
||||
[rename_file](): object;
|
||||
}
|
||||
3
Actioner/IActioner.ts
Normal file
3
Actioner/IActioner.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export interface IActioner {
|
||||
[key: symbol]: (...args: any[]) => any;
|
||||
}
|
||||
0
Components/ChatWindow.svelte
Normal file
0
Components/ChatWindow.svelte
Normal file
77
Components/Input.svelte
Normal file
77
Components/Input.svelte
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<script lang="ts">
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import type { IActioner } from "Actioner/IActioner";
|
||||
import type { IAIClass } from "AIClasses/IAIClass";
|
||||
|
||||
interface Props {
|
||||
input: string;
|
||||
}
|
||||
|
||||
let {
|
||||
input
|
||||
}: Props = $props();
|
||||
|
||||
let output: string = $state("");
|
||||
|
||||
async function submit() {
|
||||
let aiClass: IAIClass = Resolve(Services.IAIClass)
|
||||
let actioner: IActioner = Resolve(Services.IActioner);
|
||||
|
||||
let aiResponse: Part[] | null = await aiClass.apiRequest(input, actioner);
|
||||
|
||||
if (aiResponse == null) {
|
||||
throw "Response was invalid JSON";
|
||||
}
|
||||
|
||||
for (let part of aiResponse) {
|
||||
if (part.functionCall) {
|
||||
let functionName: string = part.functionCall.name;
|
||||
let functionObject: object = part.functionCall.args;
|
||||
|
||||
await actioner[Symbol.for(functionName)](functionObject);
|
||||
};
|
||||
}
|
||||
|
||||
output = `Done: ${aiResponse}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<input
|
||||
type="string"
|
||||
bind:value={input}
|
||||
placeholder="Enter a prompt"
|
||||
aria-label="Enter a prompt"
|
||||
/>
|
||||
<button onclick={submit}>Submit</button>
|
||||
|
||||
<p>{output}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
background-color: hotpink;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0.4rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #0066ff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.4rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #0055dd;
|
||||
}
|
||||
</style>
|
||||
8
Enums/ApiProvider.ts
Normal file
8
Enums/ApiProvider.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export enum AIProvider {
|
||||
Gemini = "Gemini",
|
||||
OpenAI = "OpenAI"
|
||||
};
|
||||
|
||||
export enum AIProviderURL {
|
||||
Gemini = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"
|
||||
}
|
||||
4
Enums/DynamicRecord.ts
Normal file
4
Enums/DynamicRecord.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export enum DynamicRecordProp {
|
||||
Type = "type",
|
||||
ObjectId = "object_id"
|
||||
}
|
||||
6
Enums/FileAction.ts
Normal file
6
Enums/FileAction.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export enum FileAction {
|
||||
Create = "create",
|
||||
Modify = "modify",
|
||||
Delete = "delete",
|
||||
Rename = "rename"
|
||||
}
|
||||
5
Enums/Path.ts
Normal file
5
Enums/Path.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export enum Path {
|
||||
Root = "DM's Assistant",
|
||||
Schemas = "DM's Assistant/Schemas",
|
||||
Records = "DM's Assistant/Records"
|
||||
};
|
||||
26
Helpers.ts
Normal file
26
Helpers.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { TFile, TFolder, type Vault } from "obsidian";
|
||||
|
||||
export function isValidJson(str: string): boolean {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function createDirectories(vault: Vault, filePath: string) {
|
||||
const dirPath: string = filePath.substring(0, filePath.lastIndexOf('/'));
|
||||
|
||||
const dirs: string[] = dirPath.split('/');
|
||||
|
||||
let currentPath = "";
|
||||
for (const dir of dirs) {
|
||||
if (dir) {
|
||||
currentPath = currentPath ? `${currentPath}/${dir}` : dir;
|
||||
if (vault.getAbstractFileByPath(currentPath) == null) {
|
||||
await vault.createFolder(currentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Modals/Modals.ts
Normal file
3
Modals/Modals.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export class Modals {
|
||||
static SimpleModal = Symbol("SimpleModal");
|
||||
}
|
||||
17
Modals/SimpleModal.ts
Normal file
17
Modals/SimpleModal.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { Modal, App } from "obsidian";
|
||||
|
||||
class SimpleModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.setText('Woah!');
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
157
ODB/Core/DynamicRecord.ts
Normal file
157
ODB/Core/DynamicRecord.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import { DynamicRecordProp } from 'Enums/DynamicRecord';
|
||||
import { version as uuidVersion } from 'uuid';
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
import type { OdbCache } from './OdbCache';
|
||||
import { Resolve } from 'Services/DependencyService';
|
||||
import { Services } from 'Services/Services';
|
||||
import type DmsAssistantPlugin from 'main';
|
||||
import { TAbstractFile, TFile, type Vault } from 'obsidian';
|
||||
|
||||
interface DynamicProps {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class DynamicRecord {
|
||||
|
||||
public readonly type: string;
|
||||
public readonly schema: object;
|
||||
public readonly objectId: string;
|
||||
public readonly recordPath: string;
|
||||
|
||||
public props: DynamicProps;
|
||||
|
||||
private vault: Vault;
|
||||
private odbCache: OdbCache;
|
||||
|
||||
public constructor(recordPath: string, schema: object, record: { [key: string]: any }) {
|
||||
this.odbCache = Resolve<OdbCache>(Services.OdbCache);
|
||||
this.vault = Resolve<DmsAssistantPlugin>(Services.DmsAssistantPlugin).app.vault;
|
||||
|
||||
this.schema = schema;
|
||||
this.recordPath = recordPath;
|
||||
|
||||
if (record[DynamicRecordProp.Type] === undefined || record[DynamicRecordProp.ObjectId] === undefined) {
|
||||
throw new Error(`Invalid record format: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`);
|
||||
}
|
||||
|
||||
this.type = record[DynamicRecordProp.Type];
|
||||
this.objectId = record[DynamicRecordProp.ObjectId];
|
||||
|
||||
let parsedRecord: Record<string,any> = {};
|
||||
for (let key in schema) {
|
||||
if (!(key in record) || key === DynamicRecordProp.Type || key === DynamicRecordProp.ObjectId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedRecord[key] = this.parse(record[key]);
|
||||
}
|
||||
|
||||
this.props = this.createProxy(parsedRecord);
|
||||
}
|
||||
|
||||
public save() {
|
||||
let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath);
|
||||
|
||||
if (file == null || !(file instanceof TAbstractFile)) {
|
||||
this.vault.create(this.recordPath, this.toFileContent());
|
||||
}
|
||||
|
||||
this.vault.modify(file as TFile, this.toFileContent());
|
||||
}
|
||||
|
||||
public delete() {
|
||||
let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath);
|
||||
|
||||
if (file == null || !(file instanceof TAbstractFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.vault.delete(file as TFile);
|
||||
}
|
||||
|
||||
public move(newPath: string) {
|
||||
let file: TAbstractFile | null = this.vault.getAbstractFileByPath(this.recordPath);
|
||||
|
||||
if (file == null || !(file instanceof TAbstractFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.vault.rename(file as TFile, newPath);
|
||||
}
|
||||
|
||||
private parse(data: any): any {
|
||||
if (typeof data === null) {
|
||||
return data;
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
let arr: any[] = [];
|
||||
for (let item of data) {
|
||||
arr.push(this.parse(item));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
if (typeof data === "object") {
|
||||
let obj: Record<string,any> = {};
|
||||
for (let key in data) {
|
||||
obj[key] = this.parse(data[key]);
|
||||
}
|
||||
return this.createProxy(obj);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private createProxy(data: object): DynamicProps {
|
||||
return new Proxy(data, {
|
||||
get: (target, prop, receiver) => {
|
||||
if (this.isObjectId(String(prop))) {
|
||||
return this.odbCache.getRecord(String(prop));
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
set: (target, prop, value, receiver) => {
|
||||
return Reflect.set(target, prop, value, receiver);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private isObjectId(key: string): boolean {
|
||||
return uuidValidate(key) && uuidVersion(key) === 4;
|
||||
}
|
||||
|
||||
private toFileContent(): string {
|
||||
let content: Record<string,any> = {};
|
||||
|
||||
content[DynamicRecordProp.Type] = this.type;
|
||||
content[DynamicRecordProp.ObjectId] = this.objectId;
|
||||
|
||||
// use the schema when saving to file in case the AI did something funky
|
||||
for (let key in this.schema) {
|
||||
if (!(key in this.props) || key === DynamicRecordProp.Type || key === DynamicRecordProp.ObjectId) {
|
||||
continue;
|
||||
}
|
||||
content[key] = this.serialise(this.props[key]);
|
||||
}
|
||||
return JSON.stringify(content, null, 4);
|
||||
}
|
||||
|
||||
private serialise(value: any): any {
|
||||
if (value instanceof DynamicRecord) {
|
||||
return value.objectId;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
let arr: any[] = [];
|
||||
for (let item of value) {
|
||||
arr.push(this.serialise(item));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
let obj: Record<string,any> = {};
|
||||
for (let key in value) {
|
||||
obj[key] = this.serialise(value[key]);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
131
ODB/Core/OdbCache.ts
Normal file
131
ODB/Core/OdbCache.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { TAbstractFile, TFile, TFolder, Vault } from "obsidian";
|
||||
import { DynamicRecord } from "./DynamicRecord";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import type DmsAssistantPlugin from "main";
|
||||
import { Services } from "Services/Services";
|
||||
import { Path } from "Enums/Path";
|
||||
import { DynamicRecordProp } from "Enums/DynamicRecord";
|
||||
import { FileAction } from "Enums/FileAction";
|
||||
import { isValidJson } from "Helpers";
|
||||
|
||||
export class OdbCache {
|
||||
|
||||
private vault: Vault;
|
||||
|
||||
private schemas: Map<string, object> = new Map<string, object>();
|
||||
private cache: Map<string, DynamicRecord> = new Map<string, DynamicRecord>();
|
||||
|
||||
public constructor() {
|
||||
this.vault = Resolve<DmsAssistantPlugin>(Services.DmsAssistantPlugin).app.vault;
|
||||
}
|
||||
|
||||
public getSchemas(): Map<string, object> {
|
||||
return this.schemas;
|
||||
}
|
||||
|
||||
public getRecord(objectId: string): DynamicRecord | null {
|
||||
return this.cache.get(objectId) ?? null;
|
||||
}
|
||||
|
||||
public async buildCache() {
|
||||
await this.loadSchemas();
|
||||
|
||||
let recordDir: TAbstractFile | null = this.vault.getAbstractFileByPath(Path.Records);
|
||||
|
||||
if (!(recordDir instanceof TFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.recursiveBuild(recordDir);
|
||||
}
|
||||
|
||||
public async onFileChanged(file: TAbstractFile, fileAction: FileAction) {
|
||||
if (file instanceof TFolder) {
|
||||
for (let child of file.children) {
|
||||
this.onFileChanged(child, fileAction);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let [_, record] of this.cache) {
|
||||
if (record.recordPath === file.path) {
|
||||
switch (fileAction) {
|
||||
case FileAction.Create:
|
||||
this.addToCache(file as TFile);
|
||||
break;
|
||||
case FileAction.Modify, FileAction.Rename:
|
||||
this.removeFromCache(record.objectId);
|
||||
this.addToCache(file as TFile);
|
||||
break;
|
||||
case FileAction.Delete:
|
||||
this.removeFromCache(record.objectId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async recursiveBuild(child : TAbstractFile) {
|
||||
if (child instanceof TFolder) {
|
||||
for (let subChild of child.children) {
|
||||
await this.recursiveBuild(subChild);
|
||||
}
|
||||
} else if (child instanceof TFile && child.extension === "json") {
|
||||
await this.addToCache(child);
|
||||
}
|
||||
}
|
||||
|
||||
private async createDynamicRecord(file: TFile): Promise<DynamicRecord> {
|
||||
let contents: { [key: string]: any } = JSON.parse(await this.vault.read(file));
|
||||
|
||||
if (contents[DynamicRecordProp.Type] === undefined || contents[DynamicRecordProp.ObjectId] === undefined) {
|
||||
throw new Error(`Invalid record format in file ${file.path}: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`);
|
||||
}
|
||||
|
||||
let schema: object | undefined = this.schemas.get(contents[DynamicRecordProp.Type]);
|
||||
|
||||
if (schema === undefined) {
|
||||
throw new Error(`No schema found for record type ${contents[DynamicRecordProp.Type]} in file ${file.path}`);
|
||||
}
|
||||
|
||||
return new DynamicRecord(file.path, schema, contents);
|
||||
}
|
||||
|
||||
private async loadSchemas() {
|
||||
let schemaDir: TAbstractFile | null = this.vault.getAbstractFileByPath(Path.Schemas);
|
||||
|
||||
if (!(schemaDir instanceof TFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let child of schemaDir.children) {
|
||||
if (!(child instanceof TFile) || child.extension !== "json") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let contents: string = await this.vault.read(child);
|
||||
|
||||
if (!isValidJson(contents)) {
|
||||
console.warn(`Invalid schema format in file ${child.path}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let schema: Record<string,any> = JSON.parse(contents);
|
||||
|
||||
if (schema[DynamicRecordProp.Type] === undefined || schema[DynamicRecordProp.ObjectId] === undefined) {
|
||||
throw new Error(`Invalid schema format in file ${child.path}: missing ${DynamicRecordProp.Type} or ${DynamicRecordProp.ObjectId}`);
|
||||
}
|
||||
|
||||
this.schemas.set(schema[DynamicRecordProp.Type], schema);
|
||||
}
|
||||
}
|
||||
|
||||
private async addToCache(file: TFile) {
|
||||
let record: DynamicRecord = await this.createDynamicRecord(file);
|
||||
this.cache.set(record.objectId, record);
|
||||
}
|
||||
|
||||
private async removeFromCache(objectId: string) {
|
||||
this.cache.delete(objectId);
|
||||
}
|
||||
}
|
||||
24
Services/DependencyService.ts
Normal file
24
Services/DependencyService.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const services = new Map<symbol, any>();
|
||||
|
||||
export function RegisterSingleton<T>(type: symbol, instance: T): void {
|
||||
services.set(type, instance);
|
||||
}
|
||||
|
||||
export function RegisterTransient<T>(type: symbol, factory: () => T): void {
|
||||
services.set(type, factory);
|
||||
}
|
||||
|
||||
export function Resolve<T>(type: symbol): T {
|
||||
const service = services.get(type);
|
||||
if (!service) {
|
||||
throw new Error(`Service not found for type: ${type.description}`);
|
||||
}
|
||||
|
||||
if (typeof service === 'function') {
|
||||
// It's a transient factory, return a new instance
|
||||
return service();
|
||||
}
|
||||
|
||||
// It's a singleton, return the existing instance
|
||||
return service as T;
|
||||
}
|
||||
14
Services/ModalService.ts
Normal file
14
Services/ModalService.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { Modal } from "obsidian";
|
||||
import { Resolve } from "./DependencyService";
|
||||
|
||||
interface IModalService {
|
||||
showModal(modal: symbol): void;
|
||||
}
|
||||
|
||||
export class ModalService implements IModalService {
|
||||
|
||||
showModal(modal: symbol): void {
|
||||
let modalInstance: Modal = Resolve(modal);
|
||||
modalInstance.open();
|
||||
}
|
||||
}
|
||||
31
Services/ServiceRegistration.ts
Normal file
31
Services/ServiceRegistration.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { AIProvider } from "Enums/ApiProvider";
|
||||
import type DmsAssistantPlugin from "main";
|
||||
import { OdbCache } from "ODB/Core/OdbCache";
|
||||
import { RegisterSingleton, RegisterTransient } from "./DependencyService";
|
||||
import { ModalService } from "./ModalService";
|
||||
import { Services } from "./Services";
|
||||
import { AIPrompt, type IPrompt } from "AIClasses/IPrompt";
|
||||
import { Actioner } from "Actioner/Actioner";
|
||||
import type { IActioner } from "Actioner/IActioner";
|
||||
import type { IAIClass } from "AIClasses/IAIClass";
|
||||
import { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefinitions";
|
||||
import type { IActionDefinitions } from "Actioner/IActionDefinitions";
|
||||
import { Gemini } from "AIClasses/Gemini/Gemini";
|
||||
|
||||
export function RegisterDependencies(plugin: DmsAssistantPlugin) {
|
||||
RegisterSingleton(Services.DmsAssistantPlugin, plugin);
|
||||
RegisterSingleton(Services.OdbCache, new OdbCache());
|
||||
RegisterSingleton(Services.ModalService, new ModalService())
|
||||
|
||||
RegisterSingleton<IPrompt>(Services.IPrompt, new AIPrompt());
|
||||
RegisterSingleton<IActioner>(Services.IActioner, new Actioner())
|
||||
|
||||
RegisterAiProvider(plugin);
|
||||
}
|
||||
|
||||
export function RegisterAiProvider(plugin: DmsAssistantPlugin) {
|
||||
if (plugin.settings.apiProvider == AIProvider.Gemini && plugin.settings.apiKey != "") {
|
||||
RegisterTransient<IActionDefinitions>(Services.IActionDefinitions, () => new GeminiActionDefinitions());
|
||||
RegisterSingleton<IAIClass>(Services.IAIClass, new Gemini(plugin.settings.apiKey));
|
||||
}
|
||||
}
|
||||
11
Services/Services.ts
Normal file
11
Services/Services.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export class Services {
|
||||
static DmsAssistantPlugin = Symbol("DmsAssistantPlugin");
|
||||
static OdbCache = Symbol("OdbCache");
|
||||
static ModalService = Symbol("ModalService");
|
||||
|
||||
// interfaces
|
||||
static IAIClass = Symbol("IAIClass");
|
||||
static IPrompt = Symbol("IPrompt");
|
||||
static IActioner = Symbol("IActioner");
|
||||
static IActionDefinitions = Symbol("IActionDefinitions");
|
||||
}
|
||||
0
Views/App.svelte
Normal file
0
Views/App.svelte
Normal file
0
Views/ChatHistory.svelte
Normal file
0
Views/ChatHistory.svelte
Normal file
0
Views/Input.svelte
Normal file
0
Views/Input.svelte
Normal file
40
Views/MainView.ts
Normal file
40
Views/MainView.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||
|
||||
import Input from '../Components/Input.svelte';
|
||||
import { mount, unmount } from 'svelte';
|
||||
|
||||
export const VIEW_TYPE_MAIN = 'main-view';
|
||||
|
||||
export class MainView extends ItemView {
|
||||
constructor(leaf: WorkspaceLeaf) {
|
||||
super(leaf);
|
||||
}
|
||||
|
||||
input: ReturnType<typeof Input> | undefined;
|
||||
|
||||
getViewType() {
|
||||
return VIEW_TYPE_MAIN;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return 'Main View';
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
const container = this.contentEl;
|
||||
container.empty();
|
||||
|
||||
this.input = mount(Input, {
|
||||
target: container,
|
||||
props: {
|
||||
input: "",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
if (this.input) {
|
||||
unmount(this.input);
|
||||
}
|
||||
}
|
||||
}
|
||||
0
Views/viewStore.ts
Normal file
0
Views/viewStore.ts
Normal file
|
|
@ -2,6 +2,9 @@ import esbuild from "esbuild";
|
|||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
|
||||
import esbuildSvelte from 'esbuild-svelte';
|
||||
import { sveltePreprocess } from 'svelte-preprocess';
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
|
|
@ -12,6 +15,12 @@ if you want to view the source, please visit the github repository of this plugi
|
|||
const prod = (process.argv[2] === "production");
|
||||
|
||||
const context = await esbuild.context({
|
||||
plugins: [
|
||||
esbuildSvelte({
|
||||
compilerOptions: { css: 'injected' },
|
||||
preprocess: sveltePreprocess(),
|
||||
}),
|
||||
],
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
|
|
|
|||
138
main.ts
138
main.ts
|
|
@ -1,25 +1,44 @@
|
|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { App, Editor, MarkdownView, WorkspaceLeaf, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { Resolve } from './Services/DependencyService';
|
||||
import { AIProvider } from './Enums/ApiProvider';
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
import { MainView, VIEW_TYPE_MAIN } from 'Views/MainView';
|
||||
import { Services } from 'Services/Services';
|
||||
import { OdbCache } from 'ODB/Core/OdbCache';
|
||||
import { FileAction } from 'Enums/FileAction';
|
||||
import { Path } from 'Enums/Path';
|
||||
import { RegisterAiProvider, RegisterDependencies } from 'Services/ServiceRegistration';
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
interface DmsAssistantSettings {
|
||||
apiProvider: string;
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
mySetting: 'default'
|
||||
const DEFAULT_SETTINGS: DmsAssistantSettings = {
|
||||
apiProvider: AIProvider.Gemini,
|
||||
apiKey: ""
|
||||
}
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
export default class DmsAssistantPlugin extends Plugin {
|
||||
settings: DmsAssistantSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
RegisterDependencies(this);
|
||||
|
||||
CreateDirectories(this);
|
||||
|
||||
this.registerView(
|
||||
VIEW_TYPE_MAIN,
|
||||
(leaf) => new MainView(leaf)
|
||||
);
|
||||
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
|
||||
// Called when the user clicks the icon.
|
||||
new Notice('This is a notice!');
|
||||
this.activateView();
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
|
|
@ -28,14 +47,6 @@ export default class MyPlugin extends Plugin {
|
|||
const statusBarItemEl = this.addStatusBarItem();
|
||||
statusBarItemEl.setText('Status Bar Text');
|
||||
|
||||
// This adds a simple command that can be triggered anywhere
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-simple',
|
||||
name: 'Open sample modal (simple)',
|
||||
callback: () => {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
});
|
||||
// This adds an editor command that can perform some operation on the current editor instance
|
||||
this.addCommand({
|
||||
id: 'sample-editor-command',
|
||||
|
|
@ -66,7 +77,7 @@ export default class MyPlugin extends Plugin {
|
|||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
||||
this.addSettingTab(new DmsAssistantSettingTab(this.app, this));
|
||||
|
||||
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
|
||||
// Using this function will automatically remove the event listener when this plugin is disabled.
|
||||
|
|
@ -76,10 +87,48 @@ export default class MyPlugin extends Plugin {
|
|||
|
||||
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
|
||||
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
|
||||
|
||||
let odbCache: OdbCache = Resolve<OdbCache>(Services.OdbCache)
|
||||
|
||||
await odbCache.buildCache();
|
||||
|
||||
this.registerEvent(
|
||||
this.app.vault.on(FileAction.Create, (file) => odbCache.onFileChanged(file, FileAction.Create))
|
||||
);
|
||||
this.registerEvent(
|
||||
this.app.vault.on(FileAction.Modify, (file) => odbCache.onFileChanged(file, FileAction.Modify))
|
||||
);
|
||||
this.registerEvent(
|
||||
this.app.vault.on(FileAction.Delete, (file) => odbCache.onFileChanged(file, FileAction.Delete))
|
||||
);
|
||||
this.registerEvent(
|
||||
this.app.vault.on(FileAction.Rename, (file) => odbCache.onFileChanged(file, FileAction.Rename))
|
||||
);
|
||||
}
|
||||
|
||||
onunload() {
|
||||
async onunload() {
|
||||
}
|
||||
|
||||
async activateView() {
|
||||
const { workspace } = this.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
const leaves = workspace.getLeavesOfType(VIEW_TYPE_MAIN);
|
||||
|
||||
if (leaves.length > 0) {
|
||||
// A leaf with our view already exists, use that
|
||||
leaf = leaves[0];
|
||||
} else {
|
||||
// Our view could not be found in the workspace, create a new leaf
|
||||
// in the right sidebar for it
|
||||
leaf = workspace.getRightLeaf(false);
|
||||
await leaf?.setViewState({ type: VIEW_TYPE_MAIN, active: true });
|
||||
}
|
||||
|
||||
// "Reveal" the leaf in case it is in a collapsed sidebar
|
||||
if (leaf != null) {
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
|
|
@ -88,6 +137,7 @@ export default class MyPlugin extends Plugin {
|
|||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
RegisterAiProvider(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,38 +147,68 @@ class SampleModal extends Modal {
|
|||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
const { contentEl } = this;
|
||||
contentEl.setText('Woah!');
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
class SampleSettingTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
class DmsAssistantSettingTab extends PluginSettingTab {
|
||||
plugin: DmsAssistantPlugin;
|
||||
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
constructor(app: App, plugin: DmsAssistantPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Setting #1')
|
||||
.setDesc('It\'s a secret')
|
||||
.setName("API Provider")
|
||||
.setDesc("Select the API provider to use.")
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("1", AIProvider.Gemini)
|
||||
.addOption("2", AIProvider.OpenAI)
|
||||
.setValue(this.plugin.settings.apiProvider)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.apiProvider = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("API Key")
|
||||
.setDesc("Enter your API key here.")
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your secret')
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.setPlaceholder("Enter your API key")
|
||||
.setValue(this.plugin.settings.apiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.mySetting = value;
|
||||
this.plugin.settings.apiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function CreateDirectories(plugin: DmsAssistantPlugin) {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
const vault = plugin.app.vault;
|
||||
if (vault.getAbstractFileByPath(Path.Root) == null) {
|
||||
vault.createFolder(Path.Root);
|
||||
}
|
||||
if (vault.getAbstractFileByPath(Path.Schemas) == null) {
|
||||
vault.createFolder(Path.Schemas);
|
||||
}
|
||||
if (vault.getAbstractFileByPath(Path.Records) == null) {
|
||||
vault.createFolder(Path.Records);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"id": "sample-plugin",
|
||||
"name": "Sample Plugin",
|
||||
"id": "dms_assistant",
|
||||
"name": "DM's Assistant",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Demonstrates some of the capabilities of the Obsidian API.",
|
||||
"description": "AI powered Dungeon Master's assistant.",
|
||||
"author": "Obsidian",
|
||||
"authorUrl": "https://obsidian.md",
|
||||
"fundingUrl": "https://obsidian.md/pricing",
|
||||
|
|
|
|||
3976
package-lock.json
generated
Normal file
3976
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
23
package.json
23
package.json
|
|
@ -1,24 +1,35 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"name": "dms_assistant",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"description": "AI powered Dungeon Master's assistant.",
|
||||
"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"
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"svelte-check": "svelte-check --tsconfig tsconfig.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/node": "^16.18.126",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild-svelte": "^0.9.3",
|
||||
"obsidian": "latest",
|
||||
"svelte": "^5.38.3",
|
||||
"svelte-check": "^4.3.1",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
"typescript": "~5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.17.0",
|
||||
"express": "^5.1.0",
|
||||
"uuid": "^11.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
|
|
@ -19,6 +21,7 @@
|
|||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"**/*.svelte"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue