mirror of
https://github.com/jacobinwwey/obsidian-NotEMD.git
synced 2026-07-22 05:48:27 +00:00
feat: implement first-principles language support architecture
This commit is contained in:
parent
0bd3183783
commit
a61ef462b7
59 changed files with 2578 additions and 106 deletions
|
|
@ -1,3 +1,4 @@
|
|||
node_modules/
|
||||
|
||||
main.js
|
||||
ref/
|
||||
|
|
|
|||
28
README.md
28
README.md
|
|
@ -212,6 +212,14 @@ Access plugin settings via:
|
|||
|
||||
<img width="817" height="428" alt="Multi-model" src="https://github.com/user-attachments/assets/85e6b854-c0ca-45cc-a55e-24638dceb120" />
|
||||
|
||||
### Language Architecture (UI Locale vs Task Output Language)
|
||||
|
||||
- **UI Locale** controls only plugin interface text (Settings labels, sidebar buttons, notices, and dialogs). The default `auto` mode follows Obsidian's current UI language.
|
||||
- **Task Output Language** controls model-generated task output (links, summaries, title generation, Mermaid summary, concept extraction, translation target).
|
||||
- **Per-task language mode** lets each task resolve its own output language from a unified policy layer instead of scattered per-module overrides.
|
||||
- **Disable auto translation** keeps non-Translate tasks in source-language context, while explicit Translate tasks still enforce the configured target language.
|
||||
- Mermaid-related generation paths follow the same language policy and can still trigger Mermaid auto-fix when enabled.
|
||||
|
||||
### Stable API Call Settings
|
||||
- **Enable Stable API Calls (Retry Logic)**:
|
||||
* **Disabled (Default)**: A single API call failure will stop the current task.
|
||||
|
|
@ -618,6 +626,26 @@ This is the core functionality focused on identifying concepts and adding `[[wik
|
|||
|
||||
Contributions are welcome! Please refer to the GitHub repository for guidelines: [https://github.com/Jacobinwwey/obsidian-NotEMD](https://github.com/Jacobinwwey/obsidian-NotEMD)
|
||||
|
||||
## Maintainer Regression & Release Workflow
|
||||
|
||||
1. Capture a before-change baseline:
|
||||
```bash
|
||||
npm run regression:language-baseline
|
||||
```
|
||||
2. Implement changes, then compare against the baseline:
|
||||
```bash
|
||||
npm run regression:language-compare
|
||||
```
|
||||
3. Run verification gates before release:
|
||||
```bash
|
||||
npm run build
|
||||
npm test -- --runInBand
|
||||
obsidian help
|
||||
obsidian-cli help
|
||||
```
|
||||
4. GitHub release body must be fully bilingual, with one complete English section and one complete Chinese section. Each section must be independently readable.
|
||||
5. Required release assets: `main.js`, `manifest.json`, `styles.css`, and `README.md`.
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See [LICENSE](LICENSE) file for details.
|
||||
|
|
|
|||
28
README_zh.md
28
README_zh.md
|
|
@ -222,6 +222,14 @@ Notemd 通过与各种大型语言模型 (LLM) 集成来增强您的 Obsidian
|
|||
|
||||
<img width="817" height="428" alt="Multi-model" src="https://github.com/user-attachments/assets/85e6b854-c0ca-45cc-a55e-24638dceb120" />
|
||||
|
||||
### 语言架构(UI 语言 vs 任务输出语言)
|
||||
|
||||
- **UI 语言(UI Locale)**只控制插件界面文案(设置项、侧边栏按钮、提示、弹窗)。默认 `auto` 会跟随 Obsidian 当前界面语言。
|
||||
- **任务输出语言(Task Output Language)**控制模型生成内容的语言(添加链接、研究摘要、按标题生成、Mermaid 总结、概念提取、翻译目标)。
|
||||
- **按任务语言模式**通过统一策略层解析每个任务的输出语言,避免分散在各模块中的语言分支造成行为漂移。
|
||||
- **禁用自动翻译**后,非翻译任务会保留原文语言上下文;显式“翻译”任务仍按目标语言执行。
|
||||
- Mermaid 相关生成链路与上述统一语言策略保持一致,并在开启时继续支持自动 Mermaid 修复。
|
||||
|
||||
### 稳定 API 调用设置 (Stable API Call Settings)
|
||||
|
||||
- 启用稳定 API 调用(重试逻辑)(Enable Stable API Calls (Retry Logic))
|
||||
|
|
@ -688,6 +696,26 @@ Notemd 通过与各种大型语言模型 (LLM) 集成来增强您的 Obsidian
|
|||
|
||||
欢迎任何形式的贡献!请参考GitHub项目说明: [https://github.com/Jacobinwwey/obsidian-NotEMD](https://github.com/Jacobinwwey/obsidian-NotEMD)
|
||||
|
||||
## 维护者回归与发布流程
|
||||
|
||||
1. 先采集变更前基线:
|
||||
```bash
|
||||
npm run regression:language-baseline
|
||||
```
|
||||
2. 完成功能修改后,执行对比回归:
|
||||
```bash
|
||||
npm run regression:language-compare
|
||||
```
|
||||
3. 发布前执行验证门禁:
|
||||
```bash
|
||||
npm run build
|
||||
npm test -- --runInBand
|
||||
obsidian help
|
||||
obsidian-cli help
|
||||
```
|
||||
4. GitHub Release 描述必须为完整双语:独立英文段落 + 独立中文段落,且各自可单独阅读。
|
||||
5. Release 资产必须包含:`main.js`、`manifest.json`、`styles.css`、`README.md`。
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT许可证 - 详情请见[LICENSE](LICENSE)。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> notemd@1.7.14 build
|
||||
> tsc -noEmit -skipLibCheck && node esbuild.config.mjs production
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
0
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> notemd@1.7.14 build
|
||||
> tsc -noEmit -skipLibCheck && node esbuild.config.mjs production
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
0
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> notemd@1.7.14 build
|
||||
> tsc -noEmit -skipLibCheck && node esbuild.config.mjs production
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> notemd@1.7.14 build
|
||||
> tsc -noEmit -skipLibCheck && node esbuild.config.mjs production
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
=== DATE ===
|
||||
2026-04-09T06:17:03-05:00
|
||||
|
||||
=== NODE/NPM ===
|
||||
v24.14.1
|
||||
11.11.0
|
||||
|
||||
=== OBSIDIAN CLI ===
|
||||
Ignored: Error: ENOENT: no such file or directory, open '/root/.config/obsidian/obsidian.json'
|
||||
2026-04-09 11:17:03 Loaded main app package /opt/obsidian-desktop/1.12.7/resources/obsidian.asar
|
||||
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/workflowButtons.test.ts src/tests/sidebarDomButtonClicks.test.ts src/tests/llmUtilsProviderSupport.test.ts src/tests/providerDiagnostics.test.ts
|
||||
|
||||
PASS src/tests/llmUtilsProviderSupport.test.ts
|
||||
● Console
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 2 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Google API Call: Attempt 1 failed: Google API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Azure OpenAI API Call: Attempt 1 failed: Azure OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API error: 400 - Bad request
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: The operation was aborted.
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Anthropic API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Ollama API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
PASS src/tests/sidebarDomButtonClicks.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Active.txt: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:566:29)
|
||||
|
||||
PASS src/tests/providerDiagnostics.test.ts
|
||||
PASS src/tests/workflowButtons.test.ts
|
||||
|
||||
Test Suites: 4 passed, 4 total
|
||||
Tests: 80 passed, 80 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.782 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/workflowButtons.test.ts|src\/tests\/sidebarDomButtonClicks.test.ts|src\/tests\/llmUtilsProviderSupport.test.ts|src\/tests\/providerDiagnostics.test.ts/i.
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/workflowButtons.test.ts src/tests/sidebarDomButtonClicks.test.ts src/tests/llmUtilsProviderSupport.test.ts src/tests/providerDiagnostics.test.ts
|
||||
|
||||
PASS src/tests/llmUtilsProviderSupport.test.ts
|
||||
● Console
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 2 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Google API Call: Attempt 1 failed: Google API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Azure OpenAI API Call: Attempt 1 failed: Azure OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API error: 400 - Bad request
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: The operation was aborted.
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Anthropic API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Ollama API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
PASS src/tests/sidebarDomButtonClicks.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Active.txt: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:576:29)
|
||||
|
||||
PASS src/tests/providerDiagnostics.test.ts
|
||||
PASS src/tests/workflowButtons.test.ts
|
||||
|
||||
Test Suites: 4 passed, 4 total
|
||||
Tests: 80 passed, 80 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.379 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/workflowButtons.test.ts|src\/tests\/sidebarDomButtonClicks.test.ts|src\/tests\/llmUtilsProviderSupport.test.ts|src\/tests\/providerDiagnostics.test.ts/i.
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/workflowButtons.test.ts src/tests/sidebarDomButtonClicks.test.ts src/tests/llmUtilsProviderSupport.test.ts src/tests/providerDiagnostics.test.ts
|
||||
|
||||
PASS src/tests/llmUtilsProviderSupport.test.ts
|
||||
● Console
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 2 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Google API Call: Attempt 1 failed: Google API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Azure OpenAI API Call: Attempt 1 failed: Azure OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API error: 400 - Bad request
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: The operation was aborted.
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Anthropic API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Ollama API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
PASS src/tests/sidebarDomButtonClicks.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Active.txt: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:576:29)
|
||||
|
||||
PASS src/tests/providerDiagnostics.test.ts
|
||||
PASS src/tests/workflowButtons.test.ts
|
||||
|
||||
Test Suites: 4 passed, 4 total
|
||||
Tests: 80 passed, 80 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.369 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/workflowButtons.test.ts|src\/tests\/sidebarDomButtonClicks.test.ts|src\/tests\/llmUtilsProviderSupport.test.ts|src\/tests\/providerDiagnostics.test.ts/i.
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/languagePolicy.test.ts
|
||||
|
||||
PASS src/tests/languagePolicy.test.ts
|
||||
task language policy
|
||||
✓ uses global language when per-task language is disabled (1 ms)
|
||||
✓ uses task-specific language when enabled
|
||||
✓ uses extractOriginalText language for merged mode
|
||||
✓ translate task always uses global translation target
|
||||
✓ resolves language display name from configured language list
|
||||
✓ disableAutoTranslation is ignored for explicit translate task
|
||||
✓ disableAutoTranslation disables auto-translation for non-translate tasks
|
||||
ui locale resolution
|
||||
✓ uses manual locale when configured and supported
|
||||
✓ uses obsidian locale when ui locale is auto
|
||||
✓ falls back to en when unsupported
|
||||
|
||||
Test Suites: 1 passed, 1 total
|
||||
Tests: 10 passed, 10 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.222 s
|
||||
Ran all test suites matching /src\/tests\/languagePolicy.test.ts/i.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/languagePolicy.test.ts
|
||||
|
||||
No tests found, exiting with code 1
|
||||
Run with `--passWithNoTests` to exit with code 0
|
||||
In /home/jacob/obsidian-NotEMD
|
||||
631 files checked.
|
||||
testMatch: **/tests/**/*.test.ts - 149 matches
|
||||
testPathIgnorePatterns: /node_modules/, /ref/ - 84 matches
|
||||
testRegex: - 0 matches
|
||||
Pattern: src/tests/languagePolicy.test.ts - 0 matches
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/i18nFallback.test.ts
|
||||
|
||||
PASS src/tests/i18nFallback.test.ts
|
||||
i18n fallback and cache
|
||||
✓ falls back to English when locale is unsupported (1 ms)
|
||||
✓ supports zh locale alias resolution
|
||||
✓ fills missing localized keys from English base catalog (1 ms)
|
||||
✓ returns cached object for identical locale
|
||||
✓ resolves strings using Obsidian language when uiLocale is auto
|
||||
✓ manual ui locale override has priority over Obsidian language
|
||||
✓ formats placeholders
|
||||
|
||||
Test Suites: 1 passed, 1 total
|
||||
Tests: 7 passed, 7 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.234 s
|
||||
Ran all test suites matching /src\/tests\/i18nFallback.test.ts/i.
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/workflowButtons.test.ts src/tests/sidebarDomButtonClicks.test.ts src/tests/llmUtilsProviderSupport.test.ts src/tests/providerDiagnostics.test.ts
|
||||
|
||||
PASS src/tests/llmUtilsProviderSupport.test.ts
|
||||
● Console
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 2 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Google API Call: Attempt 1 failed: Google API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Azure OpenAI API Call: Attempt 1 failed: Azure OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API error: 400 - Bad request
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: The operation was aborted.
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Anthropic API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Ollama API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
PASS src/tests/sidebarDomButtonClicks.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Active.txt: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:566:29)
|
||||
|
||||
PASS src/tests/providerDiagnostics.test.ts
|
||||
PASS src/tests/workflowButtons.test.ts
|
||||
|
||||
Test Suites: 4 passed, 4 total
|
||||
Tests: 80 passed, 80 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.384 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/workflowButtons.test.ts|src\/tests\/sidebarDomButtonClicks.test.ts|src\/tests\/llmUtilsProviderSupport.test.ts|src\/tests\/providerDiagnostics.test.ts/i.
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/providerDiagnostics.test.ts src/tests/sidebarDomButtonClicks.test.ts
|
||||
|
||||
PASS src/tests/sidebarDomButtonClicks.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Active.txt: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:574:29)
|
||||
|
||||
PASS src/tests/providerDiagnostics.test.ts
|
||||
|
||||
Test Suites: 2 passed, 2 total
|
||||
Tests: 14 passed, 14 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.326 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/providerDiagnostics.test.ts|src\/tests\/sidebarDomButtonClicks.test.ts/i.
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/workflowButtons.test.ts src/tests/sidebarDomButtonClicks.test.ts src/tests/llmUtilsProviderSupport.test.ts src/tests/providerDiagnostics.test.ts
|
||||
|
||||
PASS src/tests/llmUtilsProviderSupport.test.ts
|
||||
● Console
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 2 failed: OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Google API Call: Attempt 1 failed: Google API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Azure OpenAI API Call: Attempt 1 failed: Azure OpenAI API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: net::ERR_CONNECTION_CLOSED
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API error: 400 - Bad request
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: The operation was aborted.
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
OpenAI API Call: Attempt 1 failed: OpenAI API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
OpenAI API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Anthropic API Call: Attempt 1 failed: Anthropic API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Anthropic API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.warn
|
||||
Ollama API Call: Attempt 1 failed: Ollama API request failed: socket hang up
|
||||
|
||||
2349 | lastError = error instanceof Error ? error : new Error(errorMessage); // Store Error object if possible
|
||||
2350 | progressReporter.log(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
> 2351 | console.warn(`${provider.name} API Call: Attempt ${attempt} failed: ${errorMessage}`);
|
||||
| ^
|
||||
2352 |
|
||||
2353 | // Handle cancellation specifically
|
||||
2354 | if ((error instanceof Error && error.name === 'AbortError') || errorMessage.includes("cancelled by user")) {
|
||||
|
||||
at src/llmUtils.ts:2351:21
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
console.error
|
||||
Ollama API Call: All configured attempts failed.
|
||||
|
||||
2407 | }
|
||||
2408 |
|
||||
> 2409 | console.error(`${provider.name} API Call: All configured attempts failed.`);
|
||||
| ^
|
||||
2410 | throw lastError || new Error(`${provider.name} API call failed after multiple retries.`);
|
||||
2411 | }
|
||||
2412 |
|
||||
|
||||
at src/llmUtils.ts:2409:13
|
||||
at Generator.throw (<anonymous>)
|
||||
at rejected (node_modules/tslib/tslib.js:116:69)
|
||||
|
||||
PASS src/tests/sidebarDomButtonClicks.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Active.txt: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:566:29)
|
||||
|
||||
PASS src/tests/providerDiagnostics.test.ts
|
||||
PASS src/tests/workflowButtons.test.ts
|
||||
|
||||
Test Suites: 4 passed, 4 total
|
||||
Tests: 80 passed, 80 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.384 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/workflowButtons.test.ts|src\/tests\/sidebarDomButtonClicks.test.ts|src\/tests\/llmUtilsProviderSupport.test.ts|src\/tests\/providerDiagnostics.test.ts/i.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/sidebarDomButtonClicks.test.ts src/tests/sidebarButtonTriggerChains.test.ts
|
||||
|
||||
PASS src/tests/sidebarButtonTriggerChains.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Current.md: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:574:29)
|
||||
|
||||
PASS src/tests/sidebarDomButtonClicks.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Active.txt: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:574:29)
|
||||
|
||||
|
||||
Test Suites: 2 passed, 2 total
|
||||
Tests: 23 passed, 23 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.293 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/sidebarDomButtonClicks.test.ts|src\/tests\/sidebarButtonTriggerChains.test.ts/i.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/providerDiagnostics.test.ts src/tests/sidebarDomButtonClicks.test.ts src/tests/sidebarButtonTriggerChains.test.ts
|
||||
|
||||
PASS src/tests/sidebarButtonTriggerChains.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Current.md: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:574:29)
|
||||
|
||||
PASS src/tests/sidebarDomButtonClicks.test.ts
|
||||
● Console
|
||||
|
||||
console.log
|
||||
Potential duplicates in Active.txt: [ 'duplicate' ]
|
||||
|
||||
at NotemdSidebarView.<anonymous> (src/ui/NotemdSidebarView.ts:574:29)
|
||||
|
||||
PASS src/tests/providerDiagnostics.test.ts
|
||||
|
||||
Test Suites: 3 passed, 3 total
|
||||
Tests: 32 passed, 32 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.423 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/providerDiagnostics.test.ts|src\/tests\/sidebarDomButtonClicks.test.ts|src\/tests\/sidebarButtonTriggerChains.test.ts/i.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/languagePolicy.test.ts src/tests/processFile.test.ts src/tests/workflowButtons.test.ts
|
||||
|
||||
PASS src/tests/processFile.test.ts
|
||||
PASS src/tests/languagePolicy.test.ts
|
||||
PASS src/tests/workflowButtons.test.ts
|
||||
|
||||
Test Suites: 3 passed, 3 total
|
||||
Tests: 17 passed, 17 total
|
||||
Snapshots: 0 total
|
||||
Time: 0.374 s, estimated 1 s
|
||||
Ran all test suites matching /src\/tests\/languagePolicy.test.ts|src\/tests\/processFile.test.ts|src\/tests\/workflowButtons.test.ts/i.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> notemd@1.7.14 build
|
||||
> tsc -noEmit -skipLibCheck && node esbuild.config.mjs production
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Ignored: Error: ENOENT: no such file or directory, open '/root/.config/obsidian/obsidian.json'
|
||||
2026-04-09 11:15:42 Loaded main app package /opt/obsidian-desktop/1.12.7/resources/obsidian.asar
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
> notemd@1.7.14 regression:language-baseline
|
||||
> bash scripts/regression/language-support-baseline.sh
|
||||
|
||||
[baseline] writing into docs/superpowers/baselines/2026-04-09-language-support
|
||||
[baseline] done
|
||||
BASE_DIR=docs/superpowers/baselines/2026-04-09-language-support
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
> notemd@1.7.14 regression:language-compare
|
||||
> bash scripts/regression/language-support-compare.sh
|
||||
|
||||
[compare] using baseline docs/superpowers/baselines/2026-04-09-language-support
|
||||
[compare] PASS: build + targeted regressions are stable
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
> notemd@1.7.14 test
|
||||
> jest --runInBand src/tests/workflowButtons.test.ts src/tests/sidebarDomButtonClicks.test.ts src/tests/llmUtilsProviderSupport.test.ts src/tests/providerDiagnostics.test.ts src/tests/languagePolicy.test.ts src/tests/i18nFallback.test.ts src/tests/localeFormat.test.ts
|
||||
|
||||
25
docs/superpowers/baselines/README.md
Normal file
25
docs/superpowers/baselines/README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Regression Baselines
|
||||
|
||||
This directory stores reproducible before/after evidence for high-risk changes.
|
||||
|
||||
## Standard workflow
|
||||
|
||||
1. Capture baseline:
|
||||
```bash
|
||||
npm run regression:language-baseline
|
||||
```
|
||||
|
||||
2. Implement changes.
|
||||
|
||||
3. Compare against latest baseline:
|
||||
```bash
|
||||
npm run regression:language-compare
|
||||
```
|
||||
|
||||
4. If compare fails, fix regressions before merging.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Use dated folders: `YYYY-MM-DD-<topic>`.
|
||||
- Keep both command output and exit codes when relevant.
|
||||
- Never overwrite historical baseline logs; append new files or create a new dated folder.
|
||||
|
|
@ -3,6 +3,7 @@ module.exports = {
|
|||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['ts', 'js', 'json'],
|
||||
testMatch: ['**/tests/**/*.test.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/ref/'],
|
||||
moduleNameMapper: {
|
||||
'^obsidian$': '<rootDir>/src/__mocks__/obsidian.ts',
|
||||
'^mermaid$': '<rootDir>/src/__mocks__/mermaid.ts'
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"test": "jest",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"diagnose:llm": "node scripts/diagnose-llm-provider.js"
|
||||
"diagnose:llm": "node scripts/diagnose-llm-provider.js",
|
||||
"regression:language-baseline": "bash scripts/regression/language-support-baseline.sh",
|
||||
"regression:language-compare": "bash scripts/regression/language-support-compare.sh"
|
||||
},
|
||||
"keywords": [
|
||||
"obsidian",
|
||||
|
|
|
|||
42
scripts/regression/language-support-baseline.sh
Executable file
42
scripts/regression/language-support-baseline.sh
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
STAMP="${1:-$(date +%F)}"
|
||||
BASE_DIR="docs/superpowers/baselines/${STAMP}-language-support"
|
||||
mkdir -p "$BASE_DIR"
|
||||
|
||||
echo "[baseline] writing into $BASE_DIR"
|
||||
|
||||
{
|
||||
echo "=== DATE ==="
|
||||
date -Is
|
||||
echo
|
||||
echo "=== NODE/NPM ==="
|
||||
node -v
|
||||
npm -v
|
||||
echo
|
||||
echo "=== OBSIDIAN CLI ==="
|
||||
obsidian help || true
|
||||
echo
|
||||
obsidian-cli help || true
|
||||
} > "$BASE_DIR/environment-baseline.txt"
|
||||
|
||||
set +e
|
||||
npm run build > "$BASE_DIR/build-baseline.txt" 2>&1
|
||||
BUILD_EXIT=$?
|
||||
set -e
|
||||
|
||||
echo "$BUILD_EXIT" > "$BASE_DIR/build-baseline.exitcode"
|
||||
|
||||
npm test -- --runInBand \
|
||||
src/tests/workflowButtons.test.ts \
|
||||
src/tests/sidebarDomButtonClicks.test.ts \
|
||||
src/tests/llmUtilsProviderSupport.test.ts \
|
||||
src/tests/providerDiagnostics.test.ts \
|
||||
> "$BASE_DIR/targeted-tests-baseline.txt" 2>&1
|
||||
|
||||
echo "[baseline] done"
|
||||
echo "BASE_DIR=$BASE_DIR"
|
||||
54
scripts/regression/language-support-compare.sh
Executable file
54
scripts/regression/language-support-compare.sh
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
BASE_DIR="${1:-}"
|
||||
if [[ -z "$BASE_DIR" ]]; then
|
||||
BASE_DIR="$(ls -td docs/superpowers/baselines/*-language-support 2>/dev/null | head -n1 || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$BASE_DIR" || ! -d "$BASE_DIR" ]]; then
|
||||
echo "[compare] baseline directory not found"
|
||||
echo "usage: bash scripts/regression/language-support-compare.sh <baseline-dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[compare] using baseline $BASE_DIR"
|
||||
|
||||
CURRENT_BUILD="$BASE_DIR/build-compare-current.txt"
|
||||
CURRENT_TEST="$BASE_DIR/targeted-tests-compare-current.txt"
|
||||
|
||||
set +e
|
||||
npm run build > "$CURRENT_BUILD" 2>&1
|
||||
CURRENT_BUILD_EXIT=$?
|
||||
set -e
|
||||
|
||||
echo "$CURRENT_BUILD_EXIT" > "$BASE_DIR/build-compare-current.exitcode"
|
||||
|
||||
npm test -- --runInBand \
|
||||
src/tests/workflowButtons.test.ts \
|
||||
src/tests/sidebarDomButtonClicks.test.ts \
|
||||
src/tests/llmUtilsProviderSupport.test.ts \
|
||||
src/tests/providerDiagnostics.test.ts \
|
||||
> "$CURRENT_TEST" 2>&1
|
||||
|
||||
if grep -q "error TS6059" "$CURRENT_BUILD"; then
|
||||
echo "[compare] FAIL: TS6059 still present in current build output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -E "Test Suites:\s*[1-9][0-9]* failed|\bFAIL\b" "$CURRENT_TEST" >/dev/null; then
|
||||
echo "[compare] FAIL: targeted regression tests reported failures"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -E "ERR_CONNECTION_CLOSED|socket hang up" "$CURRENT_TEST" >/dev/null; then
|
||||
if ! grep -q "llmUtilsProviderSupport.test.ts" "$CURRENT_TEST"; then
|
||||
echo "[compare] FAIL: network-close signatures found outside expected mock test context"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[compare] PASS: build + targeted regressions are stable"
|
||||
|
|
@ -6,6 +6,7 @@ export const Notice = jest.fn();
|
|||
export const Plugin = jest.fn();
|
||||
export const PluginSettingTab = jest.fn();
|
||||
export const requestUrl = jest.fn();
|
||||
export const getLanguage = jest.fn(() => 'en');
|
||||
export const Setting = jest.fn().mockImplementation(() => ({
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDesc: jest.fn().mockReturnThis(),
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ export const DEFAULT_SETTINGS: NotemdSettings = {
|
|||
// Add Links Post-Processing Defaults
|
||||
removeCodeFencesOnAddLinks: false, // Default to NOT removing code fences
|
||||
// Language Settings Defaults
|
||||
uiLocale: 'auto',
|
||||
language: 'en', // Default to English
|
||||
availableLanguages: [ // Default list of available languages
|
||||
{ code: 'en', name: 'English' },
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { refineMermaidBlocks, cleanupLatexDelimiters, deepDebugMermaid, applyDee
|
|||
import { _performResearch } from './searchUtils'; // Assuming this will be moved or imported correctly later
|
||||
import { showDeletionConfirmationModal } from './ui/modals'; // Assuming this will be moved or imported correctly later
|
||||
import mermaid from 'mermaid';
|
||||
import { resolveTaskLanguageName, shouldApplyAutoTranslation } from './i18n/taskLanguagePolicy';
|
||||
|
||||
// --- Backlink and Note Management ---
|
||||
|
||||
|
|
@ -639,8 +640,8 @@ export async function generateContentForTitle(app: App, settings: NotemdSettings
|
|||
);
|
||||
|
||||
// Language-specific instruction
|
||||
const targetLanguageName = settings.availableLanguages.find(lang => lang.code === settings.language)?.name || settings.language;
|
||||
if (settings.language && settings.language !== 'en') {
|
||||
const targetLanguageName = resolveTaskLanguageName(settings, 'generateTitle');
|
||||
if (shouldApplyAutoTranslation(settings, 'generateTitle') && targetLanguageName && targetLanguageName !== 'English') {
|
||||
generationPrompt += `\n\nIMPORTANT: Process the request and perform all reasoning in English. However, the final output MUST be written in ${targetLanguageName}.In mermaid diagrams, it is necessary to translate into ${targetLanguageName} while retaining the English.`;
|
||||
}
|
||||
|
||||
|
|
|
|||
86
src/i18n/index.ts
Normal file
86
src/i18n/index.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { getLanguage } from 'obsidian';
|
||||
import { normalizeLocaleCode, resolveUiLocale } from './languageContext';
|
||||
import { STRINGS_EN, NotemdEnglishStrings } from './locales/en';
|
||||
import { STRINGS_ZH_CN } from './locales/zh_cn';
|
||||
import { STRINGS_ZH_TW } from './locales/zh_tw';
|
||||
|
||||
type TranslationStrings = NotemdEnglishStrings;
|
||||
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
|
||||
|
||||
const LANGUAGE_MAP: Record<string, DeepPartial<TranslationStrings>> = {
|
||||
en: STRINGS_EN,
|
||||
zh: STRINGS_ZH_CN,
|
||||
'zh-CN': STRINGS_ZH_CN,
|
||||
'zh-TW': STRINGS_ZH_TW
|
||||
};
|
||||
|
||||
const resolvedLanguageCache = new Map<string, TranslationStrings>();
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function mergeTranslationValues(base: unknown, override: unknown): unknown {
|
||||
if (override === undefined) {
|
||||
return base;
|
||||
}
|
||||
|
||||
if (Array.isArray(base)) {
|
||||
return Array.isArray(override) ? override : base;
|
||||
}
|
||||
|
||||
if (isPlainObject(base)) {
|
||||
if (!isPlainObject(override)) {
|
||||
return base;
|
||||
}
|
||||
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const key of Object.keys(base)) {
|
||||
result[key] = mergeTranslationValues(base[key], override[key]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return typeof override === typeof base ? override : base;
|
||||
}
|
||||
|
||||
export function getResolvedStrings(locale: string): TranslationStrings {
|
||||
const normalizedLocale = normalizeLocaleCode(locale);
|
||||
if (normalizedLocale === 'en') {
|
||||
return STRINGS_EN;
|
||||
}
|
||||
|
||||
const cached = resolvedLanguageCache.get(normalizedLocale);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const merged = mergeTranslationValues(STRINGS_EN, LANGUAGE_MAP[normalizedLocale] || LANGUAGE_MAP.en) as TranslationStrings;
|
||||
resolvedLanguageCache.set(normalizedLocale, merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
export function clearI18nCache(): void {
|
||||
resolvedLanguageCache.clear();
|
||||
}
|
||||
|
||||
export function getCurrentObsidianLocale(): string {
|
||||
return normalizeLocaleCode(getLanguage());
|
||||
}
|
||||
|
||||
export function getI18nStrings(settings?: { uiLocale: string }): TranslationStrings {
|
||||
if (settings) {
|
||||
const locale = resolveUiLocale(settings, getCurrentObsidianLocale(), Object.keys(LANGUAGE_MAP));
|
||||
return getResolvedStrings(locale);
|
||||
}
|
||||
return getResolvedStrings(getCurrentObsidianLocale());
|
||||
}
|
||||
|
||||
export function formatI18n(template: string, variables: Record<string, string | number>): string {
|
||||
return template.replace(/{(\w+)}/g, (_, key: string) => {
|
||||
const value = variables[key];
|
||||
return value === undefined ? `{${key}}` : String(value);
|
||||
});
|
||||
}
|
||||
|
||||
export const strings: TranslationStrings = getI18nStrings();
|
||||
63
src/i18n/languageContext.ts
Normal file
63
src/i18n/languageContext.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { NotemdSettings } from '../types';
|
||||
|
||||
export const UI_LOCALE_AUTO = 'auto';
|
||||
|
||||
function normalizeChineseLocale(locale: string): string {
|
||||
const lower = locale.toLowerCase();
|
||||
if (lower === 'zh' || lower === 'zh-cn' || lower === 'zh_cn') {
|
||||
return 'zh-CN';
|
||||
}
|
||||
if (lower === 'zh-tw' || lower === 'zh_tw' || lower === 'zh-hant') {
|
||||
return 'zh-TW';
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
export function normalizeLocaleCode(locale: string | undefined | null): string {
|
||||
if (!locale) {
|
||||
return 'en';
|
||||
}
|
||||
|
||||
const trimmed = locale.trim();
|
||||
if (!trimmed) {
|
||||
return 'en';
|
||||
}
|
||||
|
||||
const unified = normalizeChineseLocale(trimmed.replace(/_/g, '-'));
|
||||
const parts = unified.split('-');
|
||||
if (parts.length === 1) {
|
||||
return parts[0].toLowerCase();
|
||||
}
|
||||
|
||||
return `${parts[0].toLowerCase()}-${parts[1].toUpperCase()}`;
|
||||
}
|
||||
|
||||
export function languageCodesEqual(left: string | undefined | null, right: string | undefined | null): boolean {
|
||||
return normalizeLocaleCode(left) === normalizeLocaleCode(right);
|
||||
}
|
||||
|
||||
export function resolveLanguageDisplayName(settings: NotemdSettings, languageCode: string): string {
|
||||
const normalizedTarget = normalizeLocaleCode(languageCode);
|
||||
const language = settings.availableLanguages.find(lang => languageCodesEqual(lang.code, normalizedTarget));
|
||||
return language?.name || normalizedTarget;
|
||||
}
|
||||
|
||||
export function resolveUiLocale(
|
||||
settings: { uiLocale: string },
|
||||
obsidianLocale: string | undefined | null,
|
||||
supportedLocales: readonly string[] = ['en', 'zh-CN', 'zh-TW']
|
||||
): string {
|
||||
const normalizedSupported = supportedLocales.map(normalizeLocaleCode);
|
||||
const configured = normalizeLocaleCode(settings.uiLocale);
|
||||
|
||||
if (configured !== UI_LOCALE_AUTO && normalizedSupported.includes(configured)) {
|
||||
return configured;
|
||||
}
|
||||
|
||||
const detected = normalizeLocaleCode(obsidianLocale);
|
||||
if (normalizedSupported.includes(detected)) {
|
||||
return detected;
|
||||
}
|
||||
|
||||
return 'en';
|
||||
}
|
||||
27
src/i18n/localeFormat.ts
Normal file
27
src/i18n/localeFormat.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { normalizeLocaleCode } from './languageContext';
|
||||
|
||||
const RTL_LANGUAGE_PREFIXES = ['ar', 'he', 'fa', 'ur'];
|
||||
|
||||
export function isRtlLocale(locale: string | undefined | null): boolean {
|
||||
const normalized = normalizeLocaleCode(locale);
|
||||
return RTL_LANGUAGE_PREFIXES.some(prefix => normalized.startsWith(prefix));
|
||||
}
|
||||
|
||||
export function formatTimeForLocale(date: Date, locale: string | undefined | null): string {
|
||||
const normalized = normalizeLocaleCode(locale);
|
||||
try {
|
||||
return date.toLocaleTimeString(normalized);
|
||||
} catch (_error) {
|
||||
return date.toLocaleTimeString();
|
||||
}
|
||||
}
|
||||
|
||||
export function formatDateForLocale(date: Date, locale: string | undefined | null): string {
|
||||
const normalized = normalizeLocaleCode(locale);
|
||||
try {
|
||||
return date.toLocaleDateString(normalized);
|
||||
} catch (_error) {
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
}
|
||||
|
||||
60
src/i18n/locales/en.ts
Normal file
60
src/i18n/locales/en.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
export const STRINGS_EN = {
|
||||
common: {
|
||||
language: 'Language',
|
||||
cancel: 'Cancel',
|
||||
close: 'Close',
|
||||
copy: 'Copy',
|
||||
ready: 'Ready',
|
||||
standby: 'Standby',
|
||||
unknownError: 'Unknown error'
|
||||
},
|
||||
settings: {
|
||||
language: {
|
||||
heading: 'Language settings',
|
||||
outputName: 'Output language',
|
||||
outputDesc: 'Select the desired output language for LLM responses.',
|
||||
perTaskName: 'Select different languages for different tasks.',
|
||||
perTaskDesc: 'On: Select a specific language for each task below. Off: Use the single "Output language".',
|
||||
disableAutoTranslationName: 'Disable auto translation (except for "Translate" task)',
|
||||
disableAutoTranslationDesc:
|
||||
'On: Non-Translate tasks do not force a target language or auto-translate outputs. The explicit "Translate" task still performs translation as configured.',
|
||||
taskLanguageLabel: '{task} language',
|
||||
taskLanguageDesc: 'Select the output language for "{task}".'
|
||||
},
|
||||
developer: {
|
||||
modeName: 'Developer mode',
|
||||
modeDesc: 'On: Show dedicated developer diagnostic tools in settings. Off: Hide developer-only controls.',
|
||||
heading: 'Developer diagnostics',
|
||||
runDiagnostic: 'Run diagnostic',
|
||||
runStability: 'Run stability test'
|
||||
}
|
||||
},
|
||||
sidebar: {
|
||||
heroTitle: 'Notemd Workbench',
|
||||
heroDesc: 'Run single actions or custom one-click workflows with live progress and logs.',
|
||||
quickWorkflowTitle: 'Quick Workflows',
|
||||
quickWorkflowDesc: 'Custom buttons assembled from built-in actions.',
|
||||
builtInActionsPrefix: 'Built-in {category} actions.',
|
||||
logOutputTitle: 'Log output',
|
||||
copyLog: 'Copy log',
|
||||
copyLogSuccess: 'Log copied!',
|
||||
copyLogFailed: 'Failed to copy log.',
|
||||
logEmpty: 'Log is empty.',
|
||||
cancelProcessing: 'Cancel processing',
|
||||
workflowFallbackWarning: 'Workflow DSL has {count} issue(s). Sidebar is using default fallback.',
|
||||
languageChangedNotice: 'Language changed to {language}'
|
||||
},
|
||||
notices: {
|
||||
processingAlreadyRunning: 'Processing already in progress.',
|
||||
anotherProcessRunning: 'Another process is running. Please wait.',
|
||||
notemdBusy: 'Notemd is busy.'
|
||||
},
|
||||
errorModal: {
|
||||
copyDetails: 'Copy Error Details',
|
||||
copied: 'Copied!',
|
||||
copySuccessNotice: 'Error details copied to clipboard!',
|
||||
copyFailedNotice: 'Failed to copy error details. See console.'
|
||||
}
|
||||
};
|
||||
|
||||
export type NotemdEnglishStrings = typeof STRINGS_EN;
|
||||
59
src/i18n/locales/zh_cn.ts
Normal file
59
src/i18n/locales/zh_cn.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import type { NotemdEnglishStrings } from './en';
|
||||
|
||||
export const STRINGS_ZH_CN: Partial<NotemdEnglishStrings> = {
|
||||
common: {
|
||||
language: '语言',
|
||||
cancel: '取消',
|
||||
close: '关闭',
|
||||
copy: '复制',
|
||||
ready: '就绪',
|
||||
standby: '待命',
|
||||
unknownError: '未知错误'
|
||||
},
|
||||
settings: {
|
||||
language: {
|
||||
heading: '语言设置',
|
||||
outputName: '输出语言',
|
||||
outputDesc: '选择 LLM 输出内容使用的语言。',
|
||||
perTaskName: '为不同任务选择不同语言',
|
||||
perTaskDesc: '开:为每个任务分别设置语言。关:统一使用“输出语言”。',
|
||||
disableAutoTranslationName: '关闭自动翻译(“Translate”任务除外)',
|
||||
disableAutoTranslationDesc: '开:非 Translate 任务不强制翻译到目标语言。Translate 任务仍按配置执行翻译。',
|
||||
taskLanguageLabel: '{task} 语言',
|
||||
taskLanguageDesc: '为“{task}”选择输出语言。'
|
||||
},
|
||||
developer: {
|
||||
modeName: '开发者模式',
|
||||
modeDesc: '开:显示开发者诊断工具。关:隐藏开发者专用控件。',
|
||||
heading: '开发者诊断',
|
||||
runDiagnostic: '运行诊断',
|
||||
runStability: '运行稳定性测试'
|
||||
}
|
||||
},
|
||||
sidebar: {
|
||||
heroTitle: 'Notemd 工作台',
|
||||
heroDesc: '执行单项任务或自定义一键工作流,并查看实时进度与日志。',
|
||||
quickWorkflowTitle: '快速工作流',
|
||||
quickWorkflowDesc: '由内置动作组装的自定义按钮。',
|
||||
builtInActionsPrefix: '内置{category}动作。',
|
||||
logOutputTitle: '日志输出',
|
||||
copyLog: '复制日志',
|
||||
copyLogSuccess: '日志已复制!',
|
||||
copyLogFailed: '复制日志失败。',
|
||||
logEmpty: '日志为空。',
|
||||
cancelProcessing: '取消处理',
|
||||
workflowFallbackWarning: '工作流 DSL 有 {count} 个问题,侧边栏已使用默认回退配置。',
|
||||
languageChangedNotice: '语言已切换为 {language}'
|
||||
},
|
||||
notices: {
|
||||
processingAlreadyRunning: '已有任务正在处理中。',
|
||||
anotherProcessRunning: '另一个任务正在运行,请稍候。',
|
||||
notemdBusy: 'Notemd 正在忙碌中。'
|
||||
},
|
||||
errorModal: {
|
||||
copyDetails: '复制错误详情',
|
||||
copied: '已复制!',
|
||||
copySuccessNotice: '错误详情已复制到剪贴板!',
|
||||
copyFailedNotice: '复制错误详情失败,请查看控制台。'
|
||||
}
|
||||
};
|
||||
59
src/i18n/locales/zh_tw.ts
Normal file
59
src/i18n/locales/zh_tw.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import type { NotemdEnglishStrings } from './en';
|
||||
|
||||
export const STRINGS_ZH_TW: Partial<NotemdEnglishStrings> = {
|
||||
common: {
|
||||
language: '語言',
|
||||
cancel: '取消',
|
||||
close: '關閉',
|
||||
copy: '複製',
|
||||
ready: '就緒',
|
||||
standby: '待命',
|
||||
unknownError: '未知錯誤'
|
||||
},
|
||||
settings: {
|
||||
language: {
|
||||
heading: '語言設定',
|
||||
outputName: '輸出語言',
|
||||
outputDesc: '選擇 LLM 輸出內容使用的語言。',
|
||||
perTaskName: '為不同任務選擇不同語言',
|
||||
perTaskDesc: '開:可為每個任務設定語言。關:統一使用「輸出語言」。',
|
||||
disableAutoTranslationName: '停用自動翻譯(「Translate」任務除外)',
|
||||
disableAutoTranslationDesc: '開:非 Translate 任務不強制翻譯到目標語言。Translate 任務仍依設定執行。',
|
||||
taskLanguageLabel: '{task} 語言',
|
||||
taskLanguageDesc: '為「{task}」選擇輸出語言。'
|
||||
},
|
||||
developer: {
|
||||
modeName: '開發者模式',
|
||||
modeDesc: '開:顯示開發者診斷工具。關:隱藏開發者專用控制項。',
|
||||
heading: '開發者診斷',
|
||||
runDiagnostic: '執行診斷',
|
||||
runStability: '執行穩定性測試'
|
||||
}
|
||||
},
|
||||
sidebar: {
|
||||
heroTitle: 'Notemd 工作台',
|
||||
heroDesc: '執行單一動作或自訂一鍵工作流程,並查看即時進度與日誌。',
|
||||
quickWorkflowTitle: '快速工作流程',
|
||||
quickWorkflowDesc: '由內建動作組裝的自訂按鈕。',
|
||||
builtInActionsPrefix: '內建 {category} 動作。',
|
||||
logOutputTitle: '日誌輸出',
|
||||
copyLog: '複製日誌',
|
||||
copyLogSuccess: '日誌已複製!',
|
||||
copyLogFailed: '複製日誌失敗。',
|
||||
logEmpty: '日誌為空。',
|
||||
cancelProcessing: '取消處理',
|
||||
workflowFallbackWarning: '工作流程 DSL 有 {count} 個問題,側欄已使用預設回退設定。',
|
||||
languageChangedNotice: '語言已切換為 {language}'
|
||||
},
|
||||
notices: {
|
||||
processingAlreadyRunning: '已有任務正在處理中。',
|
||||
anotherProcessRunning: '另一個任務正在執行,請稍候。',
|
||||
notemdBusy: 'Notemd 忙碌中。'
|
||||
},
|
||||
errorModal: {
|
||||
copyDetails: '複製錯誤詳情',
|
||||
copied: '已複製!',
|
||||
copySuccessNotice: '錯誤詳情已複製到剪貼簿!',
|
||||
copyFailedNotice: '複製錯誤詳情失敗,請查看主控台。'
|
||||
}
|
||||
};
|
||||
50
src/i18n/taskLanguagePolicy.ts
Normal file
50
src/i18n/taskLanguagePolicy.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { NotemdSettings, TaskKey } from '../types';
|
||||
import { resolveLanguageDisplayName } from './languageContext';
|
||||
|
||||
type LanguageSettingKey =
|
||||
| 'generateTitleLanguage'
|
||||
| 'researchSummarizeLanguage'
|
||||
| 'addLinksLanguage'
|
||||
| 'summarizeToMermaidLanguage'
|
||||
| 'extractConceptsLanguage'
|
||||
| 'extractOriginalTextLanguage';
|
||||
|
||||
const TASK_LANGUAGE_KEY_MAP: Partial<Record<TaskKey, LanguageSettingKey>> = {
|
||||
addLinks: 'addLinksLanguage',
|
||||
generateTitle: 'generateTitleLanguage',
|
||||
researchSummarize: 'researchSummarizeLanguage',
|
||||
summarizeToMermaid: 'summarizeToMermaidLanguage',
|
||||
extractConcepts: 'extractConceptsLanguage',
|
||||
extractOriginalText: 'extractOriginalTextLanguage',
|
||||
extractOriginalTextMerged: 'extractOriginalTextLanguage'
|
||||
};
|
||||
|
||||
export function shouldApplyAutoTranslation(settings: NotemdSettings, taskKey: TaskKey): boolean {
|
||||
if (taskKey === 'translate') {
|
||||
return true;
|
||||
}
|
||||
return !settings.disableAutoTranslation;
|
||||
}
|
||||
|
||||
export function resolveTaskLanguageCode(settings: NotemdSettings, taskKey: TaskKey): string {
|
||||
if (taskKey === 'translate') {
|
||||
return settings.language || 'en';
|
||||
}
|
||||
|
||||
if (settings.useDifferentLanguagesForTasks) {
|
||||
const key = TASK_LANGUAGE_KEY_MAP[taskKey];
|
||||
if (key) {
|
||||
const taskLanguage = settings[key];
|
||||
if (typeof taskLanguage === 'string' && taskLanguage.trim().length > 0) {
|
||||
return taskLanguage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return settings.language || 'en';
|
||||
}
|
||||
|
||||
export function resolveTaskLanguageName(settings: NotemdSettings, taskKey: TaskKey): string {
|
||||
return resolveLanguageDisplayName(settings, resolveTaskLanguageCode(settings, taskKey));
|
||||
}
|
||||
|
||||
44
src/main.ts
44
src/main.ts
|
|
@ -28,6 +28,8 @@ import { NotemdSidebarView } from './ui/NotemdSidebarView';
|
|||
import { translateFile, batchTranslateFolder } from './translate';
|
||||
import { getSystemPrompt } from './promptUtils';
|
||||
import { extractOriginalText } from './extractOriginalText';
|
||||
import { getI18nStrings } from './i18n';
|
||||
import { resolveTaskLanguageCode } from './i18n/taskLanguagePolicy';
|
||||
|
||||
export default class NotemdPlugin extends Plugin {
|
||||
settings: NotemdSettings;
|
||||
|
|
@ -47,6 +49,10 @@ export default class NotemdPlugin extends Plugin {
|
|||
// this.updateStatusBar(busy ? 'Busy...' : 'Ready');
|
||||
}
|
||||
|
||||
private getUiStrings() {
|
||||
return getI18nStrings({ uiLocale: this.settings?.uiLocale || 'auto' });
|
||||
}
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
|
|
@ -652,7 +658,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
/** Command: Process Current File (Add Links) */
|
||||
async processWithNotemdCommand(reporter?: ProgressReporter) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -719,7 +725,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
/** Command: Process Folder (Add Links) */
|
||||
async processFolderWithNotemdCommand(reporter?: ProgressReporter, folderPathOverride?: string) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -944,7 +950,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
/** Command: Generate Content from Title */
|
||||
async generateContentForTitleCommand(file: TFile, reporter?: ProgressReporter) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -996,7 +1002,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
/** Command: Research and Summarize Topic */
|
||||
async researchAndSummarizeCommand(editor: Editor, view: MarkdownView, reporter?: ProgressReporter) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -1065,7 +1071,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
reporter?: ProgressReporter,
|
||||
folderPathOverride?: string
|
||||
): Promise<{ sourceFolderPath: string; completeFolderPath: string } | null> {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return null; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return null; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -1145,7 +1151,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
/** Command: Check and Remove Duplicate Concept Notes */
|
||||
async checkAndRemoveDuplicateConceptNotesCommand(reporter?: ProgressReporter) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -1189,7 +1195,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
reporter?: ProgressReporter,
|
||||
folderPathOverride?: string
|
||||
): Promise<{ folderPath: string; modifiedCount: number } | null> {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return null; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return null; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -1253,7 +1259,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async fixFormulaFormatsCommand(file: TFile, reporter?: ProgressReporter) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -1297,7 +1303,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async batchFixFormulaFormatsCommand(reporter?: ProgressReporter) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -1349,7 +1355,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
async batchTranslateFolderCommand(folder?: TFolder, reporter?: ProgressReporter) {
|
||||
if (this.isBusy) {
|
||||
new Notice("Notemd is busy.");
|
||||
new Notice(this.getUiStrings().notices.notemdBusy);
|
||||
return;
|
||||
}
|
||||
this.isBusy = true;
|
||||
|
|
@ -1381,7 +1387,8 @@ export default class NotemdPlugin extends Plugin {
|
|||
}
|
||||
const resolvedTargetFolder = targetFolder as TFolder;
|
||||
|
||||
await batchTranslateFolder(this.app, this.settings, resolvedTargetFolder, this.settings.language);
|
||||
const translateLanguage = resolveTaskLanguageCode(this.settings, 'translate');
|
||||
await batchTranslateFolder(this.app, this.settings, resolvedTargetFolder, translateLanguage);
|
||||
if (!useReporter.cancelled) {
|
||||
const mermaidFixTarget = (this.settings.useCustomTranslationSavePath && this.settings.translationSavePath)
|
||||
? this.settings.translationSavePath
|
||||
|
|
@ -1414,7 +1421,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
async translateFileCommand(file: TFile, signal?: AbortSignal, reporter?: ProgressReporter) {
|
||||
if (this.isBusy) {
|
||||
new Notice("Notemd is busy.");
|
||||
new Notice(this.getUiStrings().notices.notemdBusy);
|
||||
return;
|
||||
}
|
||||
this.isBusy = true;
|
||||
|
|
@ -1432,7 +1439,8 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
try {
|
||||
await this.loadSettings();
|
||||
const outputPath = await translateFile(this.app, this.settings, file, this.settings.language, useReporter, true, signal);
|
||||
const translateLanguage = resolveTaskLanguageCode(this.settings, 'translate');
|
||||
const outputPath = await translateFile(this.app, this.settings, file, translateLanguage, useReporter, true, signal);
|
||||
if (outputPath && this.settings.autoMermaidFixAfterGenerate) {
|
||||
const outputFile = this.app.vault.getAbstractFileByPath(outputPath);
|
||||
if (outputFile instanceof TFile) {
|
||||
|
|
@ -1473,7 +1481,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
async summarizeToMermaidCommand(file: TFile, reporter: ProgressReporter) {
|
||||
if (this.isBusy) {
|
||||
new Notice('Another process is running. Please wait.');
|
||||
new Notice(this.getUiStrings().notices.anotherProcessRunning);
|
||||
return;
|
||||
}
|
||||
this.isBusy = true;
|
||||
|
|
@ -1542,7 +1550,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async extractConceptsCommand(reporter?: ProgressReporter) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -1610,7 +1618,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async batchExtractConceptsForFolderCommand(reporter?: ProgressReporter) {
|
||||
if (this.isBusy) { new Notice("Notemd is busy."); return; }
|
||||
if (this.isBusy) { new Notice(this.getUiStrings().notices.notemdBusy); return; }
|
||||
this.isBusy = true;
|
||||
const useReporter = reporter || this.getReporter();
|
||||
|
||||
|
|
@ -1791,7 +1799,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
async extractConceptsAndGenerateTitlesCommand(reporter?: ProgressReporter) {
|
||||
if (this.isBusy) {
|
||||
new Notice("Notemd is busy.");
|
||||
new Notice(this.getUiStrings().notices.notemdBusy);
|
||||
return;
|
||||
}
|
||||
this.isBusy = true;
|
||||
|
|
@ -1844,7 +1852,7 @@ export default class NotemdPlugin extends Plugin {
|
|||
|
||||
async extractOriginalTextCommand(reporter?: ProgressReporter) {
|
||||
if (this.isBusy) {
|
||||
new Notice("Notemd is busy.");
|
||||
new Notice(this.getUiStrings().notices.notemdBusy);
|
||||
return;
|
||||
}
|
||||
this.isBusy = true;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { NotemdSettings, TaskKey } from './types';
|
||||
import {
|
||||
resolveTaskLanguageName,
|
||||
shouldApplyAutoTranslation
|
||||
} from './i18n/taskLanguagePolicy';
|
||||
import { resolveLanguageDisplayName } from './i18n/languageContext';
|
||||
|
||||
export const DEFAULT_PROMPTS: Record<TaskKey, string> = {
|
||||
extractConcepts: `You are an AI assistant specializing in knowledge extraction. Your task is to Completely decompose and structure the knowledge points in this markdown document, analyze the markdown document and identify all core concepts and keywords.
|
||||
|
|
@ -416,54 +421,42 @@ export function getSystemPrompt(settings: NotemdSettings, taskKey: TaskKey, repl
|
|||
prompt = `${domainText}\n\n${prompt}`;
|
||||
}
|
||||
|
||||
const autoTranslationEnabled = shouldApplyAutoTranslation(settings, taskKey);
|
||||
const resolvedTaskLanguageName = resolveTaskLanguageName(settings, taskKey);
|
||||
|
||||
const finalReplacements: Record<string, string> = { ...replacements };
|
||||
if (finalReplacements.LANGUAGE) {
|
||||
finalReplacements.LANGUAGE = resolveLanguageDisplayName(settings, finalReplacements.LANGUAGE);
|
||||
} else if (autoTranslationEnabled || taskKey === 'translate') {
|
||||
finalReplacements.LANGUAGE = resolvedTaskLanguageName;
|
||||
} else {
|
||||
finalReplacements.LANGUAGE = 'the same language as the source content';
|
||||
}
|
||||
|
||||
// Perform replacements
|
||||
for (const key in replacements) {
|
||||
prompt = prompt.replace(new RegExp(`{${key}}`, 'g'), replacements[key]);
|
||||
for (const key in finalReplacements) {
|
||||
prompt = prompt.replace(new RegExp(`{${key}}`, 'g'), finalReplacements[key]);
|
||||
}
|
||||
|
||||
// Add translation instruction for Mermaid summarization if enabled
|
||||
if (taskKey === 'summarizeToMermaid' && settings.translateSummarizeToMermaidOutput && !settings.disableAutoTranslation) {
|
||||
const targetLanguageName = settings.availableLanguages.find(lang => lang.code === settings.language)?.name || settings.language;
|
||||
if (taskKey === 'summarizeToMermaid' && settings.translateSummarizeToMermaidOutput && autoTranslationEnabled) {
|
||||
prompt += `
|
||||
|
||||
IMPORTANT: The entire Mermaid diagram, including all node text, MUST be translated into ${targetLanguageName}.`;
|
||||
IMPORTANT: The entire Mermaid diagram, including all node text, MUST be translated into ${resolvedTaskLanguageName}.`;
|
||||
}
|
||||
|
||||
// Add translation instruction for extractOriginalText (and Merged variant) if enabled
|
||||
if ((taskKey === 'extractOriginalText' || taskKey === 'extractOriginalTextMerged') && settings.translateExtractOriginalTextOutput && !settings.disableAutoTranslation) {
|
||||
// Modify the output format instruction for the multi-line format
|
||||
// We look for the generic bullet structure pattern
|
||||
|
||||
const translationInstruction = `\n\nIMPORTANT: For each matching excerpt, you MUST append the translation in {LANGUAGE}.
|
||||
if ((taskKey === 'extractOriginalText' || taskKey === 'extractOriginalTextMerged') && settings.translateExtractOriginalTextOutput && autoTranslationEnabled) {
|
||||
const translationInstruction = `\n\nIMPORTANT: For each matching excerpt, you MUST append the translation in ${resolvedTaskLanguageName}.
|
||||
The format for each bullet point must be strictly:
|
||||
- [Exact Excerpt] - [Excerpt translated into {LANGUAGE}]`;
|
||||
- [Exact Excerpt] - [Excerpt translated into ${resolvedTaskLanguageName}]`;
|
||||
|
||||
prompt += translationInstruction;
|
||||
prompt += translationInstruction;
|
||||
}
|
||||
|
||||
// Add language instruction for extractConcepts if a specific language is set
|
||||
if (taskKey === 'extractConcepts' && !settings.disableAutoTranslation) {
|
||||
const languageCode = settings.useDifferentLanguagesForTasks ? settings.extractConceptsLanguage : settings.language;
|
||||
const targetLanguageName = settings.availableLanguages.find(lang => lang.code === languageCode)?.name || languageCode;
|
||||
if (targetLanguageName) {
|
||||
prompt += `\n\nThe output concepts MUST be in ${targetLanguageName}.`;
|
||||
}
|
||||
}
|
||||
|
||||
// For other tasks that might have language settings, ensure they are also skipped if disableAutoTranslation is true
|
||||
if (taskKey !== 'translate' && taskKey !== 'summarizeToMermaid' && taskKey !== 'extractConcepts' && !settings.disableAutoTranslation) {
|
||||
const languageCode = settings.useDifferentLanguagesForTasks
|
||||
? (settings as any)[`${taskKey}Language`] // e.g., settings.addLinksLanguage
|
||||
: settings.language;
|
||||
|
||||
const targetLanguageName = settings.availableLanguages.find(lang => lang.code === languageCode)?.name || languageCode;
|
||||
|
||||
if (targetLanguageName && prompt.includes('{LANGUAGE}')) {
|
||||
prompt = prompt.replace(/{LANGUAGE}/g, targetLanguageName);
|
||||
} else if (targetLanguageName) {
|
||||
// A more generic way to add language instruction if not already in prompt
|
||||
// This part might need to be adjusted based on how other prompts are structured
|
||||
}
|
||||
if (taskKey === 'extractConcepts' && autoTranslationEnabled) {
|
||||
prompt += `\n\nThe output concepts MUST be in ${resolvedTaskLanguageName}.`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { cleanupLatexDelimiters, refineMermaidBlocks } from './mermaidProcessor'
|
|||
import { ErrorModal } from './ui/ErrorModal';
|
||||
import { getSystemPrompt } from './promptUtils';
|
||||
import { SearchManager } from './search/SearchManager';
|
||||
import { resolveTaskLanguageCode } from './i18n/taskLanguagePolicy';
|
||||
|
||||
/**
|
||||
* Fetches content from a URL and extracts basic text.
|
||||
|
|
@ -244,7 +245,7 @@ export async function researchAndSummarize(app: App, settings: NotemdSettings, e
|
|||
|
||||
progressReporter.log(`Calling ${provider.name} (Model: ${modelName}) for summarization...`);
|
||||
|
||||
const language = settings.useDifferentLanguagesForTasks ? settings.researchSummarizeLanguage : settings.language;
|
||||
const language = resolveTaskLanguageCode(settings, 'researchSummarize');
|
||||
|
||||
const finalPrompt = getSystemPrompt(settings, 'researchSummarize', {
|
||||
TOPIC: topic,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export const mockSettings: NotemdSettings = {
|
|||
duplicateCheckScopeMode: 'vault',
|
||||
duplicateCheckScopePaths: '',
|
||||
removeCodeFencesOnAddLinks: false,
|
||||
uiLocale: 'auto',
|
||||
language: 'en',
|
||||
availableLanguages: [{ code: 'en', name: 'English' }],
|
||||
useDifferentLanguagesForTasks: false,
|
||||
|
|
|
|||
52
src/tests/i18nFallback.test.ts
Normal file
52
src/tests/i18nFallback.test.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
jest.mock('obsidian');
|
||||
|
||||
import { getLanguage } from 'obsidian';
|
||||
import { clearI18nCache, formatI18n, getI18nStrings, getResolvedStrings } from '../i18n';
|
||||
|
||||
describe('i18n fallback and cache', () => {
|
||||
beforeEach(() => {
|
||||
clearI18nCache();
|
||||
(getLanguage as jest.Mock).mockReset();
|
||||
});
|
||||
|
||||
test('falls back to English when locale is unsupported', () => {
|
||||
const locale = getResolvedStrings('fr');
|
||||
expect(locale.common.cancel).toBe('Cancel');
|
||||
expect(locale.sidebar.logOutputTitle).toBe('Log output');
|
||||
});
|
||||
|
||||
test('supports zh locale alias resolution', () => {
|
||||
const zh = getResolvedStrings('zh');
|
||||
expect(zh.common.cancel).toBe('取消');
|
||||
});
|
||||
|
||||
test('fills missing localized keys from English base catalog', () => {
|
||||
const zhTw = getResolvedStrings('zh-TW');
|
||||
expect(zhTw.notices.notemdBusy).toBe('Notemd 忙碌中。');
|
||||
// This key exists in English and should always be available even if future locales omit it.
|
||||
expect(zhTw.common.unknownError).toBeDefined();
|
||||
});
|
||||
|
||||
test('returns cached object for identical locale', () => {
|
||||
const first = getResolvedStrings('zh-CN');
|
||||
const second = getResolvedStrings('zh_cn');
|
||||
expect(first).toBe(second);
|
||||
});
|
||||
|
||||
test('resolves strings using Obsidian language when uiLocale is auto', () => {
|
||||
(getLanguage as jest.Mock).mockReturnValue('zh-cn');
|
||||
const resolved = getI18nStrings({ uiLocale: 'auto' });
|
||||
expect(resolved.common.language).toBe('语言');
|
||||
});
|
||||
|
||||
test('manual ui locale override has priority over Obsidian language', () => {
|
||||
(getLanguage as jest.Mock).mockReturnValue('en');
|
||||
const resolved = getI18nStrings({ uiLocale: 'zh-TW' });
|
||||
expect(resolved.common.language).toBe('語言');
|
||||
});
|
||||
|
||||
test('formats placeholders', () => {
|
||||
expect(formatI18n('Language changed to {language}', { language: 'zh-CN' })).toBe('Language changed to zh-CN');
|
||||
});
|
||||
});
|
||||
|
||||
97
src/tests/languagePolicy.test.ts
Normal file
97
src/tests/languagePolicy.test.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import { DEFAULT_SETTINGS } from '../constants';
|
||||
import { resolveUiLocale } from '../i18n/languageContext';
|
||||
import {
|
||||
resolveTaskLanguageCode,
|
||||
resolveTaskLanguageName,
|
||||
shouldApplyAutoTranslation
|
||||
} from '../i18n/taskLanguagePolicy';
|
||||
import { NotemdSettings, TaskKey } from '../types';
|
||||
|
||||
function createSettings(overrides: Partial<NotemdSettings> = {}): NotemdSettings {
|
||||
return {
|
||||
...DEFAULT_SETTINGS,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
describe('task language policy', () => {
|
||||
test('uses global language when per-task language is disabled', () => {
|
||||
const settings = createSettings({
|
||||
language: 'zh-CN',
|
||||
useDifferentLanguagesForTasks: false,
|
||||
generateTitleLanguage: 'de'
|
||||
});
|
||||
|
||||
expect(resolveTaskLanguageCode(settings, 'generateTitle')).toBe('zh-CN');
|
||||
});
|
||||
|
||||
test('uses task-specific language when enabled', () => {
|
||||
const settings = createSettings({
|
||||
language: 'en',
|
||||
useDifferentLanguagesForTasks: true,
|
||||
summarizeToMermaidLanguage: 'ja'
|
||||
});
|
||||
|
||||
expect(resolveTaskLanguageCode(settings, 'summarizeToMermaid')).toBe('ja');
|
||||
});
|
||||
|
||||
test('uses extractOriginalText language for merged mode', () => {
|
||||
const settings = createSettings({
|
||||
language: 'en',
|
||||
useDifferentLanguagesForTasks: true,
|
||||
extractOriginalTextLanguage: 'fr'
|
||||
});
|
||||
|
||||
expect(resolveTaskLanguageCode(settings, 'extractOriginalTextMerged')).toBe('fr');
|
||||
});
|
||||
|
||||
test('translate task always uses global translation target', () => {
|
||||
const settings = createSettings({
|
||||
language: 'es',
|
||||
useDifferentLanguagesForTasks: true
|
||||
});
|
||||
|
||||
expect(resolveTaskLanguageCode(settings, 'translate')).toBe('es');
|
||||
});
|
||||
|
||||
test('resolves language display name from configured language list', () => {
|
||||
const settings = createSettings({
|
||||
language: 'zh-CN',
|
||||
useDifferentLanguagesForTasks: false
|
||||
});
|
||||
|
||||
expect(resolveTaskLanguageName(settings, 'addLinks')).toBe('简体中文');
|
||||
});
|
||||
|
||||
test('disableAutoTranslation is ignored for explicit translate task', () => {
|
||||
const settings = createSettings({ disableAutoTranslation: true });
|
||||
expect(shouldApplyAutoTranslation(settings, 'translate')).toBe(true);
|
||||
});
|
||||
|
||||
test('disableAutoTranslation disables auto-translation for non-translate tasks', () => {
|
||||
const settings = createSettings({ disableAutoTranslation: true });
|
||||
const cases: TaskKey[] = ['addLinks', 'generateTitle', 'researchSummarize', 'summarizeToMermaid', 'extractConcepts'];
|
||||
|
||||
cases.forEach(taskKey => {
|
||||
expect(shouldApplyAutoTranslation(settings, taskKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ui locale resolution', () => {
|
||||
test('uses manual locale when configured and supported', () => {
|
||||
const settings = createSettings({ uiLocale: 'zh-CN' });
|
||||
expect(resolveUiLocale(settings, 'en')).toBe('zh-CN');
|
||||
});
|
||||
|
||||
test('uses obsidian locale when ui locale is auto', () => {
|
||||
const settings = createSettings({ uiLocale: 'auto' });
|
||||
expect(resolveUiLocale(settings, 'zh_cn')).toBe('zh-CN');
|
||||
});
|
||||
|
||||
test('falls back to en when unsupported', () => {
|
||||
const settings = createSettings({ uiLocale: 'auto' });
|
||||
expect(resolveUiLocale(settings, 'fr')).toBe('en');
|
||||
});
|
||||
});
|
||||
|
||||
33
src/tests/localeFormat.test.ts
Normal file
33
src/tests/localeFormat.test.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { formatDateForLocale, formatTimeForLocale, isRtlLocale } from '../i18n/localeFormat';
|
||||
|
||||
describe('localeFormat utilities', () => {
|
||||
test('detects RTL locales by prefix', () => {
|
||||
expect(isRtlLocale('ar')).toBe(true);
|
||||
expect(isRtlLocale('he-IL')).toBe(true);
|
||||
expect(isRtlLocale('fa_IR')).toBe(true);
|
||||
expect(isRtlLocale('en')).toBe(false);
|
||||
});
|
||||
|
||||
test('formats time with provided locale and falls back safely', () => {
|
||||
const date = new Date('2026-01-02T03:04:05.000Z');
|
||||
const formatted = formatTimeForLocale(date, 'en-US');
|
||||
expect(typeof formatted).toBe('string');
|
||||
expect(formatted.length).toBeGreaterThan(0);
|
||||
|
||||
const fallback = formatTimeForLocale(date, 'invalid-locale-name');
|
||||
expect(typeof fallback).toBe('string');
|
||||
expect(fallback.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('formats date with provided locale and falls back safely', () => {
|
||||
const date = new Date('2026-01-02T03:04:05.000Z');
|
||||
const formatted = formatDateForLocale(date, 'zh-CN');
|
||||
expect(typeof formatted).toBe('string');
|
||||
expect(formatted.length).toBeGreaterThan(0);
|
||||
|
||||
const fallback = formatDateForLocale(date, 'invalid-locale-name');
|
||||
expect(typeof fallback).toBe('string');
|
||||
expect(fallback.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -4,6 +4,7 @@ import { getProviderForTask, getModelForTask, splitContent, createConcurrentProc
|
|||
import { callLLM, handleApiError } from './llmUtils';
|
||||
import { getSystemPrompt } from './promptUtils';
|
||||
import { ProgressModal } from './ui/ProgressModal';
|
||||
import { resolveLanguageDisplayName } from './i18n/languageContext';
|
||||
|
||||
|
||||
export async function batchTranslateFolder(
|
||||
|
|
@ -76,8 +77,9 @@ export async function translateFile(
|
|||
}
|
||||
const model = getModelForTask('translate', provider, settings);
|
||||
|
||||
const promptLanguageName = resolveLanguageDisplayName(settings, targetLanguage);
|
||||
const prompt = getSystemPrompt(settings, 'translate', {
|
||||
LANGUAGE: targetLanguage,
|
||||
LANGUAGE: promptLanguageName,
|
||||
TEXT: fileContent,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export interface NotemdSettings {
|
|||
// Add Links Post-Processing
|
||||
removeCodeFencesOnAddLinks: boolean; // New: Option to remove ```markdown and ``` fences
|
||||
// Language Settings
|
||||
uiLocale: string; // 'auto' uses Obsidian locale; otherwise a specific UI locale code
|
||||
language: string; // Stores the selected language code (e.g., 'en', 'es', 'fr')
|
||||
availableLanguages: Array<{ code: string; name: string }>; // List of available languages
|
||||
useDifferentLanguagesForTasks: boolean;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { App, Modal, Notice } from 'obsidian';
|
||||
import { getI18nStrings } from '../i18n';
|
||||
|
||||
// --- Error Modal for Copyable Messages ---
|
||||
export class ErrorModal extends Modal {
|
||||
|
|
@ -12,6 +13,7 @@ export class ErrorModal extends Modal {
|
|||
}
|
||||
|
||||
onOpen() {
|
||||
const i18n = getI18nStrings({ uiLocale: 'auto' });
|
||||
const { contentEl } = this;
|
||||
contentEl.addClass('notemd-error-modal');
|
||||
|
||||
|
|
@ -23,19 +25,19 @@ export class ErrorModal extends Modal {
|
|||
|
||||
// Add a copy button
|
||||
const buttonContainer = contentEl.createDiv({ cls: 'modal-button-container' });
|
||||
const copyButton = buttonContainer.createEl('button', { text: 'Copy Error Details', cls: 'mod-cta' });
|
||||
const copyButton = buttonContainer.createEl('button', { text: i18n.errorModal.copyDetails, cls: 'mod-cta' });
|
||||
copyButton.onclick = () => {
|
||||
navigator.clipboard.writeText(this.errorMessage).then(() => {
|
||||
new Notice('Error details copied to clipboard!');
|
||||
copyButton.setText('Copied!');
|
||||
setTimeout(() => copyButton.setText('Copy Error Details'), 2000);
|
||||
new Notice(i18n.errorModal.copySuccessNotice);
|
||||
copyButton.setText(i18n.errorModal.copied);
|
||||
setTimeout(() => copyButton.setText(i18n.errorModal.copyDetails), 2000);
|
||||
}).catch(err => {
|
||||
new Notice('Failed to copy error details. See console.');
|
||||
new Notice(i18n.errorModal.copyFailedNotice);
|
||||
console.error('Failed to copy error to clipboard:', err);
|
||||
});
|
||||
};
|
||||
|
||||
const closeButton = buttonContainer.createEl('button', { text: 'Close' });
|
||||
const closeButton = buttonContainer.createEl('button', { text: i18n.common.close });
|
||||
closeButton.onclick = () => {
|
||||
this.close();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import {
|
|||
SIDEBAR_ACTION_DEFINITIONS,
|
||||
SidebarActionId
|
||||
} from '../workflowButtons';
|
||||
import { formatI18n, getI18nStrings } from '../i18n';
|
||||
|
||||
// Define specific key types for settings accessed dynamically
|
||||
type ProviderSettingKey = 'addLinksProvider' | 'researchProvider' | 'generateTitleProvider' | 'translateProvider';
|
||||
|
|
@ -95,6 +96,7 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
buttonControl: ButtonComponent,
|
||||
callMode: ProviderDiagnosticCallMode
|
||||
): Promise<void> {
|
||||
const i18n = getI18nStrings({ uiLocale: this.plugin.settings.uiLocale });
|
||||
const blockingIssues = getProviderValidationIssues(provider)
|
||||
.filter(issue => issue.level === 'error')
|
||||
.map(issue => issue.message);
|
||||
|
|
@ -104,7 +106,7 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
return;
|
||||
}
|
||||
|
||||
buttonControl.setDisabled(true).setButtonText('Running...');
|
||||
buttonControl.setDisabled(true).setButtonText(i18n.settings.developer.runDiagnostic);
|
||||
const runningNotice = new Notice(`Running developer diagnostic for ${provider.name}...`, 0);
|
||||
const timeoutMs = this.sanitizeDeveloperDiagnosticTimeoutMs(this.plugin.settings.developerDiagnosticTimeoutMs);
|
||||
|
||||
|
|
@ -127,7 +129,7 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
new Notice(`Developer diagnostic failed before report generation: ${message}`, 12000);
|
||||
console.error('Developer provider diagnostic failed:', error);
|
||||
} finally {
|
||||
buttonControl.setDisabled(false).setButtonText('Run diagnostic');
|
||||
buttonControl.setDisabled(false).setButtonText(i18n.settings.developer.runDiagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +138,7 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
buttonControl: ButtonComponent,
|
||||
callMode: ProviderDiagnosticCallMode
|
||||
): Promise<void> {
|
||||
const i18n = getI18nStrings({ uiLocale: this.plugin.settings.uiLocale });
|
||||
const blockingIssues = getProviderValidationIssues(provider)
|
||||
.filter(issue => issue.level === 'error')
|
||||
.map(issue => issue.message);
|
||||
|
|
@ -147,7 +150,7 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
|
||||
const runs = this.sanitizeDeveloperDiagnosticRuns(this.plugin.settings.developerDiagnosticStabilityRuns);
|
||||
const timeoutMs = this.sanitizeDeveloperDiagnosticTimeoutMs(this.plugin.settings.developerDiagnosticTimeoutMs);
|
||||
buttonControl.setDisabled(true).setButtonText('Running...');
|
||||
buttonControl.setDisabled(true).setButtonText(i18n.settings.developer.runStability);
|
||||
const runningNotice = new Notice(
|
||||
`Running developer stability diagnostic for ${provider.name} (${runs} runs)...`,
|
||||
0
|
||||
|
|
@ -171,7 +174,7 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
new Notice(`Developer stability diagnostic failed before report generation: ${message}`, 12000);
|
||||
console.error('Developer provider stability diagnostic failed:', error);
|
||||
} finally {
|
||||
buttonControl.setDisabled(false).setButtonText('Run stability test');
|
||||
buttonControl.setDisabled(false).setButtonText(i18n.settings.developer.runStability);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -455,6 +458,7 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
const i18n = getI18nStrings({ uiLocale: this.plugin.settings.uiLocale });
|
||||
|
||||
// --- Provider Configuration ---
|
||||
new Setting(containerEl).setName('LLM providers').setHeading();
|
||||
|
|
@ -793,8 +797,8 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Developer mode')
|
||||
.setDesc('On: Show dedicated developer diagnostic tools in settings. Off: Hide developer-only controls.')
|
||||
.setName(i18n.settings.developer.modeName)
|
||||
.setDesc(i18n.settings.developer.modeDesc)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.enableDeveloperMode)
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -804,7 +808,7 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
if (this.plugin.settings.enableDeveloperMode && activeProvider) {
|
||||
new Setting(containerEl).setName('Developer diagnostics').setHeading();
|
||||
new Setting(containerEl).setName(i18n.settings.developer.heading).setHeading();
|
||||
|
||||
const diagnosticModeOptions = getProviderDiagnosticCallModeOptions(activeProvider);
|
||||
const modeSet = new Set(diagnosticModeOptions.map(option => option.value));
|
||||
|
|
@ -873,12 +877,12 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
.setName('Developer provider diagnostic (long request)')
|
||||
.setDesc('Run one long-request diagnostic with the selected call mode and save a full report to vault root.')
|
||||
.addButton(button => button
|
||||
.setButtonText('Run diagnostic')
|
||||
.setButtonText(i18n.settings.developer.runDiagnostic)
|
||||
.onClick(async () => {
|
||||
await this.runDeveloperProviderDiagnostic(activeProvider, button, effectiveCallMode);
|
||||
}))
|
||||
.addButton(button => button
|
||||
.setButtonText('Run stability test')
|
||||
.setButtonText(i18n.settings.developer.runStability)
|
||||
.onClick(async () => {
|
||||
await this.runDeveloperProviderStabilityDiagnostic(activeProvider, button, effectiveCallMode);
|
||||
}));
|
||||
|
|
@ -1094,10 +1098,10 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
|
||||
new Setting(containerEl).setName('Language settings').setHeading();
|
||||
new Setting(containerEl).setName(i18n.settings.language.heading).setHeading();
|
||||
new Setting(containerEl)
|
||||
.setName('Output language')
|
||||
.setDesc('Select the desired output language for LLM responses.')
|
||||
.setName(i18n.settings.language.outputName)
|
||||
.setDesc(i18n.settings.language.outputDesc)
|
||||
.addDropdown(dropdown => {
|
||||
(this.plugin.settings.availableLanguages || DEFAULT_SETTINGS.availableLanguages).forEach(lang => {
|
||||
dropdown.addOption(lang.code, lang.name);
|
||||
|
|
@ -1111,8 +1115,8 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Select different languages for different tasks.')
|
||||
.setDesc('On: Select a specific language for each task below. Off: Use the single "Output language".')
|
||||
.setName(i18n.settings.language.perTaskName)
|
||||
.setDesc(i18n.settings.language.perTaskDesc)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.useDifferentLanguagesForTasks)
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -1122,11 +1126,8 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Disable auto translation (except for "Translate" task)')
|
||||
.setDesc(
|
||||
'On: Non-Translate tasks do not force a target language or auto-translate outputs. ' +
|
||||
'The explicit "Translate" task still performs translation as configured.'
|
||||
)
|
||||
.setName(i18n.settings.language.disableAutoTranslationName)
|
||||
.setDesc(i18n.settings.language.disableAutoTranslationDesc)
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.disableAutoTranslation)
|
||||
|
|
@ -1141,8 +1142,8 @@ export class NotemdSettingTab extends PluginSettingTab {
|
|||
|
||||
const createTaskLanguageSettings = (languageSettingName: keyof NotemdSettings, taskDesc: string) => {
|
||||
new Setting(containerEl)
|
||||
.setName(`${taskDesc} language`)
|
||||
.setDesc(`Select the output language for "${taskDesc}".`)
|
||||
.setName(formatI18n(i18n.settings.language.taskLanguageLabel, { task: taskDesc }))
|
||||
.setDesc(formatI18n(i18n.settings.language.taskLanguageDesc, { task: taskDesc }))
|
||||
.addDropdown(dropdown => {
|
||||
availableLanguages.forEach(lang => {
|
||||
dropdown.addOption(lang.code, lang.name);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {
|
|||
SIDEBAR_ACTION_DEFINITIONS,
|
||||
SidebarActionId
|
||||
} from '../workflowButtons';
|
||||
import { formatI18n, getI18nStrings } from '../i18n';
|
||||
import { formatTimeForLocale } from '../i18n/localeFormat';
|
||||
|
||||
type ActionCategory = typeof SIDEBAR_ACTION_DEFINITIONS[number]['category'];
|
||||
|
||||
|
|
@ -88,6 +90,10 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
private actionButtons = new Map<string, HTMLButtonElement>();
|
||||
private workflowButtons: HTMLButtonElement[] = [];
|
||||
|
||||
private getStrings() {
|
||||
return getI18nStrings({ uiLocale: this.plugin.settings.uiLocale });
|
||||
}
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: NotemdPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
|
|
@ -121,9 +127,10 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
}
|
||||
|
||||
clearDisplay() {
|
||||
const i18n = this.getStrings();
|
||||
this.logContent = [];
|
||||
if (this.logEl) this.logEl.empty();
|
||||
if (this.statusEl) this.statusEl.setText('Ready');
|
||||
if (this.statusEl) this.statusEl.setText(i18n.common.ready);
|
||||
if (this.progressAreaEl) this.progressAreaEl.addClass('is-idle');
|
||||
if (this.progressEl) {
|
||||
this.progressEl.dataset.progress = '0';
|
||||
|
|
@ -136,11 +143,11 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
this.progressBarContainerEl.addClass('is-idle');
|
||||
}
|
||||
if (this.progressValueEl) {
|
||||
this.progressValueEl.setText('Ready');
|
||||
this.progressValueEl.setText(i18n.common.ready);
|
||||
this.progressValueEl.addClass('is-idle');
|
||||
this.progressValueEl.removeClass('is-error');
|
||||
}
|
||||
if (this.timeRemainingEl) this.timeRemainingEl.setText('Standby');
|
||||
if (this.timeRemainingEl) this.timeRemainingEl.setText(i18n.common.standby);
|
||||
if (this.cancelButton) {
|
||||
this.cancelButton.disabled = true;
|
||||
this.cancelButton.removeClass('is-active');
|
||||
|
|
@ -206,7 +213,8 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
if (!this.logEl) {
|
||||
return;
|
||||
}
|
||||
const timestamp = `[${new Date().toLocaleTimeString()}]`;
|
||||
const uiLocale = this.plugin.settings.uiLocale === 'auto' ? undefined : this.plugin.settings.uiLocale;
|
||||
const timestamp = `[${formatTimeForLocale(new Date(), uiLocale)}]`;
|
||||
const fullMessage = `${timestamp} ${message}`;
|
||||
this.logContent.push(fullMessage);
|
||||
|
||||
|
|
@ -357,8 +365,9 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
}
|
||||
|
||||
private async runSingleAction(actionId: SidebarActionId): Promise<void> {
|
||||
const i18n = this.getStrings();
|
||||
if (this.isProcessing || this.plugin.getIsBusy()) {
|
||||
new Notice('Processing already in progress.');
|
||||
new Notice(i18n.notices.processingAlreadyRunning);
|
||||
return;
|
||||
}
|
||||
const actionLabel = SIDEBAR_ACTION_DEFINITIONS.find(def => def.id === actionId)?.label || actionId;
|
||||
|
|
@ -380,8 +389,9 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
}
|
||||
|
||||
private async runCustomWorkflow(button: CustomWorkflowButton): Promise<void> {
|
||||
const i18n = this.getStrings();
|
||||
if (this.isProcessing || this.plugin.getIsBusy()) {
|
||||
new Notice('Processing already in progress.');
|
||||
new Notice(i18n.notices.processingAlreadyRunning);
|
||||
return;
|
||||
}
|
||||
this.startProcessing(`Workflow: ${button.name}`);
|
||||
|
|
@ -582,8 +592,9 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
}
|
||||
|
||||
private buildLanguageSelector(parent: HTMLElement) {
|
||||
const i18n = this.getStrings();
|
||||
const row = parent.createDiv({ cls: 'notemd-inline-control' });
|
||||
row.createEl('label', { text: 'Language', cls: 'notemd-inline-label' });
|
||||
row.createEl('label', { text: i18n.common.language, cls: 'notemd-inline-label' });
|
||||
this.languageSelector = row.createEl('select', { cls: 'notemd-language-select' });
|
||||
const selector = this.languageSelector;
|
||||
this.plugin.settings.availableLanguages.forEach(lang => {
|
||||
|
|
@ -593,11 +604,12 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
selector.onchange = async () => {
|
||||
this.plugin.settings.language = selector.value;
|
||||
await this.plugin.saveSettings();
|
||||
new Notice(`Language changed to ${selector.value}`);
|
||||
new Notice(formatI18n(i18n.sidebar.languageChangedNotice, { language: selector.value }));
|
||||
};
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
const i18n = this.getStrings();
|
||||
const container = this.containerEl.children[1] as HTMLElement;
|
||||
container.empty();
|
||||
container.addClass('notemd-sidebar-container');
|
||||
|
|
@ -606,14 +618,14 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
const footer = shell.createDiv({ cls: 'notemd-sidebar-footer mod-docked' });
|
||||
|
||||
const hero = scrollArea.createDiv({ cls: 'notemd-hero-card' });
|
||||
hero.createEl('h3', { text: 'Notemd Workbench' });
|
||||
hero.createEl('p', { text: 'Run single actions or custom one-click workflows with live progress and logs.' });
|
||||
hero.createEl('h3', { text: i18n.sidebar.heroTitle });
|
||||
hero.createEl('p', { text: i18n.sidebar.heroDesc });
|
||||
|
||||
const workflowResolution = resolveCustomWorkflowButtons(this.plugin.settings.customWorkflowButtonsDsl);
|
||||
const quickBody = this.createSection(
|
||||
scrollArea,
|
||||
'Quick Workflows',
|
||||
'Custom buttons assembled from built-in actions.',
|
||||
i18n.sidebar.quickWorkflowTitle,
|
||||
i18n.sidebar.quickWorkflowDesc,
|
||||
true
|
||||
);
|
||||
|
||||
|
|
@ -636,7 +648,7 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
|
||||
if (workflowResolution.usedFallback && workflowResolution.errors.length > 0) {
|
||||
const warn = quickBody.createDiv({ cls: 'notemd-workflow-warning' });
|
||||
warn.setText(`Workflow DSL has ${workflowResolution.errors.length} issue(s). Sidebar is using default fallback.`);
|
||||
warn.setText(formatI18n(i18n.sidebar.workflowFallbackWarning, { count: workflowResolution.errors.length }));
|
||||
}
|
||||
|
||||
const actionsByCategory = new Map<ActionCategory, Array<typeof SIDEBAR_ACTION_DEFINITIONS[number]>>();
|
||||
|
|
@ -655,7 +667,7 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
const body = this.createSection(
|
||||
scrollArea,
|
||||
ACTION_CATEGORY_LABEL[category].title,
|
||||
`Built-in ${ACTION_CATEGORY_LABEL[category].title.toLowerCase()} actions.`,
|
||||
formatI18n(i18n.sidebar.builtInActionsPrefix, { category: ACTION_CATEGORY_LABEL[category].title.toLowerCase() }),
|
||||
ACTION_CATEGORY_LABEL[category].openByDefault
|
||||
);
|
||||
|
||||
|
|
@ -671,26 +683,26 @@ export class NotemdSidebarView extends ItemView implements ProgressReporter {
|
|||
const progressArea = footer.createDiv({ cls: 'notemd-progress-area is-idle' });
|
||||
this.progressAreaEl = progressArea;
|
||||
const progressMeta = progressArea.createDiv({ cls: 'notemd-progress-meta' });
|
||||
this.statusEl = progressMeta.createEl('p', { text: 'Ready', cls: 'notemd-status-text' });
|
||||
this.progressValueEl = progressMeta.createEl('span', { text: 'Ready', cls: 'notemd-progress-value is-idle' });
|
||||
this.statusEl = progressMeta.createEl('p', { text: i18n.common.ready, cls: 'notemd-status-text' });
|
||||
this.progressValueEl = progressMeta.createEl('span', { text: i18n.common.ready, cls: 'notemd-progress-value is-idle' });
|
||||
this.progressBarContainerEl = progressArea.createEl('div', { cls: 'notemd-progress-bar-container mod-sidebar is-idle' });
|
||||
this.progressEl = this.progressBarContainerEl.createEl('div', { cls: 'notemd-progress-bar-fill' });
|
||||
this.timeRemainingEl = progressArea.createEl('p', { text: 'Standby', cls: 'notemd-time-remaining' });
|
||||
this.timeRemainingEl = progressArea.createEl('p', { text: i18n.common.standby, cls: 'notemd-time-remaining' });
|
||||
|
||||
this.cancelButton = progressArea.createEl('button', { text: 'Cancel processing', cls: 'notemd-cancel-button' });
|
||||
this.cancelButton = progressArea.createEl('button', { text: i18n.sidebar.cancelProcessing, cls: 'notemd-cancel-button' });
|
||||
this.cancelButton.onclick = () => this.requestCancel();
|
||||
|
||||
const logCard = footer.createDiv({ cls: 'notemd-log-card mod-persistent' });
|
||||
const logHeader = logCard.createDiv({ cls: 'notemd-log-header' });
|
||||
logHeader.createEl('h5', { text: 'Log output' });
|
||||
const copyLogButton = logHeader.createEl('button', { text: 'Copy log', cls: 'notemd-copy-log-button' });
|
||||
logHeader.createEl('h5', { text: i18n.sidebar.logOutputTitle });
|
||||
const copyLogButton = logHeader.createEl('button', { text: i18n.sidebar.copyLog, cls: 'notemd-copy-log-button' });
|
||||
copyLogButton.onclick = () => {
|
||||
if (this.logContent.length > 0) {
|
||||
navigator.clipboard
|
||||
.writeText(this.logContent.join('\n'))
|
||||
.then(() => new Notice('Log copied!'), () => new Notice('Failed to copy log.'));
|
||||
.then(() => new Notice(i18n.sidebar.copyLogSuccess), () => new Notice(i18n.sidebar.copyLogFailed));
|
||||
} else {
|
||||
new Notice('Log is empty.');
|
||||
new Notice(i18n.sidebar.logEmpty);
|
||||
}
|
||||
};
|
||||
this.logEl = logCard.createEl('div', { cls: 'notemd-log-output is-selectable mod-sidebar' });
|
||||
|
|
|
|||
16
styles.css
16
styles.css
|
|
@ -746,3 +746,19 @@
|
|||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* RTL safety: keep Notemd panel readable in right-to-left locales. */
|
||||
.mod-rtl .notemd-sidebar-container,
|
||||
.mod-rtl .notemd-log-output,
|
||||
.mod-rtl .notemd-status-text,
|
||||
.mod-rtl .notemd-time-remaining {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.notemd-log-time,
|
||||
.notemd-log-message,
|
||||
.notemd-status-text,
|
||||
.notemd-time-remaining {
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,10 @@
|
|||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"ref"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue