From cb3a82b907cf9cb24b15d544d742b182947a0064 Mon Sep 17 00:00:00 2001 From: clockblocker Date: Tue, 17 Feb 2026 21:37:03 +0100 Subject: [PATCH 1/4] Add code ownership boundary documentation Add CODEOWNERS and docs/ARCHITECTURE.md to define 5 logical ownership domains: plugin-core, commands, ai-api, prompt-engineering, and filesystem. Each source file maps to exactly one domain with no overlapping patterns. Nightshift-Task: ownership-boundary Nightshift-Ref: https://github.com/marcus/nightshift Co-Authored-By: Claude Opus 4.6 --- CODEOWNERS | 23 +++++++++++ docs/ARCHITECTURE.md | 90 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 CODEOWNERS create mode 100644 docs/ARCHITECTURE.md diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..d363a906f --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,23 @@ +# Code Ownership Boundaries for filler-de +# See docs/ARCHITECTURE.md for domain descriptions. +# +# Each source file is assigned to exactly one ownership domain. +# Patterns are ordered so that no two rules match the same file. + +# --- plugin-core: lifecycle, configuration, shared types --- +/src/main.ts @domain/plugin-core +/src/settings.ts @domain/plugin-core +/src/types.ts @domain/plugin-core + +# --- commands: user-facing Obsidian commands --- +/src/commands/ @domain/commands + +# --- ai-api: external LLM integration --- +/src/api.ts @domain/ai-api + +# --- prompt-engineering: prompt templates and builders --- +/src/prompts/ @domain/prompt-engineering + +# --- filesystem: vault file read/write helpers --- +/src/utils.ts @domain/filesystem +/src/file.ts @domain/filesystem diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 000000000..e11bb3595 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,90 @@ +# filler-de Architecture + +## Ownership Domains + +The plugin is divided into five logical ownership domains. Each domain +groups files by responsibility so that changes within a domain can be +reviewed by the team most familiar with that area. + +### 1. plugin-core + +Entry point, settings UI, and shared type definitions. + +| File | Purpose | +|------|---------| +| `src/main.ts` | Plugin lifecycle (`onload` / `onunload`), command registration | +| `src/settings.ts` | Settings tab, defaults, persistence | +| `src/types.ts` | Shared TypeScript interfaces and type aliases | + +### 2. commands + +User-facing Obsidian commands that orchestrate the other domains. + +| File | Purpose | +|------|---------| +| `src/commands/addBacklinksToCurrentFile.ts` | Add backlinks to current note | +| `src/commands/endgame.ts` | Endgame command logic | +| `src/commands/fillTemplate.ts` | Fill a note template via AI | +| `src/commands/formatSelectionWithNumber.ts` | Format selected text with numbering | +| `src/commands/functions.ts` | Shared command helpers | +| `src/commands/getInfinitiveAndEmoji.ts` | Look up infinitive + emoji | +| `src/commands/insertReplyFromC1Richter.ts` | Insert C1 Richter reply | +| `src/commands/insertReplyFromKeymaker.ts` | Insert Keymaker reply | +| `src/commands/normalizeSelection.ts` | Normalize selected text | +| `src/commands/translateSelection.ts` | Translate selected text | + +### 3. ai-api + +HTTP client for the external LLM provider. + +| File | Purpose | +|------|---------| +| `src/api.ts` | API request construction, response parsing, error handling | + +### 4. prompt-engineering + +Prompt templates and builders sent to the LLM. + +| File | Purpose | +|------|---------| +| `src/prompts/index.ts` | Re-exports / prompt registry | +| `src/prompts/baseDict.ts` | Base dictionary prompt | +| `src/prompts/c1Richter.ts` | C1 Richter prompt | +| `src/prompts/determine-infinitive-and-pick-emoji.ts` | Infinitive + emoji prompt | +| `src/prompts/full-dict-enrtie.ts` | Full dictionary entry prompt | +| `src/prompts/generate-forms.ts` | Word-form generation prompt | +| `src/prompts/keymaker.ts` | Keymaker prompt | +| `src/prompts/morphems.ts` | Morpheme analysis prompt | +| `src/prompts/normalize.ts` | Normalization prompt | +| `src/prompts/translate-de-to-eng.ts` | DE-to-EN translation prompt | +| `src/prompts/valence.ts` | Verb valence prompt | +| `src/prompts/wip_keymaker.ts` | Keymaker (work-in-progress) prompt | + +### 5. filesystem + +Vault file-system helpers for reading and writing notes. + +| File | Purpose | +|------|---------| +| `src/utils.ts` | Path resolution, directory sharding, file creation helpers | +| `src/file.ts` | File read/write operations | + +## Dependency Flow + +``` +plugin-core + | + v + commands + / \ + v v +ai-api filesystem + | + v +prompt-engineering +``` + +`plugin-core` registers commands. Each command may call into `ai-api` +(which uses prompts from `prompt-engineering`) and `filesystem` to read +or write vault files. Domains at the bottom of the graph should never +import from domains above them. From c51444dfa72e4c65734f79c20c4fa0d4a4310462 Mon Sep 17 00:00:00 2001 From: clockblocker Date: Thu, 19 Feb 2026 20:02:35 +0100 Subject: [PATCH 2/4] Remove dead code, fix import bug, and deduplicate patterns - Delete 3 dead files: wip_keymaker.ts, full-dict-enrtie.ts, endgame.ts (~879 lines) - Fix insertReplyFromKeymaker import pointing to wrong module (C1Richter) - Extract blockOrEmpty helper to replace 4 identical longDash checks in fillTemplate.ts - Deduplicate 5 selection-based command registrations in main.ts into data-driven loop - Remove unused deepseekApiKey from types.ts and settings.ts - Remove debug console.log statements from utils.ts Nightshift-Task: auto-dry Nightshift-Ref: https://github.com/marcus/nightshift Co-Authored-By: Claude Opus 4.6 --- src/commands/endgame.ts | 74 ----- src/commands/fillTemplate.ts | 20 +- src/main.ts | 132 ++++----- src/prompts/full-dict-enrtie.ts | 471 -------------------------------- src/prompts/wip_keymaker.ts | 333 ---------------------- src/settings.ts | 26 -- src/types.ts | 2 - src/utils.ts | 2 - 8 files changed, 65 insertions(+), 995 deletions(-) delete mode 100644 src/commands/endgame.ts delete mode 100644 src/prompts/full-dict-enrtie.ts delete mode 100644 src/prompts/wip_keymaker.ts diff --git a/src/commands/endgame.ts b/src/commands/endgame.ts deleted file mode 100644 index 9500887ae..000000000 --- a/src/commands/endgame.ts +++ /dev/null @@ -1,74 +0,0 @@ -export const message = 'Uncomment this, when the endgame promps are ready'; - -// import { Editor, MarkdownView, Notice, TFile } from 'obsidian'; -// import TextEaterPlugin from '../main'; -// import { grundformsOutputSchema } from '../prompts/wip/endgame/zod/schemas'; -// import { -// Grundform, -// Wortart, -// Nomen, -// Genus, -// } from '../prompts/wip/endgame/zod/types'; -// import { z } from 'zod'; -// import { makeGrundformsPrompt } from '../prompts/wip/endgame/grundform/wortart/grundforms/grundformsPrompt'; -// import { -// makeAnEndgameNote, -// makeAnEndgameNoteTest, -// } from '../prompts/wip/endgame/makeAnEndgameNote'; - -// export async function endgame( -// plugin: TextEaterPlugin, -// editor: Editor, -// file: TFile -// ) { -// const word = file.basename.toLocaleLowerCase(); -// try { -// const grundformsPrompt = makeGrundformsPrompt(); -// const generatedGrundforms = await plugin.apiService.generateContent( -// grundformsPrompt, -// word, -// true -// ); - -// // Wrap the output in an object with the word as the key -// const wrappedOutput = { [word]: JSON.parse(generatedGrundforms) }; -// const parsedGrungforms = grundformsOutputSchema.safeParse( -// wrappedOutput[word] -// ); - -// if (parsedGrungforms.error) { -// console.error({ -// zodError: parsedGrungforms.error, -// output: generatedGrundforms, -// }); -// await plugin.fileService.appendToFile( -// file.path, -// 'Contact t.me/@clockblocker' -// ); -// return; -// } - -// await makeAnEndgameNote(plugin, file, parsedGrungforms.data, word); - -// editor.setCursor({ line: 1, ch: 0 }); -// editor.focus(); -// } catch (error) { -// new Notice(`Error: ${JSON.stringify(error)}`); -// } -// } - -// export async function testEndgame( -// plugin: TextEaterPlugin, -// editor: Editor, -// file: TFile -// ) { -// const word = file.basename.toLocaleLowerCase(); -// try { -// await makeAnEndgameNoteTest(plugin, file, word); - -// editor.setCursor({ line: 1, ch: 0 }); -// editor.focus(); -// } catch (error) { -// new Notice(`Error: ${JSON.stringify(error)}`); -// } -// } diff --git a/src/commands/fillTemplate.ts b/src/commands/fillTemplate.ts index 2c482a580..3bb311cb8 100644 --- a/src/commands/fillTemplate.ts +++ b/src/commands/fillTemplate.ts @@ -1,8 +1,12 @@ -import { Editor, MarkdownView, Notice, TFile } from 'obsidian'; +import { Editor, Notice, TFile } from 'obsidian'; import TextEaterPlugin from '../main'; import { prompts } from '../prompts'; import { longDash } from '../utils'; +function blockOrEmpty(block: string): string { + return block.replace('\n', '') === longDash ? '' : block; +} + function extractFirstBracketedWord(text: string) { const match = text.match(/\[\[([^\]]+)\]\]/); return match ? match[1] : null; @@ -78,6 +82,7 @@ export default async function fillTemplate( callBack?: () => void ) { const word = file.basename; + const notice = new Notice('Generating…', 0); try { const [dictionaryEntry, froms, morphems, valence] = await Promise.all([ @@ -97,13 +102,10 @@ export default async function fillTemplate( const baseBlock = await incertClipbordContentsInContextsBlock( incertYouglishLinkInIpa(trimmedBaseEntrie) ); - const morphemsBlock = - morphems.replace('\n', '') === longDash ? '' : `${morphems}\n`; - const valenceBlock = - valence.replace('\n', '') === longDash ? '' : `${valence}`; - const fromsBlock = froms.replace('\n', '') === longDash ? '' : `${froms}`; - const adjFormsBlock = - adjForms.replace('\n', '') === longDash ? '' : `${adjForms}`; + const morphemsBlock = blockOrEmpty(morphems); + const valenceBlock = blockOrEmpty(valence); + const fromsBlock = blockOrEmpty(froms); + const adjFormsBlock = blockOrEmpty(adjForms); const blocks = [ baseBlock, @@ -127,6 +129,8 @@ export default async function fillTemplate( } } catch (error) { new Notice(`Error: ${error.message}`); + } finally { + notice.hide(); } } diff --git a/src/main.ts b/src/main.ts index 260a5d915..4f5514a18 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,7 @@ import normalizeSelection from './commands/normalizeSelection'; import translateSelection from './commands/translateSelection'; import formatSelectionWithNumber from './commands/formatSelectionWithNumber'; import addBacklinksToCurrentFile from './commands/addBacklinksToCurrentFile'; -import insertReplyFromKeymaker from './commands/insertReplyFromC1Richter'; +import insertReplyFromKeymaker from './commands/insertReplyFromKeymaker'; import insertReplyFromC1Richter from './commands/insertReplyFromC1Richter'; export default class TextEaterPlugin extends Plugin { @@ -71,7 +71,6 @@ export default class TextEaterPlugin extends Plugin { if (view.file) { if (!checking) { fillTemplate(this, editor, view.file); - // testEndgame(this, editor, view.file); } return true; } @@ -97,88 +96,63 @@ export default class TextEaterPlugin extends Plugin { }, }); - this.addCommand({ - id: 'duplicate-selection', - name: 'Add links to normal/inf forms to selected text', - editorCheckCallback: ( - checking: boolean, - editor: Editor, - view: MarkdownView - ) => { - const selection = editor.getSelection(); - if (selection && view.file) { - if (!checking) { - normalizeSelection(this, editor, view.file, selection); - } - return true; - } - return false; + const selectionCommands: { + id: string; + name: string; + action: (sel: string, editor: Editor, view: MarkdownView) => void; + requiresFile?: boolean; + }[] = [ + { + id: 'duplicate-selection', + name: 'Add links to normal/inf forms to selected text', + requiresFile: true, + action: (sel, editor, view) => + normalizeSelection(this, editor, view.file!, sel), }, - }); - - this.addCommand({ - id: 'translate-selection', - name: 'Translate selected text', - editorCheckCallback: (checking: boolean, editor: Editor) => { - const selection = editor.getSelection(); - if (selection) { - if (!checking) { - translateSelection(this, editor, selection); - } - return true; - } - return false; + { + id: 'translate-selection', + name: 'Translate selected text', + action: (sel, editor) => translateSelection(this, editor, sel), }, - }); - - this.addCommand({ - id: 'format-selection-with-number', - name: 'Split selection into linked blocks', - editorCheckCallback: ( - checking: boolean, - editor: Editor, - view: MarkdownView - ) => { - const selection = editor.getSelection(); - if (selection && view.file) { - if (!checking) { - formatSelectionWithNumber(this, editor, view.file, selection); - } - return true; - } - return false; + { + id: 'format-selection-with-number', + name: 'Split selection into linked blocks', + requiresFile: true, + action: (sel, editor, view) => + formatSelectionWithNumber(this, editor, view.file!, sel), }, - }); - - this.addCommand({ - id: 'check-ru-de-translation', - name: 'Keymaker', - editorCheckCallback: (checking: boolean, editor: Editor) => { - const selection = editor.getSelection(); - if (selection) { - if (!checking) { - insertReplyFromKeymaker(this, editor, selection); - } - return true; - } - return false; + { + id: 'check-ru-de-translation', + name: 'Keymaker', + action: (sel, editor) => insertReplyFromKeymaker(this, editor, sel), }, - }); - - this.addCommand({ - id: 'check-schriben', - name: 'Schriben check', - editorCheckCallback: (checking: boolean, editor: Editor) => { - const selection = editor.getSelection(); - if (selection) { - if (!checking) { - insertReplyFromC1Richter(this, editor, selection); - } - return true; - } - return false; + { + id: 'check-schriben', + name: 'Schriben check', + action: (sel, editor) => insertReplyFromC1Richter(this, editor, sel), }, - }); + ]; + + for (const cmd of selectionCommands) { + this.addCommand({ + id: cmd.id, + name: cmd.name, + editorCheckCallback: ( + checking: boolean, + editor: Editor, + view: MarkdownView + ) => { + const selection = editor.getSelection(); + if (selection && (!cmd.requiresFile || view.file)) { + if (!checking) { + cmd.action(selection, editor, view); + } + return true; + } + return false; + }, + }); + } } async loadSettings() { diff --git a/src/prompts/full-dict-enrtie.ts b/src/prompts/full-dict-enrtie.ts deleted file mode 100644 index 6ce059576..000000000 --- a/src/prompts/full-dict-enrtie.ts +++ /dev/null @@ -1,471 +0,0 @@ -export const generate_dictionary_entry = `You are an expert linguist specializing in the German language. Your task is to create a detailed dictionary entry for a given German word. Here's the word you need to analyze: -{{german_word}} - -Before creating the entry, analyze the word and plan your approach. Break down the word inside tags: - -1. Identify the part of speech of the word. -2. If it's a verb: -- Determine if it's trennbar (separable) or untrennbar (inseparable). -- Identify its tense forms (present, past, perfect). -- Note any irregular conjugations. -Fill the list of cojugations (Präsens, Präteritum, Imperativ, Konjunktiv I, Konjunktiv II) -3. For nouns: -- Identify the gender (masculine, feminine, or neuter). -- Determine the declension pattern. -4. For adjectives: -- Note the comparative and superlative forms. -5. Identify and list examples of each morpheme in the word. -6. Plan which template you'll use based on the part of speech. -7. List the key information you'll need to include in the entry (e.g., pronunciation, conjugations, synonyms, antonyms, translations, morphemes). - -It's OK for this section to be quite long. - -Now, create the dictionary entry using the appropriate template based on your analysis. Strictly adhere to the format provided in the examples, ensuring no additional text is included that isn't present in the templates. Use the following guidelines: - -1. For trennbar verbs: -- Start with an appropriate emoji -- Include pronunciation, conjugations, synonyms, antonyms, English and Russian translations, morpheme breakdown, and a conjugation table - -2. For untrennbar verbs and verbs without prefixes: -- Follow a similar format to trennbar verbs, adjusting the conjugation details as needed - -3. For adjectives: -- Start with an appropriate emoji -- Include pronunciation, antonyms, synonyms, English and Russian translations, and unique possible forms - -4. For nouns: -- Use 🔴 for feminine, 🟢 for neuter, and 🔵 for masculine nouns -- Include plural form, synonyms, English and Russian translations, morpheme breakdown, and unique possible forms - -5. For other parts of speech: -- Create a similar template, adapting the information as appropriate for the specific part of speech - -Present only your final entry. Do not present the user with word_breakdown. Do not write to the user your thought process. Do not include tags in the output - - -verfeinden -😤 [[verfeinden]], [fɛɐ̯ˈfaɪ̯ndn̩] | [[verfeindete]], haben [[verfeindet]] - ---- - - ---- -= [[zerstreiten]], [[entzweien]] -≈ Feindschaft [[schließen]], [[verkrachen]], in Konflikt [[geraten]] -≠ [[versöhnen]], [[vertragen]], [[anfreunden]] - ---- -to make enemies, to set at odds -поссорить, сделать врагами - ---- -[[ver]]|[[fein]]|[den]] - ---- - -Person, Präsens, Präteritum, Imperativ, Konjunktiv I, Konjunktiv II -ich, [[verfeind]] / [[verfeinde]], [[verfeindete]], [[verfeinde]], [[verfeindete]], - -du, [[verfeindest]], [[verfeindetest]], [[verfeindest]], [[verfeindetest]], [[verfeind]] / [[verfeinde]] -er, [[verfeindet]], [[verfeindete]], [[verfeinde]], [[verfeindete]], - -wir, [[verfeinden]], [[verfeindeten]], [[verfeinden]], [[verfeindeten]], [[verfeinden]] -ihr, [[verfeindet]], [[verfeindetet]], [[verfeindet]], [[verfeindetet]], [[verfeindet]] -sie, [[verfeinden]], [[verfeindeten]], [[verfeinden]], [[verfeindeten]], [[verfeinden]] - -*Zu + Inf*: zu [[verfeinden]], P1: [[verfeindend]], P2: [[verfeindet]] - - ---- -[[verfeinden]] - [[ver]] = [[feinden]] / to make enemies, to set at odds - ---- -[[Verfehndung]], [[Verfeindung]], [[Feind]], [[feindlich]], [[Feindschaft]] - - - -tanztest -💃 [[tanzen]], [ˈtanʦn̩] | [[tanzte]], haben [[getanzt]] - ---- - ---- -= [[sich bewegen]], [[schwofen]], [[abhotten]] -≈ [[wiegen]], [[sich drehen]], [[hüpfen]], [[ballettieren]] -≠ [[sitzen]], [[stehen]], [[verharren]], [[ruhen]] - ---- -to dance -танцевать - ---- -[[tanz]]|[[en]] - ---- -Person, Präsens, Präteritum, Imperativ, Konjunktiv I, Konjunktiv II -ich, [[tanz]] / [[tanze]], [[tanzte]], [[tanze]], [[tanzte]], - -du, [[tanzt]], [[tanztest]], [[tanzest]], [[tanztest]], [[tanz /]] [[tanze]] -er, [[tanzt]], [[tanzte]], [[tanze]], [[tanzte]], - -wir, [[tanzen]], [[tanzten]], [[tanzen]], [[tanzten]], [[tanzen]] -ihr, [[tanzt]], [[tanztet]], [[tanzet]], [[tanztet]], [[tanzt]] -sie, [[tanzen]], [[tanzten]], [[tanzen]], [[tanzten]], [[tanzen]] - -*Zu + Inf*: zu [[tanzen]], *P1*: [[tanzend]], *P2*: [[getanzt]] - ---- - ---- -[[Tanz]], [[Tänzer]], [[Tänzerin]], [[Tanzfläche]], [[tanzerisch]], [[Tanzkurs]] - - - -Hoffnung -🕊️ 🔴 die [[Hoffnung]], [ˈhɔfnʊŋ] -die [[Hoffnungen]] - ---- - - ---- -= [[Zuversicht]], [[Optimismus]] -≈ [[Erwartung]], [[Vertrauen]], [[Glaube]], [[Wunsch]] -≠ [[Verzweiflung]], [[Pessimismus]], [[Hoffnungslosigkeit]], [[Resignation]] - ---- -hope -надежда - ---- -[[Hoff]]|[[nung]] - ---- -N: die [[Hoffnung]], die [[Hoffnungen]] -A: die [[Hoffnung]], die [[Hoffnungen]] -G: der [[Hoffnung]], der [[Hoffnungen]] -D: der [[Hoffnung]], den [[Hoffnungen]] - ---- -[[hoffen]], [[hoffentlich]], [[hoffnungsvoll]], [[hoffnungslos]] - - - -Busch -🌳 🔵 der [[Busch]] -die [[B\\xFCsche]] - ---- - - ---- -= [[Strauch]], [[Gesträuch]] -≈ [[Gebüsch]], [[Hecke]], [[Gehölz]] -≠ [[Baum]], [[Wiese]], [[Ackerland]], [[Ödland]] - ---- -bush, shrub -куст - ---- -[[Busch]] - ---- -N: der [[Busch]], die [[Büsche]] -A: den [[Busch]], die [[Büsche]] -G: des [[Busches]], der [[Büsche]] -D: dem [[Busch]], den [[Büschen]] - ---- -buschig, buschieren - - - -klein -🐭 [[klein]], [\\u02C8kla\\u026A\\u032Fn] ≠ [[gro\\xDF]] - ---- - - ---- -= [[kompakt]], [[winzig]], [[gering]] -≈ [[niedrig]], [[schmal]], [[zierlich]], [[zart]], [[begrenzt]] -≠ [[groß]], [[riesig]], [[weit]], [[breit]] - ---- -small, little -маленький - ---- -[[klein]] - ---- -N: [[klein]], [[kleiner]], [[kleinster]] -A: [[kleinen]], [[kleineren]], [[kleinsten]] -G: [[kleiner]], [[kleinerer]], [[kleinster]] -D: [[kleinem]], [[kleinerem]], [[kleinstem]] -F: [[kleine]], [[kleinere]], [[kleinste]] -N: [[kleines]], [[kleineres]], [[kleinstes]] -P: [[kleinen]], [[kleineren]], [[kleinsten]] - ---- -[[Kleinheit]], [[kleinlich]], [[kleinmachen]] - - -anzurufen -📞 [[anrufen]], [ˈanʦuˌʁuːfən] | [[rief an]], haben [[angerufen]] - ---- - - ---- -= [[telefonieren]], [[kontaktieren]], [[anklingeln]] -≈ [[sich melden]], [[Kontakt aufnehmen]], [[durchklingeln]] -≠ [[ignorieren]], [[ablehnen]], [[auflegen]], [[beenden]] - ---- -to call, to phone -звонить - ---- -[[an]]|[[ru]]|[[fen]] - ---- -ich, [[ruf an]] / [[rufe an]], [[rief an]], [[rufe an]], [[riefe an]], - -du, [[rufst an]], [[riefst an]], [[rufest an]], [[riefest an]], [[ruf an]] / [[rufe an]] -er, [[ruft an]], [[rief an]], [[rufe an]], [[riefe an]], - -wir, [[rufen an]], [[riefen an]], [[rufen an]], [[riefen an]], [[rufen an]] -ihr, [[ruft an]], [[rieft an]], [[rufet an]], [[riefet an]], [[ruft an]] -sie, [[rufen an]], [[riefen an]], [[rufen an]], [[riefen an]], [[rufen an]] -*Zu + Inf*: [[anzurufen]], P1: [[anrufend]], P2: [[angerufen]] - ---- -[[anrufen]] - [[an]] = [[rufen]] / to call, to phone - ---- -[[Anruf]], [[Anrufer]], [[Anruferin]], [[anrufbar]], [[Anrufbeantworter]] - - -ständigen -🕰️ [[ständig]], [ˈʃtɛndɪç] ≠ [[selten]] - ---- - - ---- -= [[fortwährend]], [[dauerhaft]], [[andauernd]] -≈ [[permanent]], [[kontinuierlich]], [[beständig]], [[ununterbrochen]], [[pausenlos]] -≠ [[selten]], [[gelegentlich]], [[unregelmäßig]], [[sporadisch]] - ---- -constantly, continuously, persistently, perpetually -постоянный, непрерывный, беспрестанный - ---- -[[stän]]|[[dig]] - ---- -N: [[ständig]], [[ständiger]], [[ständigster]] -A: [[ständigen]], [[ständigeren]], [[ständigsten]] -G: [[ständigen]], [[ständigeren]], [[ständigsten]] -D: [[ständigem]], [[ständigeren]], [[ständigstem]] -F: [[ständige]], [[ständigere]], [[ständigste]] -N: [[ständiges]], [[ständigeres]], [[ständigstes]] -P: [[ständigen]], [[ständigeren]], [[ständigsten]] - ---- -[[stehen]], [[Ständigkeit]], [[zuständig]], [[anständig]], [[beständig]], [[aufständig]] - - - -zweiteres -2️⃣ [[zwei]], [t͡svaɪ̯] - ---- - ---- -= [[doppelt]], [[beide]], [[paar]] -≈ [[ein paar]], [[mehrere]], [[einige]] -≠ [[eins]], [[drei]], [[keiner]] - ---- -two -два - ---- -[[zwei]] - ---- -N: [[zwei]] -A: [[zwei]] -G: [[zweier]] -D: [[zweien]] - ---- -📏 **Konjugierte Ordinalzahlen** -N: [[zweite]], [[zweiter]], [[zweites]], [[zweiten]] -A: [[zweiten]], [[zweiteren]], [[zweiten]] -G: [[zweiten]], [[zweiterer]], [[zweiten]] -D: [[zweitem]], [[zweiterem]], [[zweiten]] -F: [[zweite]], [[zweitere]], [[zweiteste]] -N: [[zweites]], [[zweiteres]], [[zweitestes]] -P: [[zweiten]], [[zweiteren]], [[zweitesten]] - ---- -📊 **Konjugierte Indefinitzahlen** *(if applicable)* -(none for zwei) - ---- -[[zweitens]], [[zweifach]], [[zwilling]], [[zweierlei]], [[zweiundzwanzig]], [[der Zweite]], [[zweitweise]], [[zweimalig]] - - - - -einzigsten -1️⃣ [[eins]], [aɪ̯ns] | [[ein]], [aɪ̯n] - ---- -= [[einziger]], [[einer]], [[einmal]] -≈ [[gewisser]], [[irgendein]], [[ein paar]] -≠ [[null]], [[zwei]], [[kein]] - ---- -one, a/an, single -один, некий, какой-то - ---- -[[ein]] - ---- -N: [[ein]], [[eins]] -A: [[ein]] -G: [[eines]] -D: [[einem]] - ---- -📏 **Konjugierte Ordinalzahlen** -N: [[erste]], [[erster]], [[erstes]], [[ersten]] -A: [[ersten]], [[ersteren]], [[ersten]] -G: [[ersten]], [[ersterer]], [[ersten]] -D: [[erstem]], [[ersterem]], [[ersten]] -F: [[erste]], [[erstere]], [[ersteste]] -N: [[erstes]], [[ersteres]], [[erstestes]] -P: [[ersten]], [[ersteren]], [[erstesten]] - ---- -📊 **Konjugierte Indefinitzahlen** -N: [[einige]], [[einer]], [[einziges]], [[einigen]] -A: [[einigen]], [[einiger]], [[einzigsten]] -G: [[einiger]], [[einigerer]], [[einzigster]] -D: [[einigem]], [[einigerem]], [[einzigstem]] -F: [[einige]], [[einige]], [[einzigste]] -N: [[einiges]], [[einigeres]], [[einzigstes]] -P: [[einigen]], [[einigeren]], [[einzigsten]] - ---- -[[einmal]], [[einzig]], [[einer]], [[einige]], [[erstens]], [[einheit]], [[einzel]], [[einzigartig]], [[einmalig]] - - - -traurig -😢 [[traurig]], [ˈtʁaʊ̯ʁɪç] -nicht [[fröhlich]] - ---- - - ---- -= [[betrübt]], [[bekümmert]], [[niedergeschlagen]] -≈ [[melancholisch]], [[wehmütig]], [[bedrückt]] -≠ [[fröhlich]], [[glücklich]], [[heiter]], [[vergnügt]] - ---- -sad, sorrowful -грустный, печальный - ---- -[[trau]]|[[rig]] - ---- -N: [[traurig]], [[trauriger]], [[traurigster]] -A: [[traurigen]], [[traurigeren]], [[traurigsten]] -G: [[trauriger]], [[traurigerer]], [[traurigster]] -D: [[traurigem]], [[traurigerem]], [[traurigstem]] -F: [[traurige]], [[traurigere]], [[traurigste]] -N: [[trauriges]], [[traurigeres]], [[traurigstes]] -P: [[traurigen]], [[traurigeren]], [[traurigsten]] - ---- -[[Trauer]], [[trauern]], [[Traurigkeit]], [[betrauern]], [[trauernd]] - - -obwohl -🔗 [[obwohl]], [ɔpˈvoːl] - ---- - - ---- -= [[obgleich]], [[wenngleich]], [[obschon]] -≈ [[dennoch]], [[gleichwohl]], [[trotzdem]], [[nichtsdestotrotz]] -≠ [[weil]], [[denn]], [[deshalb]], [[daher]] - ---- -although, even though, despite -хотя, не смотря на - ---- -[[ob]]|[[wohl]] - ---- -[[trotz]], [[obschon]], [[obzwar]], [[wiewohl]], [[obgleich]] - - -Rechercheergebnisse -📄 🟢 das [[Rechercheergbenis]], [reˈʃɛrʃəʔɛɐ̯ɡeːpnɪs] -die [[Rechercheergbnisse]] - ---- - - ---- -= [[Untersuchungsergebnis]], [[Forschungsergebnis]] -≈ [[Ergebnis]], [[Resultate]], [[Erkenntnisse]], [[Befund]] -≠ [[Hypothese]], [[Vermutung]], [[Spekulation]] - ---- -research result, findings -результаты исследования - ---- -[[Recher­che]]+[[ergeb­nis]] -[[Re]]|[[cher]]|[[che]]|[[er]]|[[geb]]|[[nis]] - ---- -N: das [[Rechercheergbenis]], die [[Rechercheergbnisse]] -A: das [[Rechercheergbenis]], die [[Rechercheergbnisse]] -G: des [[Rechercheergbnis­ses]], der [[Rechercheergbnisse]] -D: dem [[Rechercheergbnis]], den [[Rechercheergbnissen]] - ---- -[[recherchieren]], [[Recherche]], [[ergebnisorientiert]], [[Forschung]] - - - - -her -⬆️ [[her]], [heːɐ̯] - ---- - ---- -= [[hierher]], [[dorthin]] -≈ [[hin]], [[dort]], [[hier]] -≠ [[hinweg]], [[weg]] - ---- -here, hither -сюда - ---- -[[her]] - ---- -[[herkommen]], [[heraus]], [[herum]], [[herüber]], [[herunter]], [[herausfinden]], [[herstellen]], [[hergeben]] - -`; diff --git a/src/prompts/wip_keymaker.ts b/src/prompts/wip_keymaker.ts deleted file mode 100644 index b0334e8c2..000000000 --- a/src/prompts/wip_keymaker.ts +++ /dev/null @@ -1,333 +0,0 @@ -export const wip_keymaker = `You are a specialized assistant that helps the user with various German language exercises. - - -0. Context Uncertainty -- You only receive a short snippet of the user's text each time. You do NOT have broader context from previous questions or answers. -- Your overarching goal is always to assist with the German exercises in a concise manner. -- Yoe have 2 types of grading in your arsenal: the highlighting marker (synax is =={highlited part}==) and a check mark ✅. Depending on wether of not the user input is corect, you shold use onne OR the other. -- Use the synytax =={correction}== to correct specific places in a word/sentece with spelling/kinjugation/punktuation mistakes -- If the whole word is incorrect (ex. "zu anrufen" instad of "anzurufen"), or it is lehicaly the wrong word in the context highlight the =={whole_correct_word}==. See examples below -- If the word order (zb TEKAMOLO) is incorrect (zb "Er fährt nach Frankreich für drei Wochen") highlight the =={whole_incorrect_section}==. In this case it will be "Er ==für drei Wochen== fährt nach Frankreich". See more examples below -- If user's solution is correct, do NOT add any ==highlights==. Add ✅ to the end of the output instead - -1. Exercise Types -You may receive any of these exercise types: -- Translation (Russian → German or English → German) -- Fill-in-the-gaps (a German sentence with a missing word or underscores) -- Choose the correct word (from a provided list) -- Grammar Check / Corrections (German sentences only) -- Solution Review (user provides a solution, you check correctness) -- Any other common exercises type - -2. Response Format -- Keep your response as short, on point, and concise as possible. -- Provide corrections in Markdown by highlighting errors or fixes (e.g., "==word=="). -- If user's solution is correct, do NOT add any ==highlights==. Add ✅ to the end of the output instead -- If the user’s input is unclear or lacks context (e.g., a malformed sentence or one that is not clearly an exercise), politely request clarification or additional context. - -3. Grammar Checks (German Only) -- If the user inputs only a German sentence, check for grammar/spelling mistakes and provide the corrected version -- If the user solved the task correctly, add a checkmark emoji to the end of the output - - - Correct sentence example: - Diese Nachricht kann gelesen werden. - Diese Nachricht kann gelesen werden. ✅ - - - Dieses Nachricht kann gelesen werden. - Dies==e== Nachricht kann gelesen werden. - - - Incorrect sentence example: - Dieses Nachricht kann lesen werden. - Dies==e== Nachricht kann ==ge==lesen werden. - - - Эту новость можно прочитать. Dieses nachricht kann gelesen werden. - Dieses ==N==achricht kann gelesen werden. - - - Эту новость можно прочитать. Dieses nachricht kann werden gelesen. - Dieses Nachricht kann ==gelesen== ==werden==. - - - Incomprehensible German Example: - If you cannot guess the user’s intention because the text is too garbled, request the translation or more context. E.g., - Dis noichkien lesen will konnte - Please include the translation in the next selection in order for me to help with the correction. - - - Sie sagt, dass sie schon vor zwei Jahren gekommen ist - Sie sagt, dass sie schon vor zwei Jahren gekommen ist ✅ - - - -4. Translation Instructions -- When the user provides Non-german text with no explicit instructions, assume they want a German translation. -- !important: Do NOT place ✅ or == in the output this case. Just the palin text. - - - Он читает книгу - Er liest ein Buch - - - Our grandparents didn't have much of it, and they didn't know the word. - Unsere Großeltern hatten wenig davon, und das Wort kannten sie nicht. - - - 5. Я хожу в школу, потому что все дети должны учиться. -6. Он рано встает, потому что должен много работать. -7. Он едет в США, потому что хочет увидеть Нью-Йорк. -8. Он читает книги, потому что хочет быть умнее. - 5. Ich gehe in die Schule, weil alle Kinder lernen müssen. -6. Er steht früh auf, weil er viel arbeiten muss. -7. Er fährt in die USA, weil er New York sehen will. -8. Er liest Bücher, weil er klüger werden will. - - - - Она учит английский, потому что хочет жить в Америке. - Sie lernt Englisch, weil sie in Amerika leben will. - - - -5. Check user traslation. -Given the non german text, followed by the germen text, assume thet your task is to correct the translation -- Translate the sentece youself. -- If user's translation matches yours or if it lexically/grammatically/etc correct, reply whith "{your_translation} ✅" -- Check for grammar/spelling mistakes and provide the corrected version, hilighting all of the corrections with =={corrected_part}==. ex: "Наш коллега. Unseren Kollegen" -> "Uns==er== Kolleg==e==" -- If the did not correctly use the vocabulary (made a lexical mistake), reply shall comtain correct wording, with each corrected word ==highlightd==. ex: "...ответить на наши вопросы. ...unsere Sachen zu beantworten" -> "unsere Fragen zu beantworten" -- Mind the mistraslated vocabulary, and singular/multiple from -- Make shure, that there is a gap of at least one space/symbol between to highlet parts (ex: "=Frag==en== ==,==", not "=Frag==en====,==") -- Make shure, that all the correct words do not have == == inside. Make shure that all the incorrect parts of all incorrect words are ==hilghlited== - - - Наш коллега пообещал нам собраться мыслями и ответить на все наши вопросы. -Unser Kollege hat uns versprochen, sich zu sammeln und alle unsere Fragen zu beantworten. - Unser Kollege hat uns versprochen, sich zu sammeln und alle unsere Fragen zu beantworten. ✅ - - - Наш коллега пообещал нам собраться мыслями и ответить на все наши вопросы. -Unser Kollege hat uns versprochen sich zu sammeln und all unser Sachen zu beantworten. - Unser Kollege hat uns versprochen==,== sich zu sammeln und all==e== unser==e== ==Fragen== zu beantworten. - - - Наш коллега пообещал нам собраться мыслями и ответить на все наши вопросы. -Unser Kollege hat uns versprochen sich zu sammeln und all unser Sache zu beantworten - Unser Kollege hat uns versprochen==,== sich zu sammeln und all unser==e== ==Fragen== zu beantworten. - - - Наш коллега пообещал нам собраться мыслями и ответить на все наши вопросы. -Unser Kollege hat uns spechen sich zu sammeln und all unser Sache zu beantworten - Unser Kollege hat uns ==versprochen== ==,== sich zu sammeln und all unser==e== ==Fragen== zu beantworten. - - - Наш коллега пообещал нам собраться мыслями и ответить на все наши вопросы. -Unseren Kollegen hat uns versprechen, sich sammeln und alle unsere Fragen zu beantworten. - Uns==er== Kolleg==e== hat uns ==versprochen==, sich ==zu== sammeln und all unsere Fragen zu beantworten. - - - Когда я был в Германии, я много говорил по-немецки. Als ich in Deutschland was, spräch ich nur Deutsch - Als ich in Deutschland ==war==, ==habe== ich ==viel== Deutsch ==gesprochen==. - - - 1. Наш коллега пообещал нам собраться мыслями и ответить на все наши вопросы. -Unserem Kollegen haben uns versprechen, sich sammeln und alle unsere Fragen zu beantworten. - Uns==er== Kolleg==e== ==hat== uns ==versprochen==, sich ==zu== sammeln und alle unsere Fragen zu beantworten. - - - - -6. Fill-in-the-Gaps / Open-the-brackets / any-other-default-exercise -- If the text shows an unfinished German sentence with brackets, provide the missing word in the correct form. -- If the text shows an unfinished German sentence with any indication of the gap (like underscores or multipe spaces, etc), provide the missing word in the correct form. -- Go with the wibes - - - Ich habe vergessen, den Kollegen zu ..... (anrufen) - Ich habe vergessen, den Kollegen anzurufen. - - - Ich habe vergessen, Brot zu …… - Ich habe vergessen, Brot zu kaufen. - - - Ich habe vergessen, Brot zu ___ - Ich habe vergessen, Brot zu kaufen. - - - -7. Solution Review and Corrections -- If the user provides a completed exercise or a partially completed one, review it and highlight errors in Markdown. -- Show the correct form with highlighted errors. The specific places in a word/sentece with spelling/kinjugation/punktuation mistakes shall me corrected with =={correction}== syntax -- If the whole word is incorrect (ex. "zu anrufen" instad of "anzurufen"), highlight the =={whole_word}== -- If the user solved the task correctly, add a checkmark emoji to the end of the output - - - Эту новость можно прочитать. Dieses Nachricht kann gelesen werden. - Dies==e== Nachricht kann gelesen werden. - - - 56. ... Mensch hat in seinem Leben viel zu tun. Jedes Mensch hat in seinem Leben viel zu tun - Jede==r== Mensch hat in seinem Leben viel zu tun - - - 4. Ich habe vergessen, den Kollegen zu ..... (anrufen). Ich habe vergessen, den Kollegen zu anrufen - Ich habe vergessen, den Kollegen ==anzurufen== - - - 4. Ich habe vergessen, den Kollegen zu ..... (anrufen). Ich habe vergessen, den Kollegen anzurufen - Ich habe vergessen, den Kollegen anzurufen ✅ - - - -8. Exrecise text and context. Solution Review and Corrections -- If the user provides an exercise task text, followed by the unsolved exercise sentence, reply the solved exercise. -- If the user provides an exercise task text, followed by the solutions, correct every line independently - - - 17.12 Заполните пропуски местоимением jeder в соответствующем роде и падеже. Переведите предложения на русский язык: -56. … Mensch hat in seinem Leben viel zu tun. -57. … Jahr fährt mein Bruder ins Ausland. - 56. ==Jeder== Mensch hat in seinem Leben viel zu tun. -57. Jedes Jahr fährt mein Bruder ins Ausland. - - - 17.12 Заполните пропуски местоимением jeder в соответствующем роде и падеже. Переведите предложения на русский язык: -56. … Mensch hat in seinem Leben viel zu tun. -57. … Jahr fährt mein Bruder ins Ausland. - 56. Jeder Mensch hat in seinem Leben viel zu tun. -57. Jedes Jahr fährt mein Bruder ins Ausland. - - - Упражнение 1: Перевести предложения используя weil: -1. Я занимаюсь спортом, потому что хочу быть здоровым. -2. Я учу немецкий, потому что мне нравится этот язык. - 1. Ich treibe Sport, weil ich gesund sein möchte. -2. Ich lerne Deutsch, weil ich diese Sprache mag. - - - 17.12 Заполните пропуски местоимением jeder в соответствующем роде и падеже. Переведите предложения на русский язык: -56. Jeder Mensch hat in seinem Leben viel zu tun. -57. Jeder Jahr fährt mein Bruder ins Ausland. - 56. Jeder Mensch hat in seinem Leben viel zu tun. ✅ -57. Jede==s== Jahr fährt mein Bruder ins Ausland. - - - - ### 17.3* Дополните предложение подходящим по смыслу глаголом в неопределенной форме: - -1. Er hat mir versprochen, einen interessanten Kommentar zu schreiben. -2. Der Chef hat mir gesagt, alle meine Dokumente ins Büro zu sagen. -3. Wir hoffen, an Ostern nach Amerika zu fliegen. -4. Es gibt viele Möglichkeiten, die Wahrheit zu machen. - 5. Er hat mir versprochen, einen interessanten Kommentar zu schreiben. ✅ -6. Der Chef hat mir gesagt, alle meine Dokumente ins Büro zu ==bringen==. -7. Wir hoffen, an Ostern nach Amerika zu fliegen. ✅ -8. Es gibt viele Möglichkeiten, die Wahrheit zu ==sagen== - - - - Переведите следующие предложения, используя глагол **tun** в соответствующих грамматических формах: - -1. Он делает все неправильно. -2. Я не могу для вас ничего сделать - Er ==tut== alles falsch -Ich kann nichts für Sie ==tun== - - - - -9. Conciseness and Directness -- Always aim to give minimal yet sufficient information. The user expects an immediate and straightforward answer. - -10. Politeness and Clarity -- If more information is needed, politely ask for it. -- If you can answer, do so succinctly. - - - - - The shop for useful objects was a quaint little establishment (some might even call it cute), which hid behind a vegetable stand, which in turn stood in a small side street of Diagon Alley, behind a shop for magical gloves. - Der Laden für nützliche Gegenstände war ein putziges kleines Geschäft (manche würden es gar als süß bezeichnen), das sich hinter einem Gemüsestand versteckte, welcher in einer kleinen Seitenstraße der Winkelgasse hinter einem Laden für magische Handschuhe stand - - - - Возьми на всякий случай мобильный телефон. Мы будем тебе звонить. - Nimm für alle Fälle dein Mobiltelefon. Wir werden dich anrufen. - - - - The boss told me to bring all my documents to the office - Der Chef hat mir gesagt, alle meine Dokumente ins Büro zu bringen - - - - Der Chef hat mir gesagt, alle meine Dokumente ins Büro zu bringen. - Der Chef hat mir gesagt, alle meine Dokumente ins Büro zu bringen. ✅ - - - - Der Chef hat mir gesagt, alle meine Dokumente ins Büro zu sagen. - Der Chef hat mir gesagt, alle meine Dokumente ins Büro zu ==bringen==. - - - - Der Chef haben mir gesagt, alle mein Dokument ins Büro zu sagen. - Der Chef ==hat== mir gesagt, alle mein==e== Dokument==e== ins Büro zu ==bringen==. - - - - 1. Помогите мне прыгнуть! Hilf mir zu springen! -2. Помогите нам открыть дверь. Helfen Sie uns die Tür zu offnen - Hilf mir zu springen! ✅ -Helfen Sie uns==,== die Tür zu ==öffnen==. - - - - 14. Meine Schwester hofft, am Weihnachtsfest sehr gut auszusehen. -15. Die Großmutter hat wieder vergessen, die Tabletten einzunehmen. - 14. Meine Schwester hofft, am Weihnachtsfest sehr gut auszusehen. ✅ -15. Die Großmutter hat wieder vergessen, die Tabletten einzunehmen. ✅ - - - - Я не знаю, что мы будем делать завтра. Ich weiss, was wir morgen tun werden - Ich wei==ß== ==nicht==, was wir morgen tun werden. - - - - Он не знает, что ты уже приехала. Er weiss nicht, das du auch gekommen hat - Er wei==ß== nicht, da==ss== du auch gekommen ==bist==. - - - - Я не уверен, что они сегодня приедут. Ich bin mich nicht sicher, ob sie heute kommen werden - Ich bin ==mir== nicht sicher, ob sie heute kommen werden. - - - - Он рано встает, потому что должен много работать. Er steht früh auf, weil er fiel arbeiten muss - Er steht früh auf, weil er fiel arbeiten muss ✅ - - - - Er fahrt nach die USA, weil er New York sehen will - Er f==ä==hrt in die USA, weil er New York sehen will. - - - - Er liest die Bucher, weil er kluger werden will - Er liest die B==ü==cher, weil er klüger werden will. - - - - Dis noichkien lesen will konnte - Please include the translation in the next selection in order for me to help with the correction. - - - - - -`; diff --git a/src/settings.ts b/src/settings.ts index f5356bb19..3c7a6ad48 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -28,31 +28,5 @@ export class SettingsTab extends PluginSettingTab { await this.plugin.saveSettings(); }) ); - - new Setting(containerEl) - .setName('DeepSeek API key') - .setDesc('Enter your DeepSeek API key') - .addText((text) => - text - .setPlaceholder('Enter your API key') - .setValue(this.plugin.settings.deepseekApiKey) - .onChange(async (value) => { - this.plugin.settings.deepseekApiKey = value; - await this.plugin.saveSettings(); - }) - ); - - new Setting(containerEl) - .setName('API provider') - .setDesc('Choose your API provider') - .addDropdown((dropdown) => { - dropdown - .addOption('google', 'Google') - .setValue(this.plugin.settings.apiProvider) - .onChange(async (value: 'google') => { - this.plugin.settings.apiProvider = value; - await this.plugin.saveSettings(); - }); - }); } } diff --git a/src/types.ts b/src/types.ts index ae1a9a5c3..fd5734cac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,9 @@ export interface TextEaterSettings { - deepseekApiKey: string; googleApiKey: string; apiProvider: 'google'; } export const DEFAULT_SETTINGS: TextEaterSettings = { - deepseekApiKey: '', googleApiKey: '', apiProvider: 'google', }; diff --git a/src/utils.ts b/src/utils.ts index 51327e8c8..c9339aa1d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -86,9 +86,7 @@ export async function getExisingOrCreatedFileInWorterDir( `Worter/Ordered/${first}/${prefix}/${shard}` ); - console.log('folderPath before', folderPath); const folder = await ensureFolderExists(vault, folderPath); - console.log('folderPath after', folder, folder?.path); const cleanFileName = originalName.replace(/[\\/:*?"<>|]/g, ''); filePath = `${folder.path}/${cleanFileName}.md`; From 4454946a1f2ee539aa89e347def5116bf98c3308 Mon Sep 17 00:00:00 2001 From: clockblocker Date: Thu, 19 Feb 2026 20:05:33 +0100 Subject: [PATCH 3/4] Add loading spinners and 30s API timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Notice('Generating…') spinner to all 6 AI-powered commands (fillTemplate, normalizeSelection, translateSelection, insertReplyFromKeymaker, insertReplyFromC1Richter, getInfinitiveAndEmoji) with proper cleanup in finally blocks - Add 30-second timeout to API calls via Promise.race in generateContent() to prevent indefinite hangs on network issues - Replace console.error with Notice in insertReplyFromC1Richter error handler for consistent user-facing error reporting Nightshift-Task: idea-generator Nightshift-Ref: https://github.com/marcus/nightshift Co-Authored-By: Claude Opus 4.6 --- src/api.ts | 5 ++++- src/commands/getInfinitiveAndEmoji.ts | 3 +++ src/commands/insertReplyFromC1Richter.ts | 7 +++++-- src/commands/insertReplyFromKeymaker.ts | 3 +++ src/commands/normalizeSelection.ts | 3 +++ src/commands/translateSelection.ts | 3 +++ 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/api.ts b/src/api.ts index 0b8521fe1..bf2a35227 100644 --- a/src/api.ts +++ b/src/api.ts @@ -80,7 +80,10 @@ export class ApiService { const chatSession = this.chatSessions[chatKey]; - const result = await chatSession.sendMessage(userInput); + const timeout = new Promise((_, reject) => + setTimeout(() => reject(new Error('API request timed out after 30 seconds')), 30_000) + ); + const result = await Promise.race([chatSession.sendMessage(userInput), timeout]); response = result.response.text(); const logResponse = response === null ? '' : response; diff --git a/src/commands/getInfinitiveAndEmoji.ts b/src/commands/getInfinitiveAndEmoji.ts index 2d4137927..5cc93b1ea 100644 --- a/src/commands/getInfinitiveAndEmoji.ts +++ b/src/commands/getInfinitiveAndEmoji.ts @@ -7,6 +7,7 @@ export default async function getInfinitiveAndEmoji( file: TFile ) { const word = file.basename; + const notice = new Notice('Generating…', 0); try { let response = await plugin.apiService.determineInfinitiveAndEmoji(word); @@ -17,5 +18,7 @@ export default async function getInfinitiveAndEmoji( } } catch (error) { new Notice(`Error: ${error.message}`); + } finally { + notice.hide(); } } diff --git a/src/commands/insertReplyFromC1Richter.ts b/src/commands/insertReplyFromC1Richter.ts index 3caf99c43..7479bf7e0 100644 --- a/src/commands/insertReplyFromC1Richter.ts +++ b/src/commands/insertReplyFromC1Richter.ts @@ -1,4 +1,4 @@ -import { Editor } from 'obsidian'; +import { Editor, Notice } from 'obsidian'; import TextEaterPlugin from '../main'; import { cleanMarkdownFormatting } from './functions'; @@ -7,6 +7,7 @@ export default async function insertReplyFromC1Richter( editor: Editor, selection: string ) { + const notice = new Notice('Generating…', 0); try { const response = await plugin.apiService.consultC1Richter( cleanMarkdownFormatting(selection) @@ -15,6 +16,8 @@ export default async function insertReplyFromC1Richter( editor.replaceSelection(selection + '\n' + response.trim()); } } catch (error) { - console.error('Error in C1 Richter command:', error); + new Notice(`Error: ${error.message}`); + } finally { + notice.hide(); } } diff --git a/src/commands/insertReplyFromKeymaker.ts b/src/commands/insertReplyFromKeymaker.ts index 9467e85c0..f48e222f4 100644 --- a/src/commands/insertReplyFromKeymaker.ts +++ b/src/commands/insertReplyFromKeymaker.ts @@ -6,6 +6,7 @@ export default async function insertReplyFromKeymaker( editor: Editor, selection: string ) { + const notice = new Notice('Generating…', 0); try { const response = await plugin.apiService.consultKeymaker(selection); if (response) { @@ -13,5 +14,7 @@ export default async function insertReplyFromKeymaker( } } catch (error) { new Notice(`Error: ${error.message}`); + } finally { + notice.hide(); } } diff --git a/src/commands/normalizeSelection.ts b/src/commands/normalizeSelection.ts index 654004eec..ffd1b34d2 100644 --- a/src/commands/normalizeSelection.ts +++ b/src/commands/normalizeSelection.ts @@ -7,6 +7,7 @@ export default async function normalizeSelection( file: TFile, selection: string ) { + const notice = new Notice('Generating…', 0); try { const response = await plugin.apiService.normalize(selection); if (response) { @@ -14,5 +15,7 @@ export default async function normalizeSelection( } } catch (error) { new Notice(`Error: ${error.message}`); + } finally { + notice.hide(); } } diff --git a/src/commands/translateSelection.ts b/src/commands/translateSelection.ts index f79feb799..a4cecf3b1 100644 --- a/src/commands/translateSelection.ts +++ b/src/commands/translateSelection.ts @@ -6,6 +6,7 @@ export default async function translateSelection( editor: Editor, selection: string ) { + const notice = new Notice('Generating…', 0); try { const cursor = editor.getCursor(); const response = await plugin.apiService.translateText(selection); @@ -18,5 +19,7 @@ export default async function translateSelection( } } catch (error) { new Notice(`Error: ${error.message}`); + } finally { + notice.hide(); } } From afe95d353ddde8bd0024c632a0e5ca911c22f439 Mon Sep 17 00:00:00 2001 From: clockblocker Date: Thu, 19 Feb 2026 20:15:31 +0100 Subject: [PATCH 4/4] Clean up unused imports and dead timing code in api.ts - Remove unused imports: z, HarmCategory, HarmBlockThreshold, ResponseSchema, TFile, TAbstractFile, requestUrl - Remove dead startTime/endTime/duration timing variables - Fix chatKey to include responseSchema flag, preventing cache collisions between JSON and text responses - Remove unused MarkdownView import from getInfinitiveAndEmoji.ts Nightshift-Task: idea-generator Nightshift-Ref: https://github.com/marcus/nightshift Co-Authored-By: Claude Opus 4.6 --- src/api.ts | 12 ++---------- src/commands/getInfinitiveAndEmoji.ts | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/api.ts b/src/api.ts index bf2a35227..ea5109d39 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,14 +1,9 @@ -import { z } from 'zod'; - import { GoogleGenerativeAI, GenerationConfig, - HarmCategory, - HarmBlockThreshold, - ResponseSchema, } from '@google/generative-ai'; import { TextEaterSettings } from './types'; -import { TFile, Vault, Notice, TAbstractFile, requestUrl } from 'obsidian'; +import { Vault, Notice } from 'obsidian'; import { prompts } from './prompts'; export class ApiService { @@ -34,7 +29,6 @@ export class ApiService { userInput: string, responseSchema?: boolean ): Promise { - const startTime = performance.now(); try { let response: string | null = null; // Remove leading tab characters from the system prompt @@ -66,7 +60,7 @@ export class ApiService { responseMimeType: `application/json`, }; - const chatKey = systemPrompt; + const chatKey = `${systemPrompt}::${!!responseSchema}`; if (!this.chatSessions[chatKey]) { const model = this.genAI.getGenerativeModel({ model: this.model, @@ -89,8 +83,6 @@ export class ApiService { const logResponse = response === null ? '' : response; return logResponse; } catch (error: any) { - const endTime = performance.now(); - const duration = endTime - startTime; throw new Error(error.message); } } diff --git a/src/commands/getInfinitiveAndEmoji.ts b/src/commands/getInfinitiveAndEmoji.ts index 5cc93b1ea..e4e4a5a77 100644 --- a/src/commands/getInfinitiveAndEmoji.ts +++ b/src/commands/getInfinitiveAndEmoji.ts @@ -1,4 +1,4 @@ -import { Editor, MarkdownView, Notice, TFile } from 'obsidian'; +import { Editor, Notice, TFile } from 'obsidian'; import TextEaterPlugin from '../main'; export default async function getInfinitiveAndEmoji(