mirror of
https://github.com/dralkh/spaceforge.git
synced 2026-07-22 06:45:03 +00:00
feat: Refine notices and error handling, introduce UI constants.
This commit is contained in:
parent
a5433e047d
commit
668fd66876
19 changed files with 243 additions and 216 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { Notice, requestUrl } from 'obsidian';
|
||||
import { CLAUDE, MCQS, MCQ, API } from '../ui/constants';
|
||||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
|
|
@ -13,16 +14,16 @@ export class ClaudeService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
if (!settings.claudeApiKey) {
|
||||
new Notice('Claude API key is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${CLAUDE} ${API} key is not set. Please add it in the settings`);
|
||||
return null;
|
||||
}
|
||||
if (!settings.claudeModel) {
|
||||
new Notice('Claude model is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${CLAUDE} model is not set. Please add it in the settings`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
new Notice('Generating MCQs using Claude...');
|
||||
new Notice(`Generating ${MCQS} using ${CLAUDE}...`);
|
||||
|
||||
// Determine the number of questions to generate
|
||||
let numQuestionsToGenerate: number;
|
||||
|
|
@ -38,7 +39,7 @@ export class ClaudeService implements IMCQGenerationService {
|
|||
const questions = this.parseResponse(response, settings, numQuestionsToGenerate);
|
||||
|
||||
if (questions.length === 0) {
|
||||
new Notice('Failed to generate valid MCQs from Claude. Please try again');
|
||||
new Notice(`Failed to generate valid ${MCQS} from ${CLAUDE}. Please try again`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +48,9 @@ export class ClaudeService implements IMCQGenerationService {
|
|||
questions,
|
||||
generatedAt: Date.now()
|
||||
};
|
||||
} catch {
|
||||
new Notice('Failed to generate MCQs with Claude. Please check console for details');
|
||||
} catch (error) {
|
||||
console.error(`Error generating ${MCQS} with ${CLAUDE}:`, error);
|
||||
new Notice(`Failed to generate ${MCQS} with ${CLAUDE}. Please check console for details`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +224,7 @@ export class ClaudeService implements IMCQGenerationService {
|
|||
}
|
||||
return questions.slice(0, numQuestionsToGenerate); // Use calculated number
|
||||
} catch {
|
||||
new Notice('Error parsing MCQ response from Claude. Please try again');
|
||||
new Notice(`Error parsing ${MCQ} response from ${CLAUDE}. Please try again`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Notice, requestUrl } from 'obsidian';
|
||||
import { GEMINI, MCQS, MCQ, API } from '../ui/constants';
|
||||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
|
|
@ -15,16 +16,16 @@ export class GeminiService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
if (!settings.geminiApiKey) {
|
||||
new Notice('Gemini API key is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${GEMINI} ${API} key is not set. Please add it in the settings`);
|
||||
return null;
|
||||
}
|
||||
if (!settings.geminiModel) {
|
||||
new Notice('Gemini model is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${GEMINI} model is not set. Please add it in the settings`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
new Notice('Generating MCQs using Gemini...');
|
||||
new Notice(`Generating ${MCQS} using ${GEMINI}...`);
|
||||
|
||||
// Determine the number of questions to generate
|
||||
let numQuestionsToGenerate: number;
|
||||
|
|
@ -40,7 +41,7 @@ export class GeminiService implements IMCQGenerationService {
|
|||
const questions = this.parseResponse(response, settings, numQuestionsToGenerate);
|
||||
|
||||
if (questions.length === 0) {
|
||||
new Notice('Failed to generate valid MCQs from Gemini. Please try again');
|
||||
new Notice(`Failed to generate valid ${MCQS} from ${GEMINI}. Please try again`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +51,7 @@ export class GeminiService implements IMCQGenerationService {
|
|||
generatedAt: Date.now()
|
||||
};
|
||||
} catch {
|
||||
new Notice('Failed to generate MCQs with Gemini. Please check console for details');
|
||||
new Notice(`Failed to generate ${MCQS} with ${GEMINI}. Please check console for details`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -174,7 +175,7 @@ export class GeminiService implements IMCQGenerationService {
|
|||
}
|
||||
return questions.slice(0, numQuestionsToGenerate); // Use calculated number
|
||||
} catch {
|
||||
new Notice('Error parsing MCQ response from Gemini. Please try again');
|
||||
new Notice(`Error parsing ${MCQ} response from ${GEMINI}. Please try again`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Notice, requestUrl } from 'obsidian';
|
||||
import { OLLAMA, API } from '../ui/constants';
|
||||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
|
|
@ -13,16 +14,16 @@ export class OllamaService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
if (!settings.ollamaApiUrl) {
|
||||
new Notice('Ollama API URL is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${OLLAMA} ${API} ${URL} not set in settings.`);
|
||||
return null;
|
||||
}
|
||||
if (!settings.ollamaModel) {
|
||||
new Notice('Ollama model is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${OLLAMA} model not set in settings.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
new Notice('Generating MCQs using Ollama...');
|
||||
new Notice(`Generating questions using ${OLLAMA}...`);
|
||||
|
||||
// Determine the number of questions to generate
|
||||
let numQuestionsToGenerate: number;
|
||||
|
|
@ -38,7 +39,7 @@ export class OllamaService implements IMCQGenerationService {
|
|||
const questions = this.parseResponse(response, settings, numQuestionsToGenerate);
|
||||
|
||||
if (questions.length === 0) {
|
||||
new Notice('Failed to generate valid MCQs from Ollama. Please try again');
|
||||
new Notice(`Failed to generate valid questions from ${OLLAMA}. Try again.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ export class OllamaService implements IMCQGenerationService {
|
|||
generatedAt: Date.now()
|
||||
};
|
||||
} catch {
|
||||
new Notice('Failed to generate MCQs with Ollama. Please check console for details');
|
||||
new Notice(`Failed to generate questions with ${OLLAMA}. Check console for details.`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -171,7 +172,7 @@ export class OllamaService implements IMCQGenerationService {
|
|||
}
|
||||
return questions.slice(0, numQuestionsToGenerate); // Use calculated number
|
||||
} catch {
|
||||
new Notice('Error parsing MCQ response from Ollama. Please try again');
|
||||
new Notice(`Error parsing response from ${OLLAMA}. Try again.`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Notice, requestUrl } from 'obsidian';
|
||||
import { OPENAI, API } from '../ui/constants';
|
||||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
|
|
@ -13,16 +14,16 @@ export class OpenAIService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
if (!settings.openaiApiKey) {
|
||||
new Notice('OpenAI API key is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${OPENAI} ${API} key not set in settings.`);
|
||||
return null;
|
||||
}
|
||||
if (!settings.openaiModel) {
|
||||
new Notice('OpenAI model is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${OPENAI} model not set in settings.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
new Notice('Generating MCQs using OpenAI...');
|
||||
new Notice(`Generating questions using ${OPENAI}...`);
|
||||
|
||||
// Determine the number of questions to generate
|
||||
let numQuestionsToGenerate: number;
|
||||
|
|
@ -38,7 +39,7 @@ export class OpenAIService implements IMCQGenerationService {
|
|||
const questions = this.parseResponse(response, settings, numQuestionsToGenerate);
|
||||
|
||||
if (questions.length === 0) {
|
||||
new Notice('Failed to generate valid MCQs from OpenAI, please try again');
|
||||
new Notice(`Failed to generate valid questions from ${OPENAI}. Try again.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ export class OpenAIService implements IMCQGenerationService {
|
|||
generatedAt: Date.now()
|
||||
};
|
||||
} catch {
|
||||
new Notice('Failed to generate MCQs with OpenAI, please check console for details');
|
||||
new Notice(`Failed to generate questions with ${OPENAI}. Check console for details.`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +171,7 @@ export class OpenAIService implements IMCQGenerationService {
|
|||
}
|
||||
return questions.slice(0, numQuestionsToGenerate); // Use calculated number
|
||||
} catch {
|
||||
new Notice('Error parsing MCQ response from OpenAI. Please try again');
|
||||
new Notice(`Error parsing response from ${OPENAI}. Try again.`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Notice, requestUrl } from 'obsidian';
|
||||
import { OPENROUTER, API } from '../ui/constants';
|
||||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
|
|
@ -33,13 +34,13 @@ export class OpenRouterService implements IMCQGenerationService {
|
|||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
// Check if API key is set
|
||||
if (!settings.openRouterApiKey) {
|
||||
new Notice('OpenRouter API key is not set. Please add it in the settings');
|
||||
new Notice(`${OPENROUTER} ${API} key not set in settings.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Show loading notice
|
||||
new Notice('Generating MCQs using OpenRouter...');
|
||||
new Notice(`Generating questions using ${OPENROUTER}...`);
|
||||
|
||||
// Determine the number of questions to generate
|
||||
let numQuestionsToGenerate: number;
|
||||
|
|
@ -63,7 +64,7 @@ export class OpenRouterService implements IMCQGenerationService {
|
|||
|
||||
// Ensure we have at least one question if requested
|
||||
if (questions.length === 0) {
|
||||
new Notice('Failed to generate valid MCQs from OpenRouter. Please try again');
|
||||
new Notice('Failed to generate valid questions. Try again.');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +77,7 @@ export class OpenRouterService implements IMCQGenerationService {
|
|||
|
||||
return mcqSet;
|
||||
} catch {
|
||||
new Notice('Failed to generate MCQs with OpenRouter. Please check console for details');
|
||||
new Notice('Failed to generate questions. Check console for details.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -294,7 +295,7 @@ For example:
|
|||
// Use numQuestionsToGenerate instead of settings.mcqQuestionsPerNote
|
||||
return questions.slice(0, numQuestionsToGenerate);
|
||||
} catch {
|
||||
new Notice('Error parsing MCQ response from OpenRouter. Please try again');
|
||||
new Notice(`Error parsing response from ${OPENROUTER}. Try again.`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Notice, requestUrl } from 'obsidian';
|
||||
import { TOGETHER_AI, API } from '../ui/constants';
|
||||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
|
|
@ -13,16 +14,16 @@ export class TogetherService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
if (!settings.togetherApiKey) {
|
||||
new Notice('Together AI API key is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${TOGETHER_AI} ${API} key not set in settings.`);
|
||||
return null;
|
||||
}
|
||||
if (!settings.togetherModel) {
|
||||
new Notice('Together AI model is not set. Please add it in the Spaceforge settings');
|
||||
new Notice(`${TOGETHER_AI} model not set in settings.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
new Notice('Generating MCQs using Together AI...');
|
||||
new Notice(`Generating questions using ${TOGETHER_AI}...`);
|
||||
|
||||
// Determine the number of questions to generate
|
||||
let numQuestionsToGenerate: number;
|
||||
|
|
@ -38,7 +39,7 @@ export class TogetherService implements IMCQGenerationService {
|
|||
const questions = this.parseResponse(response, settings, numQuestionsToGenerate);
|
||||
|
||||
if (questions.length === 0) {
|
||||
new Notice('Failed to generate valid MCQs from Together AI. Please try again');
|
||||
new Notice('Failed to generate valid questions. Try again.');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ export class TogetherService implements IMCQGenerationService {
|
|||
generatedAt: Date.now()
|
||||
};
|
||||
} catch {
|
||||
new Notice('Failed to generate MCQs with Together AI. Please check console for details');
|
||||
new Notice('Failed to generate questions. Check console for details.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -200,7 +201,7 @@ export class TogetherService implements IMCQGenerationService {
|
|||
}
|
||||
return questions.slice(0, numQuestionsToGenerate); // Use calculated number
|
||||
} catch {
|
||||
new Notice('Error parsing MCQ response from Together AI. Please try again');
|
||||
new Notice(`Error parsing response from ${TOGETHER_AI}. Try again.`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export class ReviewBatchController implements IReviewBatchController {
|
|||
|
||||
const todayNotes = reviewController.getTodayNotes();
|
||||
if (todayNotes.length === 0) {
|
||||
new Notice("No notes due for review today!");
|
||||
new Notice("No notes due for review today.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ export class ReviewBatchController implements IReviewBatchController {
|
|||
}
|
||||
|
||||
if (!this.plugin.mcqController) {
|
||||
new Notice("MCQ controller not initialized. Please check MCQ settings");
|
||||
new Notice("Questions feature not initialized. Check settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,12 +68,12 @@ export class MCQController {
|
|||
externalOnCompleteCallback?: (path: string, success: boolean) => void
|
||||
): Promise<void> {
|
||||
if (!this.plugin.settings.enableMCQ) {
|
||||
new Notice('MCQ feature is disabled in settings.');
|
||||
new Notice('Feature is disabled in settings.');
|
||||
if (externalOnCompleteCallback) externalOnCompleteCallback(notePath, false);
|
||||
return;
|
||||
}
|
||||
if (!this.mcqGenerationService) {
|
||||
new Notice('MCQ generation service is not available. Check API provider settings.');
|
||||
new Notice('Generation service is not available. Check API provider settings.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -81,23 +81,23 @@ export class MCQController {
|
|||
let mcqSet = this.mcqService.getMCQSetForNote(notePath);
|
||||
|
||||
if (mcqSet && mcqSet.needsQuestionRegeneration) {
|
||||
new Notice('Questions for this note are flagged for regeneration. Generating new set...');
|
||||
new Notice('Questions flagged for regeneration. Generating new set...');
|
||||
mcqSet = await this.generateMCQs(notePath, true);
|
||||
if (mcqSet) {
|
||||
mcqSet.needsQuestionRegeneration = false;
|
||||
this.mcqService.saveMCQSet(mcqSet);
|
||||
await this.plugin.savePluginData();
|
||||
} else {
|
||||
new Notice('Failed to regenerate MCQs. Using existing set if available.');
|
||||
new Notice('Failed to regenerate. Using existing set if available.');
|
||||
mcqSet = this.mcqService.getMCQSetForNote(notePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mcqSet || mcqSet.questions.length === 0) {
|
||||
new Notice('No MCQs found for this note. Generating new set...');
|
||||
new Notice('No questions found. Generating new set...');
|
||||
mcqSet = await this.generateMCQs(notePath);
|
||||
if (!mcqSet) {
|
||||
new Notice('Failed to generate MCQs for this note.');
|
||||
new Notice('Failed to generate questions.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +135,7 @@ export class MCQController {
|
|||
// Pass the new internal callback to the modal constructor
|
||||
new MCQModal(this.plugin, notePath, mcqSet, internalOnComplete).open();
|
||||
} catch {
|
||||
new Notice('Error starting MCQ review. Please check console for details.');
|
||||
new Notice('Error starting review. Please check console for details.');
|
||||
if (externalOnCompleteCallback) externalOnCompleteCallback(notePath, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -149,7 +149,7 @@ export class MCQController {
|
|||
*/
|
||||
async generateMCQs(notePath: string, forceRegeneration = false): Promise<MCQSet | null> {
|
||||
if (!this.plugin.settings.enableMCQ || !this.mcqGenerationService) {
|
||||
new Notice('MCQ feature is disabled or the generation service is not available. Check API provider settings.');
|
||||
new Notice('Feature is disabled or the generation service is not available. Check API provider settings.');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -202,11 +202,11 @@ export class MCQController {
|
|||
*/
|
||||
async startConsolidatedMCQReviewForSelectedDate(): Promise<void> {
|
||||
if (!this.plugin.settings.enableMCQ) {
|
||||
new Notice('MCQ feature is disabled in settings.');
|
||||
new Notice('Feature is disabled in settings.');
|
||||
return;
|
||||
}
|
||||
if (!this.mcqGenerationService) {
|
||||
new Notice('MCQ generation service is not available. Check API provider settings.');
|
||||
new Notice('Generation service is not available. Check API provider settings.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ export class MCQController {
|
|||
const mcqSetsForReview: { path: string; mcqSet: MCQSet; fileName: string }[] = [];
|
||||
let notesWithMCQsCount = 0;
|
||||
|
||||
new Notice(`Fetching MCQs for ${dueNotes.length} due note(s)...`);
|
||||
new Notice(`Fetching questions for ${dueNotes.length} due note(s)...`);
|
||||
|
||||
for (const noteSchedule of dueNotes) {
|
||||
const notePath = noteSchedule.path;
|
||||
|
|
@ -228,7 +228,7 @@ export class MCQController {
|
|||
|
||||
// If MCQ set needs regeneration, attempt to regenerate it.
|
||||
if (mcqSet && mcqSet.needsQuestionRegeneration) {
|
||||
new Notice(`Regenerating flagged MCQs for ${notePath}...`);
|
||||
new Notice(`Regenerating flagged questions for ${notePath}...`);
|
||||
const regeneratedMcqSet = await this.generateMCQs(notePath, true); // forceRegeneration = true
|
||||
if (regeneratedMcqSet) {
|
||||
mcqSet = regeneratedMcqSet; // Use the new set
|
||||
|
|
@ -236,19 +236,19 @@ export class MCQController {
|
|||
this.mcqService.saveMCQSet(mcqSet);
|
||||
// Overall plugin data save will happen once after the loop
|
||||
} else {
|
||||
new Notice(`Failed to regenerate MCQs for ${notePath}.Using existing set if available(might be outdated or empty).`);
|
||||
new Notice(`Failed to regenerate questions for ${notePath}. Using existing set if available (might be outdated or empty).`);
|
||||
// mcqSet remains the old one, or null if it didn't exist.
|
||||
}
|
||||
}
|
||||
|
||||
// If no MCQ set exists after potential regeneration, or if it's empty, try to generate one.
|
||||
if (!mcqSet || mcqSet.questions.length === 0) {
|
||||
new Notice(`No MCQs found or set is empty for ${notePath}.Attempting to generate new set...`);
|
||||
new Notice(`No questions found or set is empty for ${notePath}. Attempting to generate new set...`);
|
||||
const newMcqSet = await this.generateMCQs(notePath, false); // forceRegeneration = false (respects cache if fresh)
|
||||
if (newMcqSet) {
|
||||
mcqSet = newMcqSet;
|
||||
} else {
|
||||
new Notice(`Failed to generate MCQs for ${notePath}.This note will be skipped in MCQ review.`);
|
||||
new Notice(`Failed to generate questions for ${notePath}. This note will be skipped.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -264,13 +264,13 @@ export class MCQController {
|
|||
}
|
||||
|
||||
if (mcqSetsForReview.length === 0) {
|
||||
new Notice('No MCQs available or generated for the due notes.');
|
||||
new Notice('No questions available for the due notes.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.plugin.savePluginData(); // Save any changes from MCQ generation
|
||||
|
||||
new Notice(`Starting consolidated MCQ review for ${notesWithMCQsCount} note(s) with ${mcqSetsForReview.reduce((sum, s) => sum + s.mcqSet.questions.length, 0)} questions.`);
|
||||
new Notice(`Starting consolidated review for ${notesWithMCQsCount} note(s) with ${mcqSetsForReview.reduce((sum, s) => sum + s.mcqSet.questions.length, 0)} questions.`);
|
||||
|
||||
const onConsolidatedComplete = (
|
||||
results: Array<{ path: string; success: boolean; response: ReviewResponse; score?: number }>
|
||||
|
|
@ -295,7 +295,7 @@ export class MCQController {
|
|||
if (reviewsProcessed > 0) {
|
||||
new Notice(`${reviewsProcessed} note review(s) updated based on consolidated MCQ session.`);
|
||||
} else {
|
||||
new Notice("No note reviews were updated from the MCQ session");
|
||||
new Notice("No reviews updated.");
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ export class ReviewController implements IReviewController {
|
|||
*
|
||||
* @param path Path to the postponed note
|
||||
*/
|
||||
async handleNotePostponed(path: string): Promise<void> {
|
||||
await this.coreController.handleNotePostponed(path);
|
||||
handleNotePostponed(path: string): void {
|
||||
this.coreController.handleNotePostponed(path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
49
main.ts
49
main.ts
|
|
@ -145,7 +145,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
await this.savePluginData();
|
||||
new Notice(`Added "${selectedFile.path}" to review schedule.`);
|
||||
} else {
|
||||
new Notice("Selected item is not a markdown file.");
|
||||
new Notice("Selected item is not a Markdown file.");
|
||||
}
|
||||
} else {
|
||||
new Notice("No file selected in file explorer");
|
||||
|
|
@ -236,7 +236,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
}
|
||||
|
||||
// @typescript-eslint/no-misused-promises -- onunload can be async
|
||||
async onunload(): Promise<void> {
|
||||
onunload(): void {
|
||||
if (this.cssHotReloadIntervalId !== null) {
|
||||
window.clearInterval(this.cssHotReloadIntervalId);
|
||||
this.cssHotReloadIntervalId = null;
|
||||
|
|
@ -251,10 +251,11 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
|
||||
let existingData: Partial<SpaceforgePluginData> = {};
|
||||
try {
|
||||
const loaded = await this.loadData();
|
||||
if (loaded) {
|
||||
existingData = loaded;
|
||||
}
|
||||
this.loadData().then(loaded => {
|
||||
if (loaded) {
|
||||
existingData = loaded;
|
||||
}
|
||||
}).catch(() => { /* handle error */ });
|
||||
}
|
||||
catch { /* handle error */ }
|
||||
|
||||
|
|
@ -275,7 +276,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
} catch { /* handle error */ }
|
||||
|
||||
try {
|
||||
await this.savePluginData();
|
||||
this.savePluginData().catch(e => console.error("Error saving data on unload:", e));
|
||||
} catch { /* handle error */ }
|
||||
|
||||
if (this.pomodoroService) this.pomodoroService.destroy();
|
||||
|
|
@ -444,7 +445,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
if (parsedBackup.reviewData) {
|
||||
this.settings = { ...DEFAULT_SETTINGS, ...parsedBackup.settings };
|
||||
this.pluginState = { ...DEFAULT_PLUGIN_STATE_DATA, ...parsedBackup.reviewData };
|
||||
new Notice("Spaceforge: Recovered data from backup", 5000);
|
||||
new Notice("Recovered data from backup", 5000);
|
||||
} else {
|
||||
throw new Error("Invalid backup format");
|
||||
}
|
||||
|
|
@ -455,7 +456,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
console.warn("Spaceforge: Backup recovery failed, using defaults:", backupError);
|
||||
this.settings = { ...DEFAULT_SETTINGS };
|
||||
this.pluginState = { ...DEFAULT_PLUGIN_STATE_DATA };
|
||||
new Notice("Spaceforge: Error loading data, using defaults to prevent crash", 5000);
|
||||
new Notice("Error loading data, using defaults to prevent crash", 5000);
|
||||
}
|
||||
|
||||
// Repopulate services
|
||||
|
|
@ -526,7 +527,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
const dirPathOnly = effectiveSavePath.substring(0, effectiveSavePath.lastIndexOf('/'));
|
||||
if (dirPathOnly && !this.app.vault.getAbstractFileByPath(dirPathOnly)) {
|
||||
await this.app.vault.createFolder(dirPathOnly);
|
||||
new Notice(`Spaceforge: Created directory for custom data: ${dirPathOnly}`, 3000);
|
||||
new Notice(`Created directory for custom data: ${dirPathOnly}`, 3000);
|
||||
}
|
||||
|
||||
// SIMPLE SAVE: Use Obsidian's standard file operations
|
||||
|
|
@ -545,16 +546,16 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
if (oldFile instanceof TFile) {
|
||||
// Instead of deleting, we'll keep the old file as a backup
|
||||
// Users can manually clean it up later if needed
|
||||
new Notice(`Spaceforge: Successfully saved to custom path, original data file kept as backup for safety`, 5000);
|
||||
new Notice(`Successfully saved to custom path, original data file kept as backup for safety`, 5000);
|
||||
}
|
||||
} catch (writeError) {
|
||||
new Notice(`Error saving data to custom path ${effectiveSavePath}: ${writeError.message}. Falling back to default path for this save.`, 10000);
|
||||
// Fallback save to default location if custom path write fails
|
||||
try {
|
||||
await this.saveData(dataToSave); // Plugin's internal save
|
||||
new Notice(`Spaceforge: Data saved to default plugin folder due to error with custom path`, 5000);
|
||||
new Notice(`Data saved to default plugin folder due to error with custom path`, 5000);
|
||||
} catch {
|
||||
new Notice(`CRITICAL: Spaceforge failed to save data to both custom and default locations`, 10000);
|
||||
new Notice(`Critical error: failed to save data to both custom and default locations`, 10000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -567,7 +568,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
// If user wants to backup settings, they can use the export feature.
|
||||
|
||||
} catch {
|
||||
new Notice("Error saving Spaceforge data. Check console for details", 5000);
|
||||
new Notice("Error saving data. Check console for details", 5000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -586,7 +587,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
});
|
||||
void this.app.workspace.revealLeaf(leaf); // Reveal the newly created leaf
|
||||
} else {
|
||||
new Notice("Spaceforge: Could not open sidebar view");
|
||||
new Notice("Could not open sidebar view");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -616,7 +617,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
if (activeFile && activeFile instanceof TFile && activeFile.extension === 'md') {
|
||||
void this.reviewController.reviewNote(activeFile.path);
|
||||
} else {
|
||||
new Notice('No active markdown file to review');
|
||||
new Notice('No active Markdown file to review');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -631,7 +632,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
await this.savePluginData();
|
||||
new Notice(`Added "${activeFile.path}" to review schedule`);
|
||||
} else {
|
||||
new Notice('No active markdown file to add to review');
|
||||
new Notice('No active Markdown file to add to review');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -664,7 +665,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
// Pass the mcqService for data management, and the new mcqGenerationService for API calls
|
||||
this.mcqController = new MCQController(this, this.mcqService, this.mcqGenerationService);
|
||||
} else {
|
||||
new Notice('MCQ generation service could not be initialized. Check API provider settings in Spaceforge settings');
|
||||
new Notice('Generation service could not be initialized. Check API provider settings in settings');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -673,37 +674,37 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
switch (this.settings.mcqApiProvider) {
|
||||
case ApiProvider.OpenRouter:
|
||||
if (!this.settings.openRouterApiKey) {
|
||||
new Notice('OpenRouter API key is not set in Spaceforge settings');
|
||||
new Notice('OpenRouter API key is not set in settings');
|
||||
return undefined;
|
||||
}
|
||||
return new OpenRouterService(this);
|
||||
case ApiProvider.OpenAI:
|
||||
if (!this.settings.openaiApiKey) {
|
||||
new Notice('OpenAI API key is not set in Spaceforge settings');
|
||||
new Notice('OpenAI API key is not set in settings');
|
||||
return undefined;
|
||||
}
|
||||
return new OpenAIService(this);
|
||||
case ApiProvider.Ollama:
|
||||
if (!this.settings.ollamaApiUrl || !this.settings.ollamaModel) {
|
||||
new Notice('Ollama API URL or model is not set in Spaceforge settings');
|
||||
new Notice('Ollama API URL or model is not set in settings');
|
||||
return undefined;
|
||||
}
|
||||
return new OllamaService(this);
|
||||
case ApiProvider.Gemini:
|
||||
if (!this.settings.geminiApiKey) {
|
||||
new Notice('Gemini API key is not set in Spaceforge settings');
|
||||
new Notice('Gemini API key is not set in settings');
|
||||
return undefined;
|
||||
}
|
||||
return new GeminiService(this);
|
||||
case ApiProvider.Claude:
|
||||
if (!this.settings.claudeApiKey || !this.settings.claudeModel) {
|
||||
new Notice('Claude API key or model is not set in Spaceforge settings');
|
||||
new Notice('Claude API key or model is not set in settings');
|
||||
return undefined;
|
||||
}
|
||||
return new ClaudeService(this);
|
||||
case ApiProvider.Together:
|
||||
if (!this.settings.togetherApiKey || !this.settings.togetherModel) {
|
||||
new Notice('Together AI API key or model is not set in Spaceforge settings');
|
||||
new Notice('Together AI API key or model is not set in settings');
|
||||
return undefined;
|
||||
}
|
||||
return new TogetherService(this);
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export class ReviewScheduleService {
|
|||
// Check if file exists and is a markdown file
|
||||
const file = this.plugin.app.vault.getAbstractFileByPath(path);
|
||||
if (!file || !(file instanceof TFile) || file.extension !== "md") {
|
||||
new Notice("Only markdown files can be added to the review schedule.");
|
||||
new Notice("Only Markdown files can be added to review.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ export class ReviewScheduleService {
|
|||
this.plugin.events.emit('sidebar-update');
|
||||
}
|
||||
|
||||
new Notice(`Note added to review schedule.`);
|
||||
new Notice(`Note added to review.`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -708,7 +708,7 @@ export class ReviewScheduleService {
|
|||
this.customNoteOrder = this.customNoteOrder.filter(p => p !== path);
|
||||
|
||||
// Data saving is now handled by main.ts after this method returns
|
||||
new Notice("Note removed from review schedule.");
|
||||
new Notice("Note removed from review.");
|
||||
|
||||
// Note: The controller will be notified separately to update its state
|
||||
// This prevents immediate reordering based on link analysis after removal.
|
||||
|
|
@ -726,7 +726,7 @@ export class ReviewScheduleService {
|
|||
this.schedules = {};
|
||||
this.customNoteOrder = []; // Also clear custom order
|
||||
// Data saving is now handled by main.ts after this method returns
|
||||
new Notice("All review schedules have been cleared.");
|
||||
new Notice("All review schedules cleared.");
|
||||
|
||||
// Notify any listeners (for UI updates)
|
||||
if (this.plugin.events) {
|
||||
|
|
|
|||
|
|
@ -170,12 +170,12 @@ export class BatchReviewModal extends Modal {
|
|||
);
|
||||
consolidatedModal.open();
|
||||
} catch {
|
||||
new Notice("Error showing MCQ review. Falling back to manual review.");
|
||||
new Notice("Error showing review. Falling back to manual review.");
|
||||
this.open();
|
||||
void this.processNextManual();
|
||||
}
|
||||
} else {
|
||||
new Notice("MCQ controller not available. Falling back to manual review.");
|
||||
new Notice("Controller not available. Falling back to manual review.");
|
||||
this.open();
|
||||
void this.processNextManual();
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ export class BatchReviewModal extends Modal {
|
|||
const note = this.notes[this.currentIndex];
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
new Setting(contentEl).setName("MCQ review in progress").setHeading();
|
||||
new Setting(contentEl).setName("Review in progress").setHeading();
|
||||
const progressEl = contentEl.createDiv("batch-review-progress");
|
||||
progressEl.createEl("p", { text: `Processing note ${this.currentIndex + 1}/${this.notes.length}` });
|
||||
const file = this.plugin.app.vault.getAbstractFileByPath(note.path);
|
||||
|
|
@ -252,7 +252,7 @@ export class BatchReviewModal extends Modal {
|
|||
void this.processNextManual();
|
||||
}
|
||||
} else {
|
||||
new Notice("MCQ controller not available, falling back to manual review.");
|
||||
new Notice("Controller not available, falling back to manual review.");
|
||||
this.open();
|
||||
void this.processNextManual();
|
||||
}
|
||||
|
|
@ -283,17 +283,17 @@ export class BatchReviewModal extends Modal {
|
|||
}
|
||||
|
||||
const buttonsContainer = contentEl.createDiv("batch-review-buttons");
|
||||
const blackoutButton = buttonsContainer.createEl("button", { text: "0: Complete blackout", cls: "review-button review-button-complete-blackout" });
|
||||
const blackoutButton = buttonsContainer.createEl("button", { text: "0: complete blackout", cls: "review-button review-button-complete-blackout" });
|
||||
blackoutButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CompleteBlackout));
|
||||
const incorrectButton = buttonsContainer.createEl("button", { text: "1: Incorrect response", cls: "review-button review-button-incorrect" });
|
||||
const incorrectButton = buttonsContainer.createEl("button", { text: "1: incorrect response", cls: "review-button review-button-incorrect" });
|
||||
incorrectButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.IncorrectResponse));
|
||||
const incorrectFamiliarButton = buttonsContainer.createEl("button", { text: "2: Incorrect but familiar", cls: "review-button review-button-incorrect-familiar" });
|
||||
const incorrectFamiliarButton = buttonsContainer.createEl("button", { text: "2: incorrect but familiar", cls: "review-button review-button-incorrect-familiar" });
|
||||
incorrectFamiliarButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.IncorrectButFamiliar));
|
||||
const correctDifficultyButton = buttonsContainer.createEl("button", { text: "3: Correct with difficulty", cls: "review-button review-button-correct-difficulty" });
|
||||
const correctDifficultyButton = buttonsContainer.createEl("button", { text: "3: correct with difficulty", cls: "review-button review-button-correct-difficulty" });
|
||||
correctDifficultyButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CorrectWithDifficulty));
|
||||
const correctHesitationButton = buttonsContainer.createEl("button", { text: "4: Correct with hesitation", cls: "review-button review-button-correct-hesitation" });
|
||||
const correctHesitationButton = buttonsContainer.createEl("button", { text: "4: correct with hesitation", cls: "review-button review-button-correct-hesitation" });
|
||||
correctHesitationButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CorrectWithHesitation));
|
||||
const perfectRecallButton = buttonsContainer.createEl("button", { text: "5: Perfect recall", cls: "review-button review-button-perfect-recall" });
|
||||
const perfectRecallButton = buttonsContainer.createEl("button", { text: "5: perfect recall", cls: "review-button review-button-perfect-recall" });
|
||||
perfectRecallButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.PerfectRecall));
|
||||
const skipButton = buttonsContainer.createEl("button", { text: "Skip", cls: "review-button review-button-skip" });
|
||||
skipButton.addEventListener("click", () => {
|
||||
|
|
@ -320,7 +320,7 @@ export class BatchReviewModal extends Modal {
|
|||
showSummary(): void {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
new Setting(contentEl).setName('MCQ review complete').setHeading().setClass('mcq-review-complete-header');
|
||||
new Setting(contentEl).setName('Review complete').setHeading().setClass('mcq-review-complete-header');
|
||||
const statsEl = contentEl.createDiv("batch-review-summary-stats");
|
||||
const totalNotes = this.results.length;
|
||||
const successfulNotes = this.results.filter(r => r.success).length;
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ export class ConsolidatedMCQModal extends Modal {
|
|||
|
||||
// Verify we have a valid question
|
||||
if (!question || !question.choices || question.choices.length < 2) {
|
||||
new Notice('Error: Invalid question data. Moving to next question.');
|
||||
new Notice('Invalid question data. Moving to next question.');
|
||||
|
||||
// Skip to next question
|
||||
this.currentQuestionIndex++;
|
||||
|
|
@ -522,7 +522,7 @@ export class ConsolidatedMCQModal extends Modal {
|
|||
contentEl.empty();
|
||||
|
||||
// Display results header with stylized heading
|
||||
new Setting(contentEl).setName('MCQ review complete').setHeading().setClass('mcq-review-complete-header');
|
||||
new Setting(contentEl).setName('Review complete').setHeading().setClass('mcq-review-complete-header');
|
||||
|
||||
// Display overall score with enhanced styling
|
||||
const totalCorrectOverall = this.answers.filter(a => a.correct && a.attempts <= 1).length;
|
||||
|
|
@ -538,16 +538,16 @@ export class ConsolidatedMCQModal extends Modal {
|
|||
const performanceIndicator = scoreEl.createDiv('mcq-performance-indicator');
|
||||
|
||||
if (scorePercentOverall >= 90) {
|
||||
performanceIndicator.setText('🎓 Excellent performance!');
|
||||
performanceIndicator.setText('Excellent performance');
|
||||
performanceIndicator.addClass('excellent');
|
||||
} else if (scorePercentOverall >= 70) {
|
||||
performanceIndicator.setText('👍 Good work!');
|
||||
performanceIndicator.setText('Good work');
|
||||
performanceIndicator.addClass('good');
|
||||
} else if (scorePercentOverall >= 50) {
|
||||
performanceIndicator.setText('🔄 Keep practicing');
|
||||
performanceIndicator.setText('Keep practicing');
|
||||
performanceIndicator.addClass('needs-improvement');
|
||||
} else {
|
||||
performanceIndicator.setText('📚 More review recommended');
|
||||
performanceIndicator.setText('More review recommended');
|
||||
performanceIndicator.addClass('review-recommended');
|
||||
}
|
||||
|
||||
|
|
|
|||
18
ui/constants.ts
Normal file
18
ui/constants.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export const SM2 = 'SM-2';
|
||||
export const FSRS = 'FSRS';
|
||||
export const POMODORO = 'Pomodoro';
|
||||
export const MCQ = 'MCQ';
|
||||
export const MCQS = 'MCQs';
|
||||
export const API = 'API';
|
||||
export const SPACEFORGE = 'Spaceforge';
|
||||
export const UI_URL = 'URL';
|
||||
export const UI_JSON = 'JSON';
|
||||
export const UI = 'UI';
|
||||
export const CSV = 'CSV';
|
||||
export const CLAUDE = 'Claude';
|
||||
export const GEMINI = 'Gemini';
|
||||
export const OLLAMA = 'Ollama';
|
||||
export const OPENAI = 'OpenAI';
|
||||
export const TOGETHER_AI = 'Together AI';
|
||||
export const OPENROUTER = 'OpenRouter';
|
||||
export const WPM = 'WPM';
|
||||
|
|
@ -75,7 +75,7 @@ export class MCQModal extends Modal {
|
|||
new Notice('Could not find note file to regenerate MCQs.');
|
||||
}
|
||||
} else {
|
||||
new Notice('MCQ generation service not available.');
|
||||
new Notice('Generation service not available.');
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
|
@ -205,7 +205,7 @@ export class MCQModal extends Modal {
|
|||
}
|
||||
const question = this.mcqSet.questions[questionIndex];
|
||||
if (!question || !question.choices || question.choices.length < 2) {
|
||||
new Notice('Error: Invalid question data. Moving to next question.');
|
||||
new Notice('Invalid question data. Moving to next question.');
|
||||
this.session.currentQuestionIndex++;
|
||||
if (this.session.currentQuestionIndex < this.mcqSet.questions.length) {
|
||||
this.displayCurrentQuestion(containerEl);
|
||||
|
|
@ -443,7 +443,7 @@ export class MCQModal extends Modal {
|
|||
});
|
||||
} catch {
|
||||
new Setting(contentEl).setName('Error completing session').setHeading();
|
||||
contentEl.createEl('p', { text: 'There was an error completing the MCQ session. Please try again.' });
|
||||
contentEl.createEl('p', { text: 'There was an error completing the session. Please try again.' });
|
||||
const errorCloseBtn = contentEl.createEl('button', { cls: 'mcq-close-btn', text: 'Close' });
|
||||
errorCloseBtn.addEventListener('click', () => this.close());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { App, Modal, Notice, TFile, setIcon, Setting } from 'obsidian';
|
||||
import { SM2, FSRS } from './constants';
|
||||
import SpaceforgePlugin from '../main';
|
||||
import { ReviewResponse, FsrsRating } from '../models/review-schedule'; // Added FsrsRating
|
||||
import { State as FsrsState } from 'ts-fsrs'; // For displaying FSRS state name
|
||||
|
|
@ -100,7 +101,7 @@ export class ReviewModal extends Modal {
|
|||
void initializedMcqController.startMCQReview(this.path);
|
||||
this.close();
|
||||
} else {
|
||||
new Notice("MCQ feature could not be initialized. Please check settings.");
|
||||
new Notice("Feature could not be initialized. Please check settings.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -150,13 +151,13 @@ export class ReviewModal extends Modal {
|
|||
if (schedule.lastReviewDate) infoText.createEl("p", { text: `Last reviewed: ${new Date(schedule.lastReviewDate).toLocaleDateString()}` });
|
||||
|
||||
if (schedule.schedulingAlgorithm === 'fsrs' && schedule.fsrsData) {
|
||||
infoText.createEl("p", { text: `Algorithm: FSRS`, cls: "review-phase-fsrs" });
|
||||
infoText.createEl("p", { text: `Stability: ${schedule.fsrsData.stability.toFixed(2)}` });
|
||||
infoText.createEl("p", { text: `Difficulty: ${schedule.fsrsData.difficulty.toFixed(2)}` });
|
||||
infoText.createEl("p", { text: `State: ${FsrsState[schedule.fsrsData.state]}` }); // Display FSRS state name
|
||||
infoText.createEl("p", { text: `Algorithm: ${FSRS}`, cls: "review-phase-fsrs" });
|
||||
infoText.createEl("p", { text: `${'Stability'}: ${schedule.fsrsData.stability.toFixed(2)}` });
|
||||
infoText.createEl("p", { text: `${'Difficulty'}: ${schedule.fsrsData.difficulty.toFixed(2)}` });
|
||||
infoText.createEl("p", { text: `${'State'}: ${FsrsState[schedule.fsrsData.state]}` }); // Display FSRS state name
|
||||
infoText.createEl("p", { text: `Interval: ${schedule.fsrsData.scheduled_days} days` });
|
||||
} else { // SM-2 or fallback
|
||||
infoText.createEl("p", { text: `Algorithm: SM-2`, cls: "review-phase-sm2" });
|
||||
infoText.createEl("p", { text: `Algorithm: ${SM2}`, cls: "review-phase-sm2" });
|
||||
let phaseText: string; let phaseClass: string;
|
||||
if (schedule.scheduleCategory === 'initial') {
|
||||
const totalInitialSteps = this.plugin.settings.initialScheduleCustomIntervals.length;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { App, Notice, PluginSettingTab, Setting, setIcon } from 'obsidian';
|
||||
import { ConfirmationModal } from './confirmation-modal';
|
||||
import SpaceforgePlugin from '../main';
|
||||
// Import ApiProvider, DEFAULT_SETTINGS, SpaceforgeSettings, and MCQQuestionAmountMode
|
||||
import { ApiProvider, DEFAULT_SETTINGS, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings';
|
||||
import { SpaceforgePluginData, DEFAULT_PLUGIN_STATE_DATA } from '../models/plugin-data'; // Import data structures
|
||||
import { SpaceforgePluginData, DEFAULT_PLUGIN_STATE_DATA } from '../models/plugin-data';
|
||||
import { SM2, FSRS, POMODORO, MCQS, API, SPACEFORGE, CLAUDE, GEMINI, OLLAMA, TOGETHER_AI, WPM } from './constants';
|
||||
|
||||
/**
|
||||
* Settings tab for the plugin
|
||||
|
|
@ -128,7 +128,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
new Notice('All plugin data exported successfully');
|
||||
new Notice('All data exported successfully');
|
||||
});
|
||||
|
||||
// Import all data button
|
||||
|
|
@ -176,7 +176,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
this.display(); // Refresh settings display
|
||||
|
||||
new Notice('All plugin data imported successfully. Plugin may require a reload for all changes to take effect.');
|
||||
new Notice('All data imported successfully. Plugin may require a reload for all changes to take effect.');
|
||||
} catch (error) {
|
||||
new Notice(`Failed to import data: ${error.message} `);
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
this.display(); // Refresh settings display
|
||||
|
||||
new Notice('All plugin data reset to defaults.');
|
||||
new Notice('All data reset to defaults.');
|
||||
}
|
||||
).open();
|
||||
});
|
||||
|
|
@ -354,7 +354,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
// Save the cleared data
|
||||
await this.plugin.savePluginData();
|
||||
|
||||
new Notice('All plugin data cleared successfully.');
|
||||
new Notice('All data cleared successfully.');
|
||||
|
||||
// Then refresh UI elements
|
||||
this.display(); // Refresh settings UI
|
||||
|
|
@ -383,8 +383,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setName('Default scheduling algorithm')
|
||||
.setDesc('Choose the default algorithm for newly created notes.')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('sm2', 'SM-2')
|
||||
.addOption('fsrs', 'FSRS')
|
||||
.addOption('sm2', SM2)
|
||||
.addOption('fsrs', FSRS)
|
||||
.setValue(this.plugin.settings.defaultSchedulingAlgorithm)
|
||||
.onChange(async (value: 'sm2' | 'fsrs') => {
|
||||
this.plugin.settings.defaultSchedulingAlgorithm = value;
|
||||
|
|
@ -400,13 +400,13 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
const sm2ParamsContainer = spacedRepSection.createEl('details', { cls: 'sf-settings-collapsible-subsection' });
|
||||
const sm2Summary = sm2ParamsContainer.createEl('summary');
|
||||
// Changed to h3
|
||||
sm2Summary.setText('SM-2 parameters');
|
||||
sm2Summary.setText(`${SM2} parameters`);
|
||||
sm2ParamsContainer.open = false; // Initially closed
|
||||
|
||||
|
||||
new Setting(sm2ParamsContainer)
|
||||
.setName('SM-2: base ease factor')
|
||||
.setDesc('Initial ease factor for new SM-2 notes (2.5 is SM-2 default). Higher ease increases interval growth. Value shown is internal format (250 = 2.5).')
|
||||
.setName(`Base ease factor (${SM2})`)
|
||||
.setDesc(`Initial ease factor for new ${SM2} notes (2.5 is ${SM2} default). Higher ease increases interval growth. Value shown is internal format (250 = 2.5).`)
|
||||
.addSlider(slider => slider
|
||||
.setLimits(130, 500, 10)
|
||||
.setValue(this.plugin.settings.baseEase)
|
||||
|
|
@ -417,8 +417,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(sm2ParamsContainer)
|
||||
.setName('SM-2: use initial learning schedule')
|
||||
.setDesc('For new SM-2 notes, use a fixed set of initial intervals before applying the full algorithm.')
|
||||
.setName(`Use initial learning schedule (${SM2})`)
|
||||
.setDesc(`For new ${SM2} notes, use a fixed set of initial intervals before applying the full algorithm.`)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.useInitialSchedule)
|
||||
.onChange(async (value: boolean) => {
|
||||
|
|
@ -429,8 +429,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
if (this.plugin.settings.useInitialSchedule) {
|
||||
new Setting(sm2ParamsContainer)
|
||||
.setName('SM-2: custom initial intervals (days)')
|
||||
.setDesc('Comma-separated list for initial SM-2 reviews (e.g., 0,1,3,7). Must start with 0.')
|
||||
.setName(`Custom initial intervals (${SM2})`)
|
||||
.setDesc(`Comma-separated list for initial ${SM2} reviews (e.g., 0,1,3,7). Must start with 0.`)
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.initialScheduleCustomIntervals.join(', '))
|
||||
.onChange(async (value: string) => {
|
||||
|
|
@ -439,15 +439,15 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
this.plugin.settings.initialScheduleCustomIntervals = intervals;
|
||||
await this.plugin.savePluginData();
|
||||
} else {
|
||||
new Notice("Custom initial SM-2 intervals must start with 0 and be valid numbers.", 5000);
|
||||
new Notice(`Custom initial ${SM2} intervals must start with 0 and be valid numbers.`, 5000);
|
||||
text.setValue(this.plugin.settings.initialScheduleCustomIntervals.join(', '));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
new Setting(sm2ParamsContainer)
|
||||
.setName('SM-2: maximum interval (days)')
|
||||
.setDesc('Longest possible interval between SM-2 reviews.')
|
||||
.setName(`Maximum interval (${SM2})`)
|
||||
.setDesc(`Longest possible interval between ${SM2} reviews.`)
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.maximumInterval.toString())
|
||||
.onChange(async (value: string) => {
|
||||
|
|
@ -459,8 +459,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(sm2ParamsContainer)
|
||||
.setName('SM-2: load balancing')
|
||||
.setDesc('Add slight randomness to SM-2 intervals to prevent reviews clumping on the same day.')
|
||||
.setName(`Load balancing (${SM2})`)
|
||||
.setDesc(`Add slight randomness to ${SM2} intervals to prevent reviews clumping on the same day.`)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.loadBalance)
|
||||
.onChange(async (value: boolean) => {
|
||||
|
|
@ -472,7 +472,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
const fsrsParamsContainer = spacedRepSection.createEl('details', { cls: 'sf-settings-collapsible-subsection' });
|
||||
const fsrsSummary = fsrsParamsContainer.createEl('summary');
|
||||
// Changed to h3
|
||||
fsrsSummary.setText('FSRS parameters');
|
||||
fsrsSummary.setText(`${FSRS} parameters`);
|
||||
fsrsParamsContainer.open = false; // Initially closed
|
||||
|
||||
new Setting(fsrsParamsContainer)
|
||||
|
|
@ -487,13 +487,13 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
await this.plugin.savePluginData();
|
||||
this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange();
|
||||
} else {
|
||||
new Notice("FSRS request retention must be between 0.7 and 0.99.");
|
||||
new Notice(`${FSRS} request retention must be between 0.7 and 0.99.`);
|
||||
}
|
||||
}));
|
||||
|
||||
new Setting(fsrsParamsContainer)
|
||||
.setName('Maximum interval (days)')
|
||||
.setDesc('Longest possible interval FSRS will schedule.')
|
||||
.setDesc(`Longest possible interval ${FSRS} will schedule.`)
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.fsrsParameters?.maximum_interval?.toString() ?? DEFAULT_SETTINGS.fsrsParameters.maximum_interval?.toString() ?? '36500')
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -503,7 +503,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
await this.plugin.savePluginData();
|
||||
this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange();
|
||||
} else {
|
||||
new Notice("FSRS maximum interval must be a positive number.");
|
||||
new Notice(`${FSRS} maximum interval must be a positive number.`);
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
@ -523,12 +523,12 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
await this.plugin.savePluginData();
|
||||
this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange();
|
||||
} else {
|
||||
new Notice("FSRS learning steps must be valid comma-separated numbers > 0, or empty.");
|
||||
new Notice(`${FSRS} learning steps must be valid comma-separated numbers > 0, or empty.`);
|
||||
}
|
||||
}));
|
||||
new Setting(fsrsParamsContainer)
|
||||
.setName('Enable fuzz')
|
||||
.setDesc('Add slight randomness to FSRS intervals (recommended).')
|
||||
.setDesc(`Add slight randomness to ${FSRS} intervals (recommended).`)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.fsrsParameters?.enable_fuzz ?? DEFAULT_SETTINGS.fsrsParameters.enable_fuzz ?? false)
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -537,8 +537,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange();
|
||||
}));
|
||||
new Setting(fsrsParamsContainer)
|
||||
.setName('Enable short term scheduling')
|
||||
.setDesc('Use FSRS short-term memory model (affects initial learning steps).')
|
||||
.setName('Enable short-term scheduling')
|
||||
.setDesc(`Use ${FSRS} short-term memory model (affects initial learning steps).`)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.fsrsParameters?.enable_short_term ?? DEFAULT_SETTINGS.fsrsParameters.enable_short_term ?? false)
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -548,7 +548,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
new Setting(fsrsParamsContainer)
|
||||
.setName('Weights (w)')
|
||||
.setDesc('FSRS algorithm parameters (17 numbers). Edit with caution. Default weights are generally good.')
|
||||
.setDesc(`${FSRS} algorithm parameters (17 numbers). Edit with caution. Default weights are generally good.`)
|
||||
.addTextArea(text => {
|
||||
text.inputEl.rows = 3;
|
||||
text.inputEl.addClass('sf-full-width-textarea');
|
||||
|
|
@ -560,7 +560,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
await this.plugin.savePluginData();
|
||||
this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange();
|
||||
} else {
|
||||
new Notice("FSRS weights must be a comma-separated list of 17 valid numbers.");
|
||||
new Notice(`${FSRS} weights must be a comma-separated list of 17 valid numbers.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -573,42 +573,42 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
conversionContainer.open = false; // Initially closed
|
||||
|
||||
new Setting(conversionContainer)
|
||||
.setName('Convert all SM-2 cards to FSRS')
|
||||
.setDesc('Migrate all existing SM-2 cards to use the FSRS algorithm. This will reset their learning state for FSRS.')
|
||||
.setName(`Convert all ${SM2} cards to ${FSRS}`)
|
||||
.setDesc(`Migrate all existing ${SM2} cards to use the ${FSRS} algorithm. This will reset their learning state for ${FSRS}.`)
|
||||
.addButton(button => button
|
||||
.setButtonText('Convert SM-2 to FSRS')
|
||||
.setButtonText(`Convert ${SM2} to ${FSRS}`)
|
||||
.setCta() // Call to action style
|
||||
.onClick(() => {
|
||||
new ConfirmationModal(
|
||||
this.app,
|
||||
'Convert SM-2 to FSRS',
|
||||
'Are you sure you want to convert ALL SM-2 cards to FSRS? Their FSRS learning state will be reset. This action cannot be easily undone.',
|
||||
`Convert ${SM2} to ${FSRS}`,
|
||||
`Are you sure you want to convert ALL ${SM2} cards to ${FSRS}? Their ${FSRS} learning state will be reset. This action cannot be easily undone.`,
|
||||
async () => {
|
||||
new Notice('Converting SM-2 cards to FSRS... This may take a moment.');
|
||||
new Notice(`Converting ${SM2} cards to ${FSRS}... This may take a moment.`);
|
||||
this.plugin.reviewScheduleService.convertAllSm2ToFsrs();
|
||||
await this.plugin.savePluginData();
|
||||
new Notice('All SM-2 cards have been converted to FSRS.');
|
||||
new Notice(`All ${SM2} cards have been converted to ${FSRS}.`);
|
||||
this.display(); // Refresh settings tab
|
||||
}
|
||||
).open();
|
||||
}));
|
||||
|
||||
new Setting(conversionContainer)
|
||||
.setName('Convert all FSRS cards to SM-2')
|
||||
.setDesc('Migrate all existing FSRS cards to use the SM-2 algorithm. Their SM-2 learning state will be initialized with defaults.')
|
||||
.setName(`Convert all ${FSRS} cards to ${SM2}`)
|
||||
.setDesc(`Migrate all existing ${FSRS} cards to use the ${SM2} algorithm. Their ${SM2} learning state will be initialized with defaults.`)
|
||||
.addButton(button => button
|
||||
.setButtonText('Convert FSRS to SM-2')
|
||||
.setButtonText(`Convert ${FSRS} to ${SM2}`)
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
new ConfirmationModal(
|
||||
this.app,
|
||||
'Convert FSRS to SM-2',
|
||||
'Are you sure you want to convert ALL FSRS cards to SM-2? Their SM-2 learning state will be reset to defaults. This action cannot be easily undone.',
|
||||
`Convert ${FSRS} to ${SM2}`,
|
||||
`Are you sure you want to convert ALL ${FSRS} cards to ${SM2}? Their ${SM2} learning state will be reset to defaults. This action cannot be easily undone.`,
|
||||
async () => {
|
||||
new Notice('Converting FSRS cards to SM-2... This may take a moment.');
|
||||
new Notice(`Converting ${FSRS} cards to ${SM2}... This may take a moment.`);
|
||||
this.plugin.reviewScheduleService.convertAllFsrsToSm2();
|
||||
await this.plugin.savePluginData();
|
||||
new Notice('All FSRS cards have been converted to SM-2.');
|
||||
new Notice(`All ${FSRS} cards have been converted to ${SM2}.`);
|
||||
this.display(); // Refresh settings tab
|
||||
}
|
||||
).open();
|
||||
|
|
@ -674,7 +674,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(interfaceSection)
|
||||
.setName('Reading speed (WPM)')
|
||||
.setName(`Reading speed (${WPM})`)
|
||||
.setDesc('Words per minute for estimating review time')
|
||||
.addSlider(slider => slider
|
||||
.setLimits(100, 500, 10)
|
||||
|
|
@ -687,7 +687,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
interfaceSection.createEl('div', {
|
||||
cls: 'sf-setting-explain',
|
||||
text: 'Average adults read 200-250 WPM for regular content, 100-150 WPM for technical content'
|
||||
text: `Average adults read 200-250 ${WPM} for regular content, 100-150 ${WPM} for technical content`
|
||||
});
|
||||
|
||||
new Setting(interfaceSection)
|
||||
|
|
@ -756,7 +756,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
const mcqSection = createCollapsible('Multiple choice questions', 'newspaper', false); // Closed by default
|
||||
|
||||
new Setting(mcqSection)
|
||||
.setName('Enable MCQ feature')
|
||||
.setName('Enable multiple choice feature')
|
||||
.setDesc('Use AI-generated multiple-choice questions to test your knowledge')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.enableMCQ)
|
||||
|
|
@ -821,7 +821,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setClass("sf-settings-subsection-provider-header");
|
||||
const apiKeyContainer = mcqSection.createEl('div', { cls: 'sf-setting-highlight' });
|
||||
new Setting(apiKeyContainer)
|
||||
.setName('OpenRouter API key')
|
||||
.setName('API key')
|
||||
.setDesc('Required for generating MCQs via OpenRouter.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your OpenRouter API key')
|
||||
|
|
@ -834,7 +834,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
apiKeyContainer.createEl('div').setText('Get your API key at https://openrouter.ai/keys');
|
||||
|
||||
new Setting(mcqSection)
|
||||
.setName('OpenRouter model')
|
||||
.setName('Model')
|
||||
.setDesc('Model identifier from OpenRouter (e.g., openai/gpt-4.1-mini)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter OpenRouter model identifier')
|
||||
|
|
@ -850,7 +850,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setHeading()
|
||||
.setClass("sf-settings-subsection-provider-header");
|
||||
new Setting(mcqSection)
|
||||
.setName('OpenAI API key')
|
||||
.setName('API key')
|
||||
.setDesc('Your OpenAI API key.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your OpenAI API key (sk-...)')
|
||||
|
|
@ -860,7 +860,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
await this.plugin.savePluginData();
|
||||
}));
|
||||
new Setting(mcqSection)
|
||||
.setName('OpenAI model')
|
||||
.setName('Model')
|
||||
.setDesc('Model name (e.g., gpt-3.5-turbo, gpt-4)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter OpenAI model name')
|
||||
|
|
@ -875,20 +875,20 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setHeading()
|
||||
.setClass("sf-settings-subsection-provider-header");
|
||||
new Setting(mcqSection)
|
||||
.setName('Ollama API URL')
|
||||
.setName('API URL')
|
||||
.setDesc('URL of your running Ollama instance (e.g., http://localhost:11434)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('http://localhost:11434')
|
||||
.setPlaceholder(`http:${'//'}localhost:11434`)
|
||||
.setValue(this.plugin.settings.ollamaApiUrl)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.ollamaApiUrl = value;
|
||||
await this.plugin.savePluginData();
|
||||
}));
|
||||
new Setting(mcqSection)
|
||||
.setName('Ollama model')
|
||||
.setDesc('Name of the Ollama model to use (e.g., llama3, mistral)')
|
||||
.setName('Model')
|
||||
.setDesc(`Name of the ${OLLAMA} model to use (e.g., llama3, mistral)`)
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter Ollama model name')
|
||||
.setPlaceholder(`Enter ${OLLAMA} model name`)
|
||||
.setValue(this.plugin.settings.ollamaModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.ollamaModel = value;
|
||||
|
|
@ -900,20 +900,20 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setHeading()
|
||||
.setClass("sf-settings-subsection-provider-header");
|
||||
new Setting(mcqSection)
|
||||
.setName('Gemini API key')
|
||||
.setDesc('Your Google AI Gemini API key.')
|
||||
.setName('API key')
|
||||
.setDesc(`Your Google AI ${GEMINI} ${API} key.`)
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your Gemini API key')
|
||||
.setPlaceholder(`Enter your ${GEMINI} ${API} key`)
|
||||
.setValue(this.plugin.settings.geminiApiKey)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.geminiApiKey = value;
|
||||
await this.plugin.savePluginData();
|
||||
}));
|
||||
new Setting(mcqSection)
|
||||
.setName('Gemini model')
|
||||
.setName('Model')
|
||||
.setDesc('Model name (e.g., gemini-pro)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter Gemini model name')
|
||||
.setPlaceholder(`Enter ${GEMINI} model name`)
|
||||
.setValue(this.plugin.settings.geminiModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.geminiModel = value;
|
||||
|
|
@ -925,20 +925,20 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setHeading()
|
||||
.setClass("sf-settings-subsection-provider-header");
|
||||
new Setting(mcqSection)
|
||||
.setName('Claude API key')
|
||||
.setDesc('Your Anthropic Claude API key.')
|
||||
.setName('API key')
|
||||
.setDesc(`Your Anthropic ${CLAUDE} ${API} key.`)
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your Claude API key')
|
||||
.setPlaceholder(`Enter your ${CLAUDE} ${API} key`)
|
||||
.setValue(this.plugin.settings.claudeApiKey)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.claudeApiKey = value;
|
||||
await this.plugin.savePluginData();
|
||||
}));
|
||||
new Setting(mcqSection)
|
||||
.setName('Claude model')
|
||||
.setName('Model')
|
||||
.setDesc('Model name (e.g., claude-3-opus-20240229, claude-3-sonnet-20240229)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter Claude model name')
|
||||
.setPlaceholder(`Enter ${CLAUDE} model name`)
|
||||
.setValue(this.plugin.settings.claudeModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.claudeModel = value;
|
||||
|
|
@ -950,20 +950,20 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setHeading()
|
||||
.setClass("sf-settings-subsection-provider-header");
|
||||
new Setting(mcqSection)
|
||||
.setName('Together AI API key')
|
||||
.setDesc('Your Together AI API key.')
|
||||
.setName('API key')
|
||||
.setDesc(`Your ${TOGETHER_AI} ${API} key.`)
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your Together AI API key')
|
||||
.setPlaceholder(`Enter your ${TOGETHER_AI} ${API} key`)
|
||||
.setValue(this.plugin.settings.togetherApiKey)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.togetherApiKey = value;
|
||||
await this.plugin.savePluginData();
|
||||
}));
|
||||
new Setting(mcqSection)
|
||||
.setName('Together AI model')
|
||||
.setName('Model')
|
||||
.setDesc('Model identifier from Together AI (e.g., meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter Together AI model identifier')
|
||||
.setPlaceholder(`Enter ${TOGETHER_AI} model identifier`)
|
||||
.setValue(this.plugin.settings.togetherModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.togetherModel = value;
|
||||
|
|
@ -1056,7 +1056,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
const promptTypeContainer = mcqFormattingGrid.createEl('div');
|
||||
new Setting(promptTypeContainer)
|
||||
.setName('Prompt type')
|
||||
.setDesc('Format for MCQ generation')
|
||||
.setDesc('Format for question generation')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('basic', 'Basic')
|
||||
.addOption('detailed', 'Detailed')
|
||||
|
|
@ -1069,7 +1069,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
// Second item in grid
|
||||
const difficultyContainer = mcqFormattingGrid.createEl('div');
|
||||
new Setting(difficultyContainer)
|
||||
.setName('MCQ difficulty')
|
||||
.setName('Difficulty')
|
||||
.setDesc('Complexity level')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption(MCQDifficulty.Basic, 'Basic recall')
|
||||
|
|
@ -1177,8 +1177,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
if (this.plugin.settings.enableQuestionRegenerationOnRating) {
|
||||
new Setting(mcqSection)
|
||||
.setName('Min SM-2 rating for MCQ regeneration')
|
||||
.setDesc('For SM-2: Regenerate MCQs if review rating (0-5) is this value or higher. (0:Blackout, 5:Perfect)')
|
||||
.setName(`Minimum rating for regeneration (${SM2})`)
|
||||
.setDesc(`For ${SM2}: Regenerate ${MCQS} if review rating (0-5) is this value or higher. (0:Blackout, 5:Perfect)`)
|
||||
.addSlider(slider => slider
|
||||
.setLimits(0, 5, 1)
|
||||
.setValue(this.plugin.settings.minSm2RatingForQuestionRegeneration)
|
||||
|
|
@ -1189,8 +1189,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(mcqSection)
|
||||
.setName('Min FSRS rating for MCQ regeneration')
|
||||
.setDesc('For FSRS: Regenerate MCQs if review rating (1-4) is this value or higher. (1:Again, 4:Easy)')
|
||||
.setName(`Minimum rating for regeneration (${FSRS})`)
|
||||
.setDesc(`For ${FSRS}: Regenerate ${MCQS} if review rating (1-4) is this value or higher. (1:Again, 4:Easy)`)
|
||||
.addSlider(slider => slider
|
||||
.setLimits(1, 4, 1) // FSRS ratings are 1-4
|
||||
.setValue(this.plugin.settings.minFsrsRatingForQuestionRegeneration)
|
||||
|
|
@ -1205,16 +1205,16 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
// If MCQ is disabled, show a message about enabling it
|
||||
const mcqDisabledMessage = mcqSection.createEl('div', { cls: 'sf-info-box' });
|
||||
mcqDisabledMessage.createEl('p', {
|
||||
text: 'Multiple choice questions are currently disabled. Enable it to configure durations and notifications.'
|
||||
text: `${MCQS} are currently disabled. Enable it to configure durations and notifications.`
|
||||
});
|
||||
}
|
||||
|
||||
// ========= POMODORO TIMER SECTION =========
|
||||
const pomodoroSection = createCollapsible('Pomodoro timer', 'timer', false); // Added timer icon
|
||||
const pomodoroSection = createCollapsible(`${POMODORO} timer`, 'timer', false); // Added timer icon
|
||||
|
||||
new Setting(pomodoroSection)
|
||||
.setName('Enable pomodoro timer')
|
||||
.setDesc('Show the Pomodoro timer in the sidebar.')
|
||||
.setName(`Enable ${POMODORO} timer`)
|
||||
.setDesc(`Show the ${POMODORO} timer in the sidebar.`)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.pomodoroEnabled)
|
||||
.onChange(async (value: boolean) => {
|
||||
|
|
@ -1310,7 +1310,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
} else {
|
||||
const pomodoroDisabledMessage = pomodoroSection.createEl('div', { cls: 'sf-info-box' });
|
||||
pomodoroDisabledMessage.createEl('p', {
|
||||
text: 'Pomodoro timer is currently disabled. Enable it to configure durations and notifications.'
|
||||
text: `${POMODORO} timer is currently disabled. Enable it to configure durations and notifications.`
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1331,7 +1331,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setDesc(locationDesc)
|
||||
.addExtraButton(button => button
|
||||
.setIcon('info')
|
||||
.setTooltip('Current location of your Spaceforge data')
|
||||
.setTooltip(`Current location of your ${SPACEFORGE} data`)
|
||||
.onClick(() => {
|
||||
const message = this.plugin.settings.useCustomDataPath && this.plugin.settings.customDataPath
|
||||
? `Your data is stored at: ${this.plugin.settings.customDataPath.endsWith('/data.json') ? this.plugin.settings.customDataPath : `${this.plugin.settings.customDataPath}/data.json`}`
|
||||
|
|
@ -1385,17 +1385,17 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
container.empty(); // Clear previous content
|
||||
|
||||
if (algorithm === 'sm2') {
|
||||
new Setting(container).setName('About the modified SM-2 algorithm').setHeading();
|
||||
new Setting(container).setName(`About modified ${SM2} algorithm`).setHeading();
|
||||
container.createEl('p', {
|
||||
text: 'Spaceforge uses a modified version of the SuperMemo SM-2 algorithm (1991) which schedules reviews based on how well you recall information. ' +
|
||||
text: `Spaceforge uses a modified version of the SuperMemo ${SM2} algorithm (1991) which schedules reviews based on how well you recall information. ` +
|
||||
'When you rate your recall quality from 0-5, the algorithm adjusts the interval and difficulty (ease factor) accordingly.'
|
||||
});
|
||||
container.createEl('p', {
|
||||
text: 'Our implementation includes specific handling for overdue or skipped items to prevent them from accumulating in a backlog:'
|
||||
});
|
||||
const sm2List = container.createEl('ul');
|
||||
sm2List.createEl('li', { text: 'Overdue items: Automatically set to review tomorrow with a quality rating of 0.' });
|
||||
sm2List.createEl('li', { text: 'Postponed items: Explicitly moved to tomorrow with a one-step quality penalty.' });
|
||||
sm2List.createEl('li', { text: `${'Overdue'} items: Automatically set to review tomorrow with a quality rating of 0.` });
|
||||
sm2List.createEl('li', { text: `${'Postponed'} items: Explicitly moved to tomorrow with a one-step quality penalty.` });
|
||||
sm2List.createEl('li', { text: 'Both cases reset the repetition count to 1 and update the ease factor.' });
|
||||
|
||||
const ratingsTable = container.createEl('table', { cls: 'sf-ratings-table' }); // Added a class for potential styling
|
||||
|
|
@ -1426,19 +1426,19 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
row4.createEl('td', { text: 'Perfect recall' });
|
||||
row4.createEl('td', { text: 'Largest increase' });
|
||||
} else if (algorithm === 'fsrs') {
|
||||
new Setting(container).setName('About the FSRS algorithm').setHeading();
|
||||
new Setting(container).setName(`About ${FSRS} algorithm`).setHeading();
|
||||
container.createEl('p', {
|
||||
text: 'FSRS (Free Spaced Repetition Scheduler) is a modern, evidence-based algorithm that models memory retention to optimize review schedules. ' +
|
||||
'It calculates card difficulty and stability dynamically based on your review history and aims for a target retention rate.'
|
||||
text: `${FSRS} (Free Spaced Repetition Scheduler) is a modern, evidence-based algorithm that models memory retention to optimize review schedules. ` +
|
||||
`It calculates card difficulty and stability dynamically based on your review history and aims for a target retention rate.`
|
||||
});
|
||||
container.createEl('p', {
|
||||
text: 'Key concepts in FSRS:'
|
||||
text: `Key concepts in ${FSRS}:`
|
||||
});
|
||||
const fsrsList = container.createEl('ul');
|
||||
fsrsList.createEl('li', { text: 'Difficulty: How hard a card is to remember.' });
|
||||
fsrsList.createEl('li', { text: 'Stability: How long a card is likely to be remembered.' });
|
||||
fsrsList.createEl('li', { text: 'Retention: The probability of recalling a card at the time of review.' });
|
||||
fsrsList.createEl('li', { text: 'Learning steps: Initial short intervals for new cards (configurable).' });
|
||||
fsrsList.createEl('li', { text: `${'Difficulty'}: How hard a card is to remember.` });
|
||||
fsrsList.createEl('li', { text: `${'Stability'}: How long a card is likely to be remembered.` });
|
||||
fsrsList.createEl('li', { text: `${'Retention'}: The probability of recalling a card at the time of review.` });
|
||||
fsrsList.createEl('li', { text: `${'Learning'} steps: Initial short intervals for new cards (configurable).` });
|
||||
|
||||
const ratingsTable = container.createEl('table', { cls: 'sf-ratings-table' });
|
||||
const thead = ratingsTable.createTHead();
|
||||
|
|
@ -1449,22 +1449,22 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
headerRow.createEl('th', { text: 'Effect on stability/difficulty' });
|
||||
|
||||
const row1 = tbody.insertRow();
|
||||
row1.createEl('td', { text: '1 (Again)' });
|
||||
row1.createEl('td', { text: `${'1 (Again)'}` });
|
||||
row1.createEl('td', { text: 'Forgot the card' });
|
||||
row1.createEl('td', { text: 'Decreases stability, may increase difficulty' });
|
||||
|
||||
const row2 = tbody.insertRow();
|
||||
row2.createEl('td', { text: '2 (Hard)' });
|
||||
row2.createEl('td', { text: `${'2 (Hard)'}` });
|
||||
row2.createEl('td', { text: 'Recalled with significant difficulty' });
|
||||
row2.createEl('td', { text: 'Smaller increase in stability' });
|
||||
|
||||
const row3 = tbody.insertRow();
|
||||
row3.createEl('td', { text: '3 (Good)' });
|
||||
row3.createEl('td', { text: `${'3 (Good)'}` });
|
||||
row3.createEl('td', { text: 'Recalled correctly' });
|
||||
row3.createEl('td', { text: 'Standard increase in stability' });
|
||||
|
||||
const row4 = tbody.insertRow();
|
||||
row4.createEl('td', { text: '4 (Easy)' });
|
||||
row4.createEl('td', { text: `${'4 (Easy)'}` });
|
||||
row4.createEl('td', { text: 'Recalled easily' });
|
||||
row4.createEl('td', { text: 'Largest increase in stability' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ export class ReviewSidebarView extends ItemView {
|
|||
if (this.listViewRenderer) { // Check if renderer is initialized
|
||||
await this.listViewRenderer.render(container);
|
||||
} else {
|
||||
container.setText("Error: Could not render list view. Renderer not ready.");
|
||||
container.setText("Error: could not render list view. Renderer not ready.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,7 +267,7 @@ export class ReviewSidebarView extends ItemView {
|
|||
if (this.calendarView) { // Should always exist due to ensureBaseStructure
|
||||
await this.calendarView.render();
|
||||
} else {
|
||||
container.setText("Error: Could not render calendar view. CalendarView not ready.");
|
||||
container.setText("Error: could not render calendar view. CalendarView not ready.");
|
||||
return; // Avoid further errors if calendarView is somehow null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export class PomodoroUIManager {
|
|||
// new Notice("Pomodoro durations updated."); // Removed notification
|
||||
return true;
|
||||
} else {
|
||||
new Notice("Invalid Pomodoro durations. Settings not saved. Please enter positive numbers.");
|
||||
new Notice("Invalid durations. Settings not saved. Please enter positive numbers.");
|
||||
// Re-populate with current valid settings to prevent saving invalid on next close if not corrected
|
||||
if (this.pomodoroQuickWorkInput) this.pomodoroQuickWorkInput.value = String(this.plugin.settings.pomodoroWorkDuration);
|
||||
if (this.pomodoroQuickShortInput) this.pomodoroQuickShortInput.value = String(this.plugin.settings.pomodoroShortBreakDuration);
|
||||
|
|
@ -339,7 +339,7 @@ export class PomodoroUIManager {
|
|||
this.pomodoroUserOverrideHoursInput.value = String(this.plugin.pluginState.pomodoroUserOverrideHours);
|
||||
|
||||
const hoursLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small");
|
||||
hoursLabel.setText("h");
|
||||
hoursLabel.setText("H");
|
||||
|
||||
this.pomodoroUserOverrideMinutesInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-minutes" });
|
||||
this.pomodoroUserOverrideMinutesInput.setAttr("min", "0");
|
||||
|
|
@ -347,7 +347,7 @@ export class PomodoroUIManager {
|
|||
this.pomodoroUserOverrideMinutesInput.value = String(this.plugin.pluginState.pomodoroUserOverrideMinutes);
|
||||
|
||||
const minutesLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small");
|
||||
minutesLabel.setText("m");
|
||||
minutesLabel.setText("M");
|
||||
|
||||
// Add to estimation toggle
|
||||
const toggleContainer = overrideContainer.createDiv("pomodoro-override-toggle-container");
|
||||
|
|
|
|||
Loading…
Reference in a new issue