mirror of
https://github.com/aldo-g/obsidian-llm-test.git
synced 2026-07-22 05:42:19 +00:00
openai call noe possible
This commit is contained in:
parent
5e7e4532bf
commit
8ebee35c75
11 changed files with 518 additions and 92 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -20,3 +20,6 @@ data.json
|
|||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
.compile-context/
|
||||
.env
|
||||
81
TestView.ts
Normal file
81
TestView.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
This file implements a custom sidebar view for the Obsidian RAG Test Plugin.
|
||||
It displays a header with a "Prepare Tests" button that, when clicked, will trigger the indexing of test notes and prepare them to be sent to an LLM for test generation.
|
||||
Below the header, a file tree of test notes with status icons and test result counts is rendered.
|
||||
*/
|
||||
|
||||
import { App, ItemView, WorkspaceLeaf } from 'obsidian';
|
||||
import type { IndexedNote } from './main';
|
||||
|
||||
export const VIEW_TYPE = "rag-test-view";
|
||||
|
||||
/**
|
||||
* Custom sidebar view for displaying test notes and preparing tests.
|
||||
*/
|
||||
export default class TestView extends ItemView {
|
||||
pluginData: IndexedNote[];
|
||||
|
||||
/**
|
||||
* Constructs the TestView.
|
||||
* @param leaf The workspace leaf to attach the view.
|
||||
* @param app The Obsidian application instance.
|
||||
* @param pluginData The indexed notes data.
|
||||
*/
|
||||
constructor(leaf: WorkspaceLeaf, app: App, pluginData: IndexedNote[]) {
|
||||
super(leaf);
|
||||
this.pluginData = pluginData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the view type.
|
||||
* @returns The view type identifier.
|
||||
*/
|
||||
getViewType(): string {
|
||||
return VIEW_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display text for the view.
|
||||
* @returns The display text.
|
||||
*/
|
||||
getDisplayText(): string {
|
||||
return "Test Dashboard";
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the view is opened; triggers rendering.
|
||||
*/
|
||||
async onOpen(): Promise<void> {
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the view is closed.
|
||||
* @returns A promise that resolves when the view is closed.
|
||||
*/
|
||||
async onClose(): Promise<void> {
|
||||
// No additional cleanup required.
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the sidebar with a header containing a "Prepare Tests" button,
|
||||
* followed by a file tree of test notes with status icons and test result counts.
|
||||
*/
|
||||
render(): void {
|
||||
const container = this.containerEl;
|
||||
container.empty();
|
||||
const headerEl = container.createEl('div', { cls: 'test-view-header' });
|
||||
const prepareButton = headerEl.createEl('button', { text: 'Prepare Tests' });
|
||||
prepareButton.addEventListener('click', () => {
|
||||
console.log('Prepare Tests clicked');
|
||||
// Here you would call a function to index files and prepare data for the LLM.
|
||||
});
|
||||
const listEl = container.createEl('ul');
|
||||
this.pluginData.forEach((note) => {
|
||||
const itemEl = listEl.createEl('li');
|
||||
const statusIcon = note.testStatus.testsReady ? '🟢' : '🟡';
|
||||
const resultText = note.testStatus.testsReady ? ` (${note.testStatus.passed}/${note.testStatus.total})` : '';
|
||||
itemEl.createEl('span', { text: `${statusIcon} ${note.filePath}${resultText}` });
|
||||
});
|
||||
}
|
||||
}
|
||||
213
main.ts
213
main.ts
|
|
@ -1,6 +1,29 @@
|
|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
/*
|
||||
This file is the main plugin file for the Obsidian RAG Test Plugin.
|
||||
It registers a ribbon icon that, when clicked, opens the custom sidebar view (Test Dashboard).
|
||||
It also handles indexing of test notes in the "Test" folder and persisting the index.
|
||||
*/
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
import { App, Notice, Plugin, PluginSettingTab, Setting, TFile, WorkspaceLeaf } from 'obsidian';
|
||||
import TestView, { VIEW_TYPE } from './TestView';
|
||||
|
||||
/**
|
||||
* Interface for test status.
|
||||
*/
|
||||
export interface TestStatus {
|
||||
testsReady: boolean;
|
||||
passed: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for an indexed note.
|
||||
*/
|
||||
export interface IndexedNote {
|
||||
filePath: string;
|
||||
content: string;
|
||||
testStatus: TestStatus;
|
||||
}
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
|
|
@ -8,125 +31,157 @@ interface MyPluginSettings {
|
|||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
mySetting: 'default'
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for storing plugin data, including settings and the persisted index.
|
||||
*/
|
||||
interface MyPluginData {
|
||||
settings: MyPluginSettings;
|
||||
persistedIndex: IndexedNote[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Main plugin class for the Obsidian RAG Test Plugin.
|
||||
*/
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
indexedNotes: IndexedNote[] = [];
|
||||
testViewLeaf: WorkspaceLeaf | null = null;
|
||||
|
||||
/**
|
||||
* Called when the plugin is loaded.
|
||||
*/
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// 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.registerView(VIEW_TYPE, (leaf: WorkspaceLeaf) => new TestView(leaf, this.app, this.indexedNotes));
|
||||
this.addRibbonIcon('dice', 'Test Dashboard', (evt: MouseEvent) => {
|
||||
this.openTestDashboard();
|
||||
});
|
||||
// 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 simple command that can be triggered anywhere
|
||||
this.addStatusBarItem().setText('RAG Test Plugin Active');
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-simple',
|
||||
name: 'Open sample modal (simple)',
|
||||
id: 'open-test-dashboard',
|
||||
name: 'Open Test Dashboard',
|
||||
callback: () => {
|
||||
new SampleModal(this.app).open();
|
||||
this.openTestDashboard();
|
||||
}
|
||||
});
|
||||
// This adds an editor command that can perform some operation on the current editor instance
|
||||
this.addCommand({
|
||||
id: 'sample-editor-command',
|
||||
name: 'Sample editor command',
|
||||
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||
console.log(editor.getSelection());
|
||||
editor.replaceSelection('Sample Editor Command');
|
||||
}
|
||||
});
|
||||
// This adds a complex command that can check whether the current state of the app allows execution of the command
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-complex',
|
||||
name: 'Open sample modal (complex)',
|
||||
checkCallback: (checking: boolean) => {
|
||||
// Conditions to check
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (markdownView) {
|
||||
// If checking is true, we're simply "checking" if the command can be run.
|
||||
// If checking is false, then we want to actually perform the operation.
|
||||
if (!checking) {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new SampleSettingTab(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.
|
||||
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
||||
console.log('click', evt);
|
||||
console.log('Document clicked', evt);
|
||||
});
|
||||
|
||||
// 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));
|
||||
this.registerInterval(window.setInterval(() => console.log('Interval log'), 5 * 60 * 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the plugin is unloaded.
|
||||
*/
|
||||
onunload() {
|
||||
|
||||
if (this.testViewLeaf) {
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads plugin settings and the persisted index from disk.
|
||||
*/
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
const data = (await this.loadData()) as MyPluginData | null;
|
||||
if (data) {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, data.settings);
|
||||
this.indexedNotes = data.persistedIndex || [];
|
||||
} else {
|
||||
this.settings = DEFAULT_SETTINGS;
|
||||
this.indexedNotes = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves plugin settings and the current index to disk.
|
||||
*/
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class SampleModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.setText('Woah!');
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
const data: MyPluginData = {
|
||||
settings: this.settings,
|
||||
persistedIndex: this.indexedNotes
|
||||
};
|
||||
await this.saveData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes Markdown files in the "Test" folder, extracts test readiness data, and persists the index.
|
||||
*/
|
||||
async indexTestNotes() {
|
||||
this.indexedNotes = [];
|
||||
const markdownFiles = this.app.vault.getMarkdownFiles().filter((file: TFile) => file.path.startsWith("Test/"));
|
||||
for (const file of markdownFiles) {
|
||||
const content = await this.app.vault.read(file);
|
||||
const testsReady = content.includes("## Test");
|
||||
let total = 0;
|
||||
let passed = 0;
|
||||
if (testsReady) {
|
||||
const regex = /- \[( |x)\]/g;
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
total++;
|
||||
if (match[1] === "x") passed++;
|
||||
}
|
||||
}
|
||||
const testStatus = { testsReady, passed, total };
|
||||
this.indexedNotes.push({ filePath: file.path, content, testStatus });
|
||||
console.log(`Indexed: ${file.path}`);
|
||||
}
|
||||
await this.saveSettings();
|
||||
new Notice(`Indexed ${this.indexedNotes.length} test notes`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the test dashboard view in the left sidebar.
|
||||
*/
|
||||
openTestDashboard() {
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE);
|
||||
const leaf = this.app.workspace.getLeftLeaf(false);
|
||||
if (!leaf) {
|
||||
new Notice('Could not obtain workspace leaf.');
|
||||
return;
|
||||
}
|
||||
leaf.setViewState({
|
||||
type: VIEW_TYPE,
|
||||
active: true
|
||||
});
|
||||
this.app.workspace.revealLeaf(leaf);
|
||||
this.testViewLeaf = leaf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings tab for the RAG Test Plugin.
|
||||
*/
|
||||
class SampleSettingTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
|
||||
/**
|
||||
* Constructs the settings tab.
|
||||
* @param app The Obsidian application instance.
|
||||
* @param plugin The plugin instance.
|
||||
*/
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the settings tab.
|
||||
*/
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Setting #1')
|
||||
.setDesc('It\'s a secret')
|
||||
.setDesc("It's a secret")
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your secret')
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.onChange(async (value) => {
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.mySetting = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"id": "sample-plugin",
|
||||
"name": "Sample Plugin",
|
||||
"id": "obsidian-rag-test-plugin",
|
||||
"name": "Obsidian RAG Test Plugin",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Demonstrates some of the capabilities of the Obsidian API.",
|
||||
"author": "Obsidian",
|
||||
"description": "A plugin to index test notes and display a dashboard for self-testing based on your notes.",
|
||||
"author": "Aldo E George",
|
||||
"authorUrl": "https://obsidian.md",
|
||||
"fundingUrl": "https://obsidian.md/pricing",
|
||||
"isDesktopOnly": false
|
||||
|
|
|
|||
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -8,6 +8,9 @@
|
|||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
|
|
@ -1075,6 +1078,18 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.17.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"type": "commonjs",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
|
|
@ -20,5 +21,8 @@
|
|||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.7"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
src/formatter.ts
Normal file
21
src/formatter.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
This file contains functions to format indexed notes into a prompt for an LLM.
|
||||
It exports a function formatNotesForLLM that takes an array of IndexedNote and returns a formatted prompt string.
|
||||
*/
|
||||
|
||||
import type { IndexedNote } from "./types";
|
||||
|
||||
/**
|
||||
* Formats an array of IndexedNote objects into a structured prompt string for the LLM.
|
||||
* @param notes - An array of IndexedNote objects.
|
||||
* @returns A formatted prompt string containing the full content of each note.
|
||||
*/
|
||||
export function formatNotesForLLM(notes: IndexedNote[]): string {
|
||||
let prompt = "Generate test questions based on the following notes.\n\n";
|
||||
notes.forEach(note => {
|
||||
prompt += `### Note: ${note.filePath}\n`;
|
||||
prompt += `Content:\n${note.content}\n\n`;
|
||||
});
|
||||
prompt += "Ensure that the test questions are relevant to the content and test key concepts.";
|
||||
return prompt;
|
||||
}
|
||||
41
src/indexer.ts
Normal file
41
src/indexer.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
This file contains functions to scan the vault and extract test data from all Markdown files.
|
||||
It exports a function indexTestNotes that reads each Markdown file,
|
||||
extracts test-related markers (if any), and returns an array of IndexedNote objects.
|
||||
*/
|
||||
|
||||
import { App } from "obsidian";
|
||||
import type { IndexedNote, TestStatus } from "./types";
|
||||
|
||||
/**
|
||||
* Scans all Markdown files in the vault and extracts test data.
|
||||
* @param app - The Obsidian application instance.
|
||||
* @returns A promise that resolves to an array of IndexedNote objects.
|
||||
*/
|
||||
export async function indexTestNotes(app: App): Promise<IndexedNote[]> {
|
||||
const notes: IndexedNote[] = [];
|
||||
// Process all Markdown files without filtering by folder.
|
||||
const markdownFiles = app.vault.getMarkdownFiles();
|
||||
for (const file of markdownFiles) {
|
||||
const content = await app.vault.read(file);
|
||||
// Check for a test marker (e.g., "## Test"). This may be present in some notes.
|
||||
const testsReady = content.includes("## Test");
|
||||
let total = 0;
|
||||
let passed = 0;
|
||||
if (testsReady) {
|
||||
const regex = /- \[( |x)\]/g;
|
||||
let match;
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
total++;
|
||||
if (match[1] === "x") passed++;
|
||||
}
|
||||
}
|
||||
const testStatus: TestStatus = { testsReady, passed, total };
|
||||
notes.push({
|
||||
filePath: file.path,
|
||||
content,
|
||||
testStatus
|
||||
});
|
||||
}
|
||||
return notes;
|
||||
}
|
||||
62
src/llm.ts
Normal file
62
src/llm.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Handles communication with OpenAI to generate test questions.
|
||||
*/
|
||||
|
||||
import { formatNotesForLLM } from "./formatter";
|
||||
import type { IndexedNote, LLMResponse } from "./types";
|
||||
|
||||
const API_URL = "https://api.openai.com/v1/chat/completions";
|
||||
|
||||
// Load API key from environment variables (DO NOT hardcode!)
|
||||
import { config } from "dotenv";
|
||||
config(); // Load .env variables
|
||||
|
||||
const API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
if (!API_KEY) {
|
||||
throw new Error("Missing OpenAI API key! Add it to your .env file.");
|
||||
}
|
||||
|
||||
console.log("✅ OpenAI API Key Loaded Successfully");
|
||||
|
||||
/**
|
||||
* Sends formatted note data to OpenAI and retrieves test questions.
|
||||
* @param {IndexedNote[]} indexedNotes - The indexed notes to generate questions from.
|
||||
* @returns {Promise<string[]>} - The generated test questions.
|
||||
*/
|
||||
export async function generateTestQuestions(indexedNotes: IndexedNote[]): Promise<string[]> {
|
||||
const prompt = formatNotesForLLM(indexedNotes);
|
||||
|
||||
const requestBody = {
|
||||
model: "gpt-4-turbo", // You can change this model
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful AI that generates test questions from study notes." },
|
||||
{ role: "user", content: prompt }
|
||||
],
|
||||
temperature: 0.7, // Adjust for more or less creativity
|
||||
max_tokens: 500
|
||||
};
|
||||
|
||||
const response = await fetch(API_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`OpenAI API Error: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const responseData: LLMResponse = await response.json();
|
||||
const output = responseData.choices?.[0]?.message?.content;
|
||||
|
||||
if (!output) {
|
||||
throw new Error("Invalid response from OpenAI");
|
||||
}
|
||||
|
||||
// Split response into separate questions (assuming they are newline-separated)
|
||||
return output.split("\n").filter(q => q.trim().length > 0);
|
||||
}
|
||||
60
src/types.ts
Normal file
60
src/types.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
This file contains shared type definitions for the Obsidian RAG Test Plugin,
|
||||
including types for LLM responses and generated tests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the response structure from the LLM API.
|
||||
*/
|
||||
export interface LLMResponse {
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a generated test question (and an optional answer) from the LLM.
|
||||
*/
|
||||
export interface GeneratedTest {
|
||||
question: string;
|
||||
answer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the test status for a note.
|
||||
*/
|
||||
export interface TestStatus {
|
||||
testsReady: boolean;
|
||||
passed: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an indexed note with its file path, content, and test status.
|
||||
*/
|
||||
export interface IndexedNote {
|
||||
filePath: string;
|
||||
content: string;
|
||||
testStatus: TestStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared type definitions for indexed notes and LLM responses.
|
||||
*/
|
||||
|
||||
export interface LLMResponse {
|
||||
id: string;
|
||||
object: string;
|
||||
created: number;
|
||||
model: string;
|
||||
choices: {
|
||||
index: number;
|
||||
message: {
|
||||
role: string;
|
||||
content: string;
|
||||
};
|
||||
}[];
|
||||
usage: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
}
|
||||
84
test_index_formatter.ts
Normal file
84
test_index_formatter.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* Test script to verify the indexing, formatting, and LLM response generation.
|
||||
* It mocks an Obsidian vault, runs the indexer and formatter, calls the LLM, and prints the output.
|
||||
*/
|
||||
|
||||
import { indexTestNotes } from "./src/indexer";
|
||||
import { formatNotesForLLM } from "./src/formatter";
|
||||
import { generateTestQuestions } from "./src/llm";
|
||||
import type { IndexedNote } from "./src/types";
|
||||
|
||||
// Mock Vault class to simulate Obsidian vault behavior
|
||||
class MockVault {
|
||||
files: { path: string; content: string }[];
|
||||
|
||||
constructor(files: { path: string; content: string }[]) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
// Simulates fetching Markdown files
|
||||
getMarkdownFiles() {
|
||||
return this.files.map(file => ({
|
||||
path: file.path,
|
||||
}));
|
||||
}
|
||||
|
||||
// Simulates reading a file's content
|
||||
async read(file: { path: string }) {
|
||||
const found = this.files.find(f => f.path === file.path);
|
||||
if (found) {
|
||||
return found.content;
|
||||
}
|
||||
throw new Error(`File not found: ${file.path}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Mock App object
|
||||
class MockApp {
|
||||
vault: MockVault;
|
||||
|
||||
constructor(vault: MockVault) {
|
||||
this.vault = vault;
|
||||
}
|
||||
}
|
||||
|
||||
// Sample test data (simulating Obsidian Markdown files)
|
||||
const mockFiles = [
|
||||
{
|
||||
path: "Test/1.1 - The Art of Learning.md",
|
||||
content: "## Key Takeaways\n- Learning is an iterative process.\n- Experimentation is crucial.\n\n## Test\n- [ ] What is the key principle of learning?",
|
||||
},
|
||||
{
|
||||
path: "Test/1.2 - The Pyramid Principle.md",
|
||||
content: "## Summary\nThe Pyramid Principle helps structure arguments.\n\n## Test\n- [x] What does the Pyramid Principle help with?",
|
||||
},
|
||||
{
|
||||
path: "General Notes/1.3 - Random Thoughts.md",
|
||||
content: "## Notes\nSome random reflections on life.",
|
||||
},
|
||||
];
|
||||
|
||||
async function runTest() {
|
||||
// Initialize mock app and vault
|
||||
const mockApp = new MockApp(new MockVault(mockFiles));
|
||||
|
||||
// Step 1: Run indexer
|
||||
const indexedNotes: IndexedNote[] = await indexTestNotes(mockApp as any);
|
||||
console.log("\n📂 Indexed Notes:\n", indexedNotes);
|
||||
|
||||
// Step 2: Run formatter
|
||||
const formattedPrompt = formatNotesForLLM(indexedNotes);
|
||||
console.log("\n📝 Formatted Prompt for LLM:\n", formattedPrompt);
|
||||
|
||||
// Step 3: Call LLM to generate test questions
|
||||
try {
|
||||
console.log("\n🚀 Sending request to OpenAI...\n");
|
||||
const testQuestions = await generateTestQuestions(indexedNotes);
|
||||
console.log("\n✅ Generated Test Questions:\n", testQuestions);
|
||||
} catch (error) {
|
||||
console.error("\n❌ Error in LLM call:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the test
|
||||
runTest();
|
||||
Loading…
Reference in a new issue