diff --git a/TestView.ts b/TestView.ts deleted file mode 100644 index 2ced7cc..0000000 --- a/TestView.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* -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 { - this.render(); - } - - /** - * Called when the view is closed. - * @returns A promise that resolves when the view is closed. - */ - async onClose(): Promise { - // 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}` }); - }); - } -} \ No newline at end of file diff --git a/main.ts b/main.ts index bea380b..f2b57d4 100644 --- a/main.ts +++ b/main.ts @@ -1,11 +1,14 @@ /* 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. +It registers a ribbon icon that, when clicked, opens the dashboard view for selecting notes. +It also registers the full-screen Question Document view and exposes a method to open that view. +It handles indexing of notes, persisting settings, and provides a settings tab for configuration. */ -import { App, Notice, Plugin, PluginSettingTab, Setting, TFile, WorkspaceLeaf } from 'obsidian'; -import TestView, { VIEW_TYPE } from './TestView'; +import { App, Notice, Plugin, PluginSettingTab, Setting, TFile, WorkspaceLeaf } from "obsidian"; +import TestDashboardView, { VIEW_TYPE as DASHBOARD_VIEW_TYPE } from "./src/components/TestDashboardView"; +import QuestionDocumentView, { QUESTION_VIEW_TYPE } from "./src/components/QuestionDocumentView"; +import SettingsTab from "./src/components/SettingsTab"; /** * Interface for test status. @@ -27,10 +30,12 @@ export interface IndexedNote { interface MyPluginSettings { mySetting: string; + apiKey: string; } const DEFAULT_SETTINGS: MyPluginSettings = { - mySetting: 'default' + mySetting: "default", + apiKey: "" }; /** @@ -47,39 +52,42 @@ interface MyPluginData { export default class MyPlugin extends Plugin { settings: MyPluginSettings; indexedNotes: IndexedNote[] = []; - testViewLeaf: WorkspaceLeaf | null = null; + testDashboardLeaf: WorkspaceLeaf | null = null; /** * Called when the plugin is loaded. */ async onload() { await this.loadSettings(); - this.registerView(VIEW_TYPE, (leaf: WorkspaceLeaf) => new TestView(leaf, this.app, this.indexedNotes)); - this.addRibbonIcon('dice', 'Test Dashboard', (evt: MouseEvent) => { + // Register dashboard view for note selection. + this.registerView(DASHBOARD_VIEW_TYPE, (leaf: WorkspaceLeaf) => new TestDashboardView(leaf, this.app, this.indexedNotes)); + // Register full-screen question document view. + this.registerView(QUESTION_VIEW_TYPE, (leaf: WorkspaceLeaf) => new QuestionDocumentView(leaf, this.app, [])); + + this.addRibbonIcon("dice", "Test Dashboard", (evt: MouseEvent) => { this.openTestDashboard(); }); - this.addStatusBarItem().setText('RAG Test Plugin Active'); + this.addStatusBarItem().setText("RAG Test Plugin Active"); this.addCommand({ - id: 'open-test-dashboard', - name: 'Open Test Dashboard', + id: "open-test-dashboard", + name: "Open Test Dashboard", callback: () => { this.openTestDashboard(); } }); - this.addSettingTab(new SampleSettingTab(this.app, this)); - this.registerDomEvent(document, 'click', (evt: MouseEvent) => { - console.log('Document clicked', evt); + this.addSettingTab(new SettingsTab(this.app, this)); + this.registerDomEvent(document, "click", (evt: MouseEvent) => { + console.log("Document clicked", evt); }); - this.registerInterval(window.setInterval(() => console.log('Interval log'), 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); - } + this.app.workspace.detachLeavesOfType(DASHBOARD_VIEW_TYPE); + this.app.workspace.detachLeavesOfType(QUESTION_VIEW_TYPE); } /** @@ -108,11 +116,12 @@ export default class MyPlugin extends Plugin { } /** - * Indexes Markdown files in the "Test" folder, extracts test readiness data, and persists the index. + * Indexes all Markdown files in the vault, 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/")); + // Scan all Markdown files without filtering by folder. + const markdownFiles = this.app.vault.getMarkdownFiles(); for (const file of markdownFiles) { const content = await this.app.vault.read(file); const testsReady = content.includes("## Test"); @@ -131,59 +140,44 @@ export default class MyPlugin extends Plugin { console.log(`Indexed: ${file.path}`); } await this.saveSettings(); - new Notice(`Indexed ${this.indexedNotes.length} test notes`); + new Notice(`Indexed ${this.indexedNotes.length} notes`); } /** * Opens the test dashboard view in the left sidebar. */ openTestDashboard() { - this.app.workspace.detachLeavesOfType(VIEW_TYPE); + this.app.workspace.detachLeavesOfType(DASHBOARD_VIEW_TYPE); const leaf = this.app.workspace.getLeftLeaf(false); if (!leaf) { - new Notice('Could not obtain workspace leaf.'); + new Notice("Could not obtain workspace leaf."); return; } leaf.setViewState({ - type: VIEW_TYPE, + type: DASHBOARD_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; + this.testDashboardLeaf = leaf; } /** - * Renders the settings tab. + * Opens a full-screen question document view with the provided test questions. + * @param questions - An array of test question strings. */ - display(): void { - const { containerEl } = this; - containerEl.empty(); - new Setting(containerEl) - .setName('Setting #1') - .setDesc("It's a secret") - .addText(text => text - .setPlaceholder('Enter your secret') - .setValue(this.plugin.settings.mySetting) - .onChange(async (value: string) => { - this.plugin.settings.mySetting = value; - await this.plugin.saveSettings(); - })); + public openQuestionDocument(questions: string[]): void { + this.app.workspace.detachLeavesOfType(QUESTION_VIEW_TYPE); + const leaf = this.app.workspace.getRightLeaf(false); + if (!leaf) { + new Notice("Could not obtain workspace leaf for question document."); + return; + } + leaf.setViewState({ + type: QUESTION_VIEW_TYPE, + active: true + }); + this.app.workspace.revealLeaf(leaf); + // Instantiate the full-screen view with the questions. + new QuestionDocumentView(leaf, this.app, questions); } } \ No newline at end of file diff --git a/src/components/QuestionDocumentView.ts b/src/components/QuestionDocumentView.ts new file mode 100644 index 0000000..e5f133a --- /dev/null +++ b/src/components/QuestionDocumentView.ts @@ -0,0 +1,98 @@ +/* +This file implements a full-page view for displaying generated test questions. +It renders an interactive document where each question is displayed with an input field for the user's answer, +and includes a "Mark Test" button to submit the answers. +*/ + +import { App, ItemView, Notice, WorkspaceLeaf } from "obsidian"; + +export const QUESTION_VIEW_TYPE = "question-document-view"; + +/** + * A full-page view for displaying test questions. + */ +export default class QuestionDocumentView extends ItemView { + questions: string[]; + + /** + * Constructs the QuestionDocumentView. + * @param leaf - The workspace leaf to attach the view. + * @param app - The Obsidian application instance. + * @param questions - An array of test questions. + */ + constructor(leaf: WorkspaceLeaf, app: App, questions: string[]) { + super(leaf); + this.questions = questions; + } + + /** + * Returns the view type identifier. + * @returns The view type string. + */ + getViewType(): string { + return QUESTION_VIEW_TYPE; + } + + /** + * Returns the display text for the view. + * @returns The display text. + */ + getDisplayText(): string { + return "Generated Test Questions"; + } + + /** + * Called when the view is opened; triggers rendering. + */ + async onOpen(): Promise { + this.render(); + } + + /** + * Called when the view is closed. + * @returns A promise that resolves when the view is closed. + */ + async onClose(): Promise { + // No additional cleanup required. + } + + /** + * Renders the interactive question document. + */ + render(): void { + const container = this.containerEl; + container.empty(); + + const titleEl = container.createEl("h1", { text: "Generated Test Questions" }); + const formEl = container.createEl("form"); + + this.questions.forEach((question, index) => { + const questionDiv = formEl.createEl("div", { cls: "question-item" }); + const label = questionDiv.createEl("label", { text: `Q${index + 1}: ${question}` }); + label.style.display = "block"; + const input = questionDiv.createEl("input", { type: "text" }) as HTMLInputElement; + input.placeholder = "Type your answer here"; + input.style.width = "100%"; + (input as HTMLElement).dataset.questionIndex = index.toString(); + }); + + const markButton = formEl.createEl("button", { text: "Mark Test" }); + markButton.type = "button"; + markButton.addEventListener("click", () => { + const answers: { [key: number]: string } = {}; + const inputs = formEl.querySelectorAll("input[type='text']") as NodeListOf; + inputs.forEach((input) => { + const idxStr = input.dataset.questionIndex; + if (idxStr !== undefined) { + const idx = parseInt(idxStr, 10); + answers[idx] = input.value; + } + }); + new Notice("Test answers submitted!"); + console.log("User Answers:", answers); + }); + + formEl.appendChild(markButton); + container.appendChild(formEl); + } +} diff --git a/src/components/SettingsTab.ts b/src/components/SettingsTab.ts new file mode 100644 index 0000000..19b2f16 --- /dev/null +++ b/src/components/SettingsTab.ts @@ -0,0 +1,57 @@ +/* +This file implements the settings tab for the Obsidian RAG Test Plugin. +It allows the user to configure plugin options, including setting the OpenAI API key. +*/ + +import { App, PluginSettingTab, Setting } from "obsidian"; +import type { IndexedNote } from "../models/types"; +import type MyPlugin from "../../main"; + +/** + * Settings tab for the RAG Test Plugin. + */ +export default class SettingsTab extends PluginSettingTab { + plugin: MyPlugin; + + /** + * Constructs the settings tab. + * @param app - The Obsidian application instance. + * @param plugin - The main plugin instance. + */ + constructor(app: App, plugin: MyPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + /** + * Renders the settings tab. + */ + display(): void { + const { containerEl } = this; + containerEl.empty(); + new Setting(containerEl) + .setName("Setting #1") + .setDesc("It's a secret") + .addText((text) => + text + .setPlaceholder("Enter your secret") + .setValue(this.plugin.settings.mySetting) + .onChange(async (value: string) => { + this.plugin.settings.mySetting = value; + await this.plugin.saveSettings(); + }) + ); + new Setting(containerEl) + .setName("OpenAI API Key") + .setDesc("Enter your OpenAI API key for test generation.") + .addText((text) => + text + .setPlaceholder("sk-...") + .setValue(this.plugin.settings.apiKey) + .onChange(async (value: string) => { + this.plugin.settings.apiKey = value; + await this.plugin.saveSettings(); + }) + ); + } +} diff --git a/src/components/TestDashboardView.ts b/src/components/TestDashboardView.ts new file mode 100644 index 0000000..fdb8a18 --- /dev/null +++ b/src/components/TestDashboardView.ts @@ -0,0 +1,147 @@ +/* +This file implements a custom sidebar view (dashboard) for the Obsidian RAG Test Plugin. +It displays a list of all indexed notes with checkboxes for selection and a "Create Tests" button. +When "Create Tests" is clicked, an OpenAI API call is made for each selected note (using that note's full content) +to generate test questions. All generated questions are then aggregated and displayed in a full-screen question document view. +*/ + +import { App, ItemView, Notice, WorkspaceLeaf } from "obsidian"; +import type { IndexedNote } from "../models/types"; +import { generateTestQuestions } from "../services/llm"; + +export const VIEW_TYPE = "rag-test-view"; + +/** + * Custom dashboard view for selecting notes and generating tests. + */ +export default class TestDashboardView extends ItemView { + pluginData: IndexedNote[]; + + /** + * Constructs the dashboard view. + * @param leaf - The workspace leaf to attach the view. + * @param app - The Obsidian application instance. + * @param pluginData - The array of indexed notes. + */ + constructor(leaf: WorkspaceLeaf, app: App, pluginData: IndexedNote[]) { + super(leaf); + this.pluginData = pluginData; + } + + /** + * Returns the view type identifier. + * @returns The view type string. + */ + 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 { + this.render(); + } + + /** + * Called when the view is closed. + * @returns A promise that resolves when the view is closed. + */ + async onClose(): Promise { + // No cleanup needed. + } + + /** + * Renders the dashboard: a list of notes with selection checkboxes and a "Create Tests" button. + */ + async render(): Promise { + const container = this.containerEl; + container.empty(); + + // Header with "Create Tests" button + const headerEl = container.createEl("div", { cls: "test-view-header" }); + const createTestsButton = headerEl.createEl("button", { text: "Create Tests" }); + createTestsButton.disabled = true; + createTestsButton.addEventListener("click", async () => { + await this.createTests(); + }); + + // Container for the list of notes + const listEl = container.createEl("ul"); + this.pluginData.forEach((note) => { + const itemEl = listEl.createEl("li"); + const checkbox = itemEl.createEl("input", { type: "checkbox" }); + checkbox.dataset.filePath = note.filePath; + itemEl.createEl("span", { text: ` ${note.filePath}` }); + checkbox.addEventListener("change", () => { + this.updateCreateTestsButtonState(createTestsButton, listEl); + }); + }); + + this.updateCreateTestsButtonState(createTestsButton, listEl); + } + + /** + * Updates the enabled state of the "Create Tests" button based on selection. + * @param button - The button element. + * @param listEl - The container element for the checkboxes. + */ + updateCreateTestsButtonState(button: HTMLButtonElement, listEl: HTMLElement): void { + const checkboxes = listEl.querySelectorAll('input[type="checkbox"]'); + let anyChecked = false; + checkboxes.forEach((cb) => { + if ((cb as HTMLInputElement).checked) { + anyChecked = true; + } + }); + button.disabled = !anyChecked; + } + + /** + * For each selected note, calls the LLM API to generate test questions, + * aggregates all questions, and then opens a full-screen view to display them. + */ + async createTests(): Promise { + const container = this.containerEl; + const checkboxes = container.querySelectorAll('input[type="checkbox"]'); + // Retrieve API key from plugin settings stored in the main plugin instance. + const plugin = (this.app as any).plugins.plugins["obsidian-rag-test-plugin"] as any; + const apiKey = plugin?.settings?.apiKey; + if (!apiKey) { + new Notice("āŒ OpenAI API Key is missing! Please set it in the plugin settings."); + return; + } + let aggregatedQuestions: string[] = []; + for (const checkbox of Array.from(checkboxes)) { + const input = checkbox as HTMLInputElement; + if (input.checked) { + const filePath = input.dataset.filePath; + const note = this.pluginData.find(n => n.filePath === filePath); + if (!note) continue; + console.log(`Generating tests for: ${filePath}`); + try { + const questions = await generateTestQuestions([note], apiKey); + console.log(`Generated tests for ${filePath}:`, questions); + aggregatedQuestions.push(`Questions for ${filePath}:`); + aggregatedQuestions.push(...questions); + } catch (error) { + console.error(`Error generating tests for ${filePath}:`, error); + } + } + } + if (aggregatedQuestions.length > 0) { + // Open a full-screen view (new page) to display the aggregated test questions. + (plugin as any).openQuestionDocument(aggregatedQuestions); + } else { + new Notice("No tests generated."); + } + } +} \ No newline at end of file diff --git a/src/types.ts b/src/models/types.ts similarity index 83% rename from src/types.ts rename to src/models/types.ts index aee93af..452f86e 100644 --- a/src/types.ts +++ b/src/models/types.ts @@ -1,13 +1,28 @@ /* This file contains shared type definitions for the Obsidian RAG Test Plugin, -including types for LLM responses and generated tests. +including types for LLM responses, generated tests, and indexed notes. */ /** * Represents the response structure from the LLM API. */ export interface LLMResponse { - data: any; + 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; + }; } /** @@ -35,26 +50,3 @@ export interface IndexedNote { 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; - }; -} \ No newline at end of file diff --git a/src/formatter.ts b/src/services/formatter.ts similarity index 93% rename from src/formatter.ts rename to src/services/formatter.ts index 2d0373f..d192a27 100644 --- a/src/formatter.ts +++ b/src/services/formatter.ts @@ -3,7 +3,7 @@ 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"; +import type { IndexedNote } from "../models/types"; /** * Formats an array of IndexedNote objects into a structured prompt string for the LLM. @@ -18,4 +18,4 @@ export function formatNotesForLLM(notes: IndexedNote[]): string { }); prompt += "Ensure that the test questions are relevant to the content and test key concepts."; return prompt; -} \ No newline at end of file +} diff --git a/src/indexer.ts b/src/services/indexer.ts similarity index 95% rename from src/indexer.ts rename to src/services/indexer.ts index 21e29a0..ab1f93a 100644 --- a/src/indexer.ts +++ b/src/services/indexer.ts @@ -5,7 +5,7 @@ extracts test-related markers (if any), and returns an array of IndexedNote obje */ import { App } from "obsidian"; -import type { IndexedNote, TestStatus } from "./types"; +import type { IndexedNote, TestStatus } from "../models/types"; /** * Scans all Markdown files in the vault and extracts test data. @@ -38,4 +38,4 @@ export async function indexTestNotes(app: App): Promise { }); } return notes; -} \ No newline at end of file +} diff --git a/src/llm.ts b/src/services/llm.ts similarity index 55% rename from src/llm.ts rename to src/services/llm.ts index 3b876ed..64a3fd1 100644 --- a/src/llm.ts +++ b/src/services/llm.ts @@ -1,46 +1,39 @@ -/** - * Handles communication with OpenAI to generate test questions. - */ +/* +This file handles communication with OpenAI to generate test questions. +It exports a function generateTestQuestions that sends a prompt (built using formatter.ts) +to OpenAI using the provided API key and returns an array of test questions. +*/ import { formatNotesForLLM } from "./formatter"; -import type { IndexedNote, LLMResponse } from "./types"; +import type { IndexedNote, LLMResponse } from "../models/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} - The generated test questions. + * @param indexedNotes - The indexed notes to generate questions from. + * @param apiKey - The OpenAI API key. + * @returns A promise that resolves to an array of generated test questions. */ -export async function generateTestQuestions(indexedNotes: IndexedNote[]): Promise { +export async function generateTestQuestions(indexedNotes: IndexedNote[], apiKey: string): Promise { + if (!apiKey) { + throw new Error("Missing OpenAI API key! Please set it in the plugin settings."); + } const prompt = formatNotesForLLM(indexedNotes); - const requestBody = { - model: "gpt-4-turbo", // You can change this model + model: "gpt-3.5-turbo-0125", messages: [ - { role: "system", content: "You are a helpful AI that generates test questions from study notes." }, + { role: "system", content: "You are a helpful AI that generates test questions from study notes. Your questions should be answered with written answers." }, { role: "user", content: prompt } ], - temperature: 0.7, // Adjust for more or less creativity + temperature: 0.7, max_tokens: 500 }; const response = await fetch(API_URL, { method: "POST", headers: { - "Authorization": `Bearer ${API_KEY}`, + "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" }, body: JSON.stringify(requestBody) @@ -59,4 +52,4 @@ export async function generateTestQuestions(indexedNotes: IndexedNote[]): Promis // Split response into separate questions (assuming they are newline-separated) return output.split("\n").filter(q => q.trim().length > 0); -} \ No newline at end of file +} diff --git a/test_index_formatter.ts b/test_index_formatter.ts deleted file mode 100644 index 5643085..0000000 --- a/test_index_formatter.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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(); \ No newline at end of file