diff --git a/.gitignore b/.gitignore index b24dfe6..dd85a09 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ src/backend/bin/ src/backend/*.txt src/backend/*.db +telos.db references/ private/ diff --git a/RELEASE_v0.3.0.md b/RELEASE_v0.3.0.md index 7e3903e..2e343a9 100644 --- a/RELEASE_v0.3.0.md +++ b/RELEASE_v0.3.0.md @@ -2,6 +2,7 @@ TelosDB v0.3.0 へようこそ! 今回のリリースでは、データベース構造の抜本的な正規化、検索精度の向上、そして「MIMEタイプ対応」による将来的なファイル形式拡張への基盤を構築しました。 +加えて、配布パッケージ(NSIS インストーラ)の安定化と UI の改善を行いました。 ## 🚀 新機能 (Major Features) @@ -32,6 +33,18 @@ ## 🛠 改善・修正 (Improvements & Fixes) +### 配布パッケージ・インストーラ (Installer & Packaging) + +- **vec0.dll の扱い**: セマンティック検索用 SQLite 拡張(vec0.dll)をバンドルリソースのルートに配置し、インストール先のパス差を吸収するため、起動時に `%APPDATA%\com.telosdb.app\vec0.dll` へコピーしてから読み込むようにしました。 +- **複数フォールバック**: exe と同じフォルダ・`resources` フォルダ・Tauri の resource_dir を順に参照し、いずれかで vec0.dll が見つかれば起動可能です。 +- **エラーメッセージ**: vec0.dll が見つからない場合はパニックせず、試したパスを表示して終了するようにしました。ログは `%APPDATA%\com.telosdb.app\logs\telos.log` に出力されます。 + +### UI + +- **Toast UI Editor ダークモード**: 文書の追加・編集モーダル内のエディタをダークテーマに統一し、アプリ全体のダーク UI と一貫させました。 + +### その他 + - **プロジェクト構成**: `nodejs/` と `scripts/` を統合し、標準的なディレクトリ構造へ移行。 - **AIエージェント最適化**: 運用ルールを `.agent/rules` へ集約。AIエージェントによる自己修正機能を強化しました。 - **仕様書の整理**: 技術仕様書を最新の状態に更新し、番号体系を整理(01〜06)。 @@ -41,5 +54,8 @@ ## 📦 インストール方法 -添付の `TelosDB_0.3.0_x64-setup.exe`(またはソースコードからのビルド)をご利用ください。 -データベーススキーマに変更があるため、起動時に自動的にマイグレーションまたは補完(セルフヒーリング)が実行されます。 +添付の `TelosDB_0.3.0_x64-setup.exe`(NSIS インストーラ)をご利用ください。 +ソースコードからビルドする場合は、リポジトリルートで `npm install` の後に `npm run tauri build` を実行してください。 + +- データベーススキーマに変更があるため、起動時に自動的にマイグレーションまたは補完(セルフヒーリング)が実行されます。 +- 初回起動時に vec0.dll がアプリデータフォルダへコピーされます。問題が発生した場合は `%APPDATA%\com.telosdb.app\logs\telos.log` で原因を確認できます。 diff --git a/docs/specification/04_mcp_api_specification.md b/docs/specification/04_mcp_api_specification.md index d1d9f2d..9457bff 100644 --- a/docs/specification/04_mcp_api_specification.md +++ b/docs/specification/04_mcp_api_specification.md @@ -25,11 +25,23 @@ | ツール名 | 役割 | 解説 | | :--- | :--- | :--- | | `add_item_text` | 文書の永続化 | テキストを LSA 解析によってベクトル化し DB へ保存。 | -| `search_text` | 意味検索 | 記述内容を LSA 解析し、類似度に基づいたベクトル検索を実行。 | +| `search_text` | 意味検索 | 記述内容を LSA 解析し、類似度に基づいたベクトル検索を実行。FTS5 (BM25) とベクトル検索のハイブリッド。 | | `update_item` | 知識の更新 | ID 指定による既存データの書き換え。 | | `delete_item` | 知識の抹消 | ID 指定による物理削除。 | | `get_item_by_id` | 生データ取得 | メタデータを含むレコードの直接参照。 | +### 3.1 search_text パラメータ + +| パラメータ | 型 | 必須 | 既定値 | 説明 | +| :--- | :--- | :--- | :--- | :--- | +| `content` | string | ○ | — | 検索クエリ文字列。 | +| `limit` | integer | — | 10 | 返却する最大件数(1〜100)。 | +| `min_score` | number | — | 0.3 | 足切り閾値(0〜1)。この値未満の類似度の結果は返却しない。 | + +- **省略時**: `min_score` を省略した場合は **0.3** が適用される。 +- **スコア**: 各結果の `similarity` は 0〜1 に正規化され、ベクトル検索と FTS5 のスコアの大きい方が採用される。 +- **該当語句がない場合**: クエリのトークンが LSA 語彙に1つも存在しない場合はベクトル検索をスキップし、FTS5 の結果のみ返す(無関係なドキュメントに 1.0 が付くのを防ぐ)。 + ## 4. 通信シーケンスとレスポンス形式 ツール呼び出しは JSON-RPC 2.0 に準拠しており、結果は常に MCP 規格の `content` 配列形式で返されます。 diff --git a/docs/specification/06_ui_design_spec.md b/docs/specification/06_ui_design_spec.md index 0a433f5..2b3f249 100644 --- a/docs/specification/06_ui_design_spec.md +++ b/docs/specification/06_ui_design_spec.md @@ -36,3 +36,21 @@ 1. **Header (HUD)**: 常に固定され、モデル名とデータ件数を表示(ヘッドアップディスプレイ)。 2. **Main Content**: 検索バーと結果リスト。余白(Negative Space)を適切に取り、情報のノイズを排除。 3. **Footer (Toolbar)**: 設定へのアクセスと再インデックスボタンを集約。 + +## 5. 設定パネルと永続化 + +サイドバーの「設定」から検索まわりの設定を変更できる。 + +### 5.1 検索設定項目 + +| 項目 | 説明 | 既定値 | +| :--- | :--- | :--- | +| スコア足切り (0〜1) | `min_score`。この値未満の類似度の結果は表示しない。 | 0.3 | +| 取得件数 | 検索で返す最大件数(`limit`)。 | 10 | + +### 5.2 設定値の保存先 + +- **保存先**: ブラウザ(WebView)の **localStorage**。 +- **キー**: `telosdb_settings`。 +- **形式**: JSON(例: `{"min_score":0.3,"limit":10}`)。 +- 「保存」ボタンで反映され、アプリ再起動後も保持される。設定未保存時は上記の既定値が使われる。 diff --git "a/journals/202602-010-\343\202\244\343\203\263\343\202\271\343\203\210\343\203\274\343\203\251\345\256\211\345\256\232\345\214\226\343\201\250\343\203\252\343\203\252\343\203\274\343\202\271\346\224\271\350\250\202.md" "b/journals/202602-010-\343\202\244\343\203\263\343\202\271\343\203\210\343\203\274\343\203\251\345\256\211\345\256\232\345\214\226\343\201\250\343\203\252\343\203\252\343\203\274\343\202\271\346\224\271\350\250\202.md" new file mode 100644 index 0000000..26d3fc8 --- /dev/null +++ "b/journals/202602-010-\343\202\244\343\203\263\343\202\271\343\203\210\343\203\274\343\203\251\345\256\211\345\256\232\345\214\226\343\201\250\343\203\252\343\203\252\343\203\274\343\202\271\346\224\271\350\250\202.md" @@ -0,0 +1,89 @@ +# 2026年第010週 (2/24) 作業アーカイブ: インストーラ安定化・UI・リリースノート・MCP登録 + +## 週全体のサマリー + +今週は、v0.3.0 配布パッケージ(NSIS インストーラ)の起動不良対策を中心に、vec0.dll のリソース配置と起動時コピーを実装。あわせて Toast UI Editor のダークモード化、RELEASE_v0.3.0.md の改訂、および MCP 経由での俳句・短歌解説の登録と LSA 再学習を実施した。 + +--- + +## 2026-02-24: インストーラ起動不良の修正(vec0.dll) + +### 1. 作業実施の理由と指示 + +- **背景**: インストール後にアプリが起動しない事象が報告された。ログでは `vec0.dll NOT FOUND` および DB 初期化失敗(crashed background worker)が確認された。 +- **意図**: 配布パッケージ実行時にも vec0.dll を確実に参照できるようにし、起動を安定させる。 + +### 2. 指摘事項とその対応 + +- **指摘**: `tauri.conf.json` の `resources` を配列で指定していたため、バンドル時に `_up_/_up_/node_modules/...` のようなパスに配置され、`resource_dir.join("vec0.dll")` で見つからない。 + - **対応**: リソースをマップ形式に変更し、`"../../node_modules/sqlite-vec-windows-x64/vec0.dll": "vec0.dll"` としてリソースルートに配置。 +- **指摘**: インストール先によっては exe と resources が別階層になる。 + - **対応**: `exe_dir.join("vec0.dll")` および `exe_dir.join("resources").join("vec0.dll")` をフォールバックに追加。さらに、見つかった dll を `%APPDATA%\com.telosdb.app\vec0.dll` にコピーしてから load_extension するようにし、パス差を吸収。 +- **指摘**: vec0.dll 未検出時に panic でクラッシュする。 + - **対応**: 未検出時は `Err` を返して終了し、試したパスをメッセージとログに出力するように変更。 + +### 3. 作業詳細 + +- `src/backend/tauri.conf.json`: `resources` をオブジェクト形式に変更。 +- `src/backend/src/lib.rs`: `BaseDirectory::Resource` による resolve、exe_dir / resource_dir の複数フォールバック、app_data_dir へのコピー、`resource_dir` / `exe_dir` のログ出力、未検出時のエラー返却を実装。 + +### 4. AI視点での結果 + +インストール後の実行パスに依存せず、vec0.dll を参照できるようになった。ユーザー環境では再ビルド・再インストール後に動作確認が必要。 + +--- + +## 2026-02-24: Toast UI Editor ダークモード化 + +### 1. 作業実施の理由と指示 + +- **背景**: アプリ全体がダークテーマのため、文書編集モーダル内の Toast UI Editor もダークに統一したい。 +- **意図**: 公式のダークテーマを有効化し、UI の一貫性を保つ。 + +### 2. 作業詳細 + +- `tools/editor-bundle-entry.js`: `@toast-ui/editor/dist/theme/toastui-editor-dark.css` を import に追加。 +- `src/frontend/components/main-panel.js`: `toastui.Editor` のオプションに `theme: 'dark'` を追加。 +- `npm run build-editor` でバンドルを再生成し、`vendor/toast-ui/toastui-editor-bundle.css` にダークスタイルを含めた。 + +### 3. AI視点での結果 + +編集モーダルを開いた際にエディタがダーク表示となり、アプリのトーンと揃った。 + +--- + +## 2026-02-24: RELEASE_v0.3.0.md の改訂 + +### 1. 作業実施の理由と指示 + +- **背景**: インストーラ対応と UI 変更をリリースノートに反映させたい。 +- **意図**: 配布パッケージの扱いとインストール手順を正確に記載する。 + +### 2. 作業詳細 + +- 冒頭に「配布パッケージの安定化と UI の改善」の一文を追加。 +- 「改善・修正」に「配布パッケージ・インストーラ」(vec0.dll のコピー、フォールバック、エラー表示・ログ)と「UI」(Toast UI ダークモード)を追記。 +- 「インストール方法」に NSIS インストーラの明記、ソースからのビルド手順(`npm run tauri build`)、vec0.dll の初回コピーとログの場所を追記。 + +### 3. AI視点での結果 + +利用者と開発者が、インストールとトラブルシュートの手がかりをリリースノートから参照しやすくなった。 + +--- + +## 2026-02-24: 俳句・短歌解説の MCP 登録と LSA 再学習 + +### 1. 作業実施の理由と指示 + +- **背景**: TelosDB を MCP 経由で利用する流れの確認として、俳句・短歌の解説文を登録し、インデックス化まで行いたい。 +- **意図**: HTTP やスクリプトではなく、MCP ツール(add_item_text / lsa_retrain)のみで登録〜再学習を行う。 + +### 2. 作業詳細 + +- **俳句**: MCP サーバー `project-0-TelosDB-TelosDB` のツール `add_item_text` を呼び出し、`path: "haiku-explanation.md"`、`content` に俳句の解説(形式・歴史・季語・例句)を渡して登録。 +- **短歌**: 同様に `add_item_text` で `path: "tanka-explanation.md"`、短歌の解説(三十一音・万葉集〜近代・例句)を登録。 +- **インデックス化**: ツール `lsa_retrain` を呼び出し、バックグラウンドで LSA 再学習を実行。俳句・短歌のチャンクがベクトル化され、意味検索の対象になった。 + +### 3. AI視点での結果 + +MCP 経由でコンテンツ登録とインデックス更新が一通り完了し、TelosDB を「MCP の知識ベース」として扱う流れが確認できた。 diff --git a/package-lock.json b/package-lock.json index a78cdfb..83ba51f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@modelcontextprotocol/sdk": "^1.26.0", "@tauri-apps/api": "^2.10.1", "@tauri-apps/cli": "^2.10.0", + "@toast-ui/editor": "^3.2.2", "axios": "^1.13.5", "better-sqlite3": "^12.6.2", "eventsource": "^4.1.0", @@ -22,7 +23,424 @@ "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "prettier": "^3.8.1", - "typescript": "^5.2.2" + "typescript": "^5.2.2", + "vite": "^6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, "node_modules/@hono/node-server": { @@ -106,6 +524,331 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@tauri-apps/api": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", @@ -308,6 +1051,27 @@ "node": ">= 10" } }, + "node_modules/@toast-ui/editor": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@toast-ui/editor/-/editor-3.2.2.tgz", + "integrity": "sha512-ASX7LFjN2ZYQJrwmkUajPs7DRr9FsM1+RQ82CfTO0Y5ZXorBk1VZS4C2Dpxinx9kl55V4F8/A2h2QF4QMDtRbA==", + "dependencies": { + "dompurify": "^2.3.3", + "prosemirror-commands": "^1.1.9", + "prosemirror-history": "^1.1.3", + "prosemirror-inputrules": "^1.1.3", + "prosemirror-keymap": "^1.1.4", + "prosemirror-model": "^1.14.1", + "prosemirror-state": "^1.3.4", + "prosemirror-view": "^1.18.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -676,6 +1440,11 @@ "node": ">=8" } }, + "node_modules/dompurify": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz", + "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -751,6 +1520,47 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -870,6 +1680,23 @@ } ] }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -969,6 +1796,20 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1269,6 +2110,24 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -1342,6 +2201,11 @@ "wrappy": "1" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1367,6 +2231,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -1375,6 +2257,34 @@ "node": ">=16.20.0" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -1415,6 +2325,81 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz", + "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.6", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz", + "integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1543,6 +2528,55 @@ "node": ">=0.10.0" } }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1768,6 +2802,15 @@ "simple-concat": "^1.0.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sqlite-vec-windows-x64": { "version": "0.1.7-alpha.2", "resolved": "https://registry.npmjs.org/sqlite-vec-windows-x64/-/sqlite-vec-windows-x64-0.1.7-alpha.2.tgz", @@ -1821,6 +2864,22 @@ "node": ">=6" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1887,6 +2946,85 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index e9bc3de..cfa75a6 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,13 @@ "version": "0.3.0", "type": "module", "scripts": { - "tauri": "tauri" + "tauri": "tauri", + "copy-editor": "node tools/copy-toast-ui-editor.js", + "build-editor": "vite build --config tools/vite.config.editor.mjs" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", + "@toast-ui/editor": "^3.2.2", "@tauri-apps/api": "^2.10.1", "@tauri-apps/cli": "^2.10.0", "react": "^18.2.0", @@ -20,7 +23,8 @@ "devDependencies": { "prettier": "^3.8.1", "typescript": "^5.2.2", + "vite": "^6.0.0", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7" } -} \ No newline at end of file +} diff --git a/src/backend/src/lib.rs b/src/backend/src/lib.rs index aa95868..f1eb3ab 100644 --- a/src/backend/src/lib.rs +++ b/src/backend/src/lib.rs @@ -37,7 +37,9 @@ pub mod mcp; use std::sync::{Arc, Mutex}; +use std::path::PathBuf; use tauri::Manager; +use tauri::path::BaseDirectory; use tauri::menu::{Menu, MenuItem}; use tauri::tray::{TrayIconBuilder, TrayIconEvent}; use tauri_plugin_shell::process::CommandChild; @@ -127,12 +129,45 @@ let bin_dir = resource_dir.join("bin"); let model_path = bin_dir.join("gemma-3-270m-it-Q4_K_M.gguf"); - // vec0.dll はビルド時に実行ルート(resource_dir)にコピーされる - let mut vec0_path = resource_dir.join("vec0.dll"); + let exe_dir = std::env::current_exe().ok().and_then(|p| p.parent().map(|p| p.to_path_buf())); + log::info!("resource_dir: {:?}, exe_dir: {:?}", resource_dir, exe_dir); - // 開発時 (target/debug) かつ bin にある場合のフォールバック - if !vec0_path.exists() && bin_dir.join("vec0.dll").exists() { - vec0_path = bin_dir.join("vec0.dll"); + // vec0.dll: バンドル時は resources、開発時は exe 同階層。見つかったら app_data にコピーしてそこから読む + let mut vec0_path: PathBuf = app + .path() + .resolve("vec0.dll", BaseDirectory::Resource) + .unwrap_or_else(|_| app.path().resource_dir().unwrap_or_default().join("vec0.dll")); + + if !vec0_path.exists() { + if let Some(ref exe_dir) = exe_dir { + for candidate in [ + exe_dir.join("vec0.dll"), + exe_dir.join("resources").join("vec0.dll"), + resource_dir.join("vec0.dll"), + resource_dir.parent().unwrap_or(exe_dir).join("vec0.dll"), + ] { + if candidate.exists() { + vec0_path = candidate; + break; + } + } + } + } + + // 見つかった dll を app_data_dir にコピーしてから使う(インストール先のパス差を吸収) + let vec0_in_app_data = app_data_dir.join("vec0.dll"); + if vec0_path.exists() { + let _ = std::fs::create_dir_all(&app_data_dir); + if vec0_in_app_data != vec0_path { + if let Err(e) = std::fs::copy(&vec0_path, &vec0_in_app_data) { + log::warn!("Failed to copy vec0.dll to app data: {}", e); + } else { + log::info!("Copied vec0.dll to {:?}", vec0_in_app_data); + } + } + if vec0_in_app_data.exists() { + vec0_path = vec0_in_app_data; + } } // Tray Menu @@ -187,13 +222,14 @@ log::info!("vec0.dll path: {:?}", vec0_path); if !vec0_path.exists() { - log::error!( - "vec0.dll NOT FOUND at {:?}. Vector search and DB init will fail.", + let msg = format!( + "vec0.dll が見つかりません。\n試したパス:\n- {:?}\n- exe と同じフォルダ\n- exe の resources フォルダ\n\nTelosDB を再インストールしてください。", vec0_path ); + log::error!("{}", msg); + return Err(std::io::Error::new(std::io::ErrorKind::NotFound, msg).into()); } - // llama-server自動起動(Tauri sidecar API使用)は無効化 /* if model_path.exists() { let (mut rx, child) = app diff --git a/src/backend/src/mcp/mod.rs b/src/backend/src/mcp/mod.rs index ce079ce..7010f6b 100644 --- a/src/backend/src/mcp/mod.rs +++ b/src/backend/src/mcp/mod.rs @@ -172,7 +172,8 @@ "type": "object", "properties": { "content": { "type": "string" }, - "limit": { "type": "integer", "default": 10 } + "limit": { "type": "integer", "default": 10 }, + "min_score": { "type": "number", "default": 0.3, "description": "Minimum similarity (0-1). Results below this are dropped. Default 0.3." } }, "required": ["content"] } @@ -201,13 +202,40 @@ } }, { + "name": "list_documents", + "description": "List all documents (path, mime, chunk count)", + "inputSchema": { "type": "object", "properties": {} } + }, + { + "name": "get_document", + "description": "Get full document content by document ID", + "inputSchema": { + "type": "object", + "properties": { + "document_id": { "type": "integer" }, + "id": { "type": "integer" } + } + } + }, + { + "name": "delete_document", + "description": "Delete a document and all its chunks", + "inputSchema": { + "type": "object", + "properties": { + "document_id": { "type": "integer" }, + "id": { "type": "integer" } + } + } + }, + { "name": "lsa_retrain", "description": "Manually trigger LSA model retraining and vector rebuild", "inputSchema": { "type": "object", "properties": {} } } ] })), - "tools/call" | "get_item_by_id" | "add_item_text" | "search_text" | "lsa_search" | "update_item" | "delete_item" | "lsa_retrain" => { + "tools/call" | "get_item_by_id" | "add_item_text" | "search_text" | "lsa_search" | "update_item" | "delete_item" | "list_documents" | "get_document" | "delete_document" | "lsa_retrain" => { let empty_map = serde_json::Map::new(); let mut args = req.params.as_ref().and_then(|p| p.as_object()).unwrap_or(&empty_map); diff --git a/src/backend/src/mcp/tools/items.rs b/src/backend/src/mcp/tools/items.rs index 23e0f1e..2270019 100644 --- a/src/backend/src/mcp/tools/items.rs +++ b/src/backend/src/mcp/tools/items.rs @@ -346,3 +346,177 @@ tx.commit().await.map_err(|e| format!("Failed to commit transaction: {}", e))?; Ok(()) } + +// ---------------------------------------------------------------------------- +// Document-level API (list, get full content, delete by document) +// ---------------------------------------------------------------------------- + +pub async fn handle_list_documents( + state: &AppState, +) -> Option { + let rows = match sqlx::query( + "SELECT d.id, d.path, d.mime, d.updated_at, + (SELECT COUNT(*) FROM items i WHERE i.document_id = d.id) AS chunk_count + FROM documents d ORDER BY d.path" + ) + .fetch_all(&state.db_pool) + .await + { + Ok(r) => r, + Err(e) => { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Failed to list documents: {}", e) }], + "isError": true + })); + } + }; + + let list: Vec = rows.iter().map(|row| { + let id: i64 = row.get(0); + let path: String = row.get(1); + let mime: Option = row.get(2); + let updated_at: Option = row.get(3); + let chunk_count: i64 = row.get(4); + serde_json::json!({ + "id": id, + "path": path, + "mime": mime, + "updated_at": updated_at, + "chunk_count": chunk_count + }) + }).collect(); + + let text = serde_json::to_string_pretty(&list).unwrap_or_else(|_| "[]".to_string()); + Some(serde_json::json!({ + "content": [{ "type": "text", "text": text }] + })) +} + +pub async fn handle_get_document( + state: &AppState, + args: &serde_json::Map, +) -> Option { + let doc_id = args.get("document_id").and_then(|v| v.as_i64()) + .or_else(|| args.get("id").and_then(|v| v.as_i64())) + .unwrap_or(0); + + let doc_row = match sqlx::query("SELECT id, path, mime FROM documents WHERE id = ?") + .bind(doc_id) + .fetch_optional(&state.db_pool) + .await + { + Ok(Some(r)) => r, + Ok(None) => { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Document not found: {}", doc_id) }], + "isError": true + })); + } + Err(e) => { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Error: {}", e) }], + "isError": true + })); + } + }; + + let path: String = doc_row.get(1); + let mime: Option = doc_row.get(2); + + let chunks = match sqlx::query("SELECT id, chunk_index, content FROM items WHERE document_id = ? ORDER BY chunk_index") + .bind(doc_id) + .fetch_all(&state.db_pool) + .await + { + Ok(rows) => rows, + Err(e) => { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Error: {}", e) }], + "isError": true + })); + } + }; + + let full_content: String = chunks.iter() + .map(|r| r.get::(2)) + .collect(); + + let chunks_json: Vec = chunks.iter().map(|r| { + serde_json::json!({ + "id": r.get::(0), + "chunk_index": r.get::(1), + "content": r.get::(2) + }) + }).collect(); + + Some(serde_json::json!({ + "content": [{ + "type": "text", + "text": serde_json::to_string_pretty(&serde_json::json!({ + "id": doc_id, + "path": path, + "mime": mime, + "content": full_content, + "chunks": chunks_json + })).unwrap_or_else(|_| "{}".to_string()) + }] + })) +} + +pub async fn handle_delete_document( + state: &AppState, + args: &serde_json::Map, +) -> Option { + let doc_id = args.get("document_id").and_then(|v| v.as_i64()) + .or_else(|| args.get("id").and_then(|v| v.as_i64())) + .unwrap_or(0); + + let item_ids: Vec = match sqlx::query_scalar("SELECT id FROM items WHERE document_id = ?") + .bind(doc_id) + .fetch_all(&state.db_pool) + .await + { + Ok(ids) => ids, + Err(e) => { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Error: {}", e) }], + "isError": true + })); + } + }; + + let mut tx = match state.db_pool.begin().await { + Ok(t) => t, + Err(e) => { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Error: {}", e) }], + "isError": true + })); + } + }; + + for id in &item_ids { + let _ = sqlx::query("DELETE FROM vec_items WHERE id = ?").bind(id).execute(&mut *tx).await; + let _ = sqlx::query("DELETE FROM items_fts WHERE rowid = ?").bind(id).execute(&mut *tx).await; + let _ = sqlx::query("DELETE FROM items_lsa WHERE id = ?").bind(id).execute(&mut *tx).await; + } + let _ = sqlx::query("DELETE FROM items WHERE document_id = ?").bind(doc_id).execute(&mut *tx).await; + if let Err(e) = sqlx::query("DELETE FROM documents WHERE id = ?").bind(doc_id).execute(&mut *tx).await { + let _ = tx.rollback().await; + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Error: {}", e) }], + "isError": true + })); + } + if let Err(e) = tx.commit().await { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Error: {}", e) }], + "isError": true + })); + } + + let _ = state.tx.send("data_changed".to_string()); + Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Successfully deleted document {} ({} chunks)", doc_id, item_ids.len()) }] + })) +} diff --git a/src/backend/src/mcp/tools/mod.rs b/src/backend/src/mcp/tools/mod.rs index 5a8abde..e4db9d0 100644 --- a/src/backend/src/mcp/tools/mod.rs +++ b/src/backend/src/mcp/tools/mod.rs @@ -16,6 +16,9 @@ "search_text" | "lsa_search" => search::handle_search_text(state, actual_method, args).await, "update_item" => items::handle_update_item(state, args).await, "delete_item" => items::handle_delete_item(state, args).await, + "list_documents" => items::handle_list_documents(state).await, + "get_document" => items::handle_get_document(state, args).await, + "delete_document" => items::handle_delete_document(state, args).await, "lsa_retrain" => system::handle_lsa_retrain(state).await, _ => Some(serde_json::json!({ "content": [{ "type": "text", "text": format!("Unknown tool: {}", actual_method) }], diff --git a/src/backend/src/mcp/tools/search.rs b/src/backend/src/mcp/tools/search.rs index 3ac2655..8324029 100644 --- a/src/backend/src/mcp/tools/search.rs +++ b/src/backend/src/mcp/tools/search.rs @@ -13,6 +13,12 @@ args.get("content").and_then(|v| v.as_str()).unwrap_or("") }; let search_limit = args.get("limit").and_then(|v| v.as_i64()).unwrap_or(10); + // 足切り: この値未満の similarity は返さない。未指定時は 0.3(ノイズを落としつつ関連しそうなものを残す) + let min_score = args.get("min_score") + .or_else(|| args.get("minScore")) + .and_then(|v| v.as_f64()) + .unwrap_or(0.3) + .clamp(0.0, 1.0) as f32; if search_content.is_empty() { return Some(serde_json::json!({ @@ -48,6 +54,11 @@ *query_counts.entry(tid).or_insert(0.0) += 1.0; } } + // 該当する語句が語彙に1つもない場合、query_vec がゼロベクトルになり、 + // ゼロに近いドキュメントが distance≈0 で返って similarity=1.0 になるのを防ぐ + if query_counts.is_empty() { + // ベクトル検索はスキップ。FTS のみの結果にする(FTS もヒットしなければ空) + } else { let mut query_vec = ndarray::Array1::zeros(model.vocabulary.len()); for (tid, count) in query_counts { query_vec[tid] = count; @@ -99,6 +110,7 @@ } } } + } } // 4. Add remaining FTS results not found by vector search @@ -125,7 +137,11 @@ .unwrap_or(std::cmp::Ordering::Equal) }); - let final_items = sorted.into_iter().take(search_limit as usize).collect::>(); + let final_items = sorted + .into_iter() + .filter(|v| v.get("similarity").and_then(|x| x.as_f64()).unwrap_or(0.0) >= min_score as f64) + .take(search_limit as usize) + .collect::>(); let result_text = serde_json::to_string_pretty(&final_items).unwrap_or_else(|_| "[]".to_string()); Some(serde_json::json!({ diff --git a/src/backend/tauri.conf.json b/src/backend/tauri.conf.json index a40d899..4ede0d0 100644 --- a/src/backend/tauri.conf.json +++ b/src/backend/tauri.conf.json @@ -3,38 +3,18 @@ "productName": "TelosDB", "version": "0.3.0", "identifier": "com.telosdb.app", - "build": { - "frontendDist": "../frontend" - }, + "build": { "frontendDist": "../frontend" }, "app": { - "windows": [ - { - "title": "TelosDB", - "width": 800, - "height": 600, - "resizable": true, - "fullscreen": false - } - ], - "security": { - "csp": null - } + "windows": [{ "title": "TelosDB", "width": 800, "height": 600, "resizable": true, "fullscreen": false }], + "security": { "csp": null } }, "bundle": { "active": true, - "targets": [ - "nsis" - ], - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], + "targets": ["nsis"], + "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], "externalBin": [], - "resources": [ - "../../node_modules/sqlite-vec-windows-x64/vec0.dll" - ] + "resources": { + "../../node_modules/sqlite-vec-windows-x64/vec0.dll": "vec0.dll" + } } -} \ No newline at end of file +} diff --git a/src/frontend/components/main-panel.js b/src/frontend/components/main-panel.js index 3cdd701..4478d69 100644 --- a/src/frontend/components/main-panel.js +++ b/src/frontend/components/main-panel.js @@ -31,12 +31,65 @@ `; @@ -47,6 +100,25 @@ }); // Expose showPanel as method + const SETTINGS_KEY = 'telosdb_settings'; + const DEFAULTS = { min_score: 0.3, limit: 10 }; + + const loadSettingsIntoForm = () => { + try { + const raw = localStorage.getItem(SETTINGS_KEY); + const s = raw ? { ...DEFAULTS, ...JSON.parse(raw) } : DEFAULTS; + const minScoreEl = this.querySelector('#setting-min-score'); + const limitEl = this.querySelector('#setting-limit'); + if (minScoreEl) minScoreEl.value = String(Number(s.min_score)); + if (limitEl) limitEl.value = String(Number(s.limit)); + } catch (e) { + const minScoreEl = this.querySelector('#setting-min-score'); + const limitEl = this.querySelector('#setting-limit'); + if (minScoreEl) minScoreEl.value = String(DEFAULTS.min_score); + if (limitEl) limitEl.value = String(DEFAULTS.limit); + } + }; + this.showPanel = (panelId) => { const panels = this.querySelectorAll('.panel'); panels.forEach(p => p.classList.add('hidden')); @@ -56,6 +128,7 @@ break; case 'settings': this.querySelector('#panel-settings').classList.remove('hidden'); + loadSettingsIntoForm(); break; default: this.querySelector('#panel-search').classList.remove('hidden'); @@ -64,6 +137,28 @@ // default show search this.showPanel('search'); + const saveBtn = this.querySelector('#settings-save-btn'); + const feedbackEl = this.querySelector('#settings-feedback'); + if (saveBtn && feedbackEl) { + saveBtn.addEventListener('click', () => { + const minScoreEl = this.querySelector('#setting-min-score'); + const limitEl = this.querySelector('#setting-limit'); + const min_score = Math.max(0, Math.min(1, parseFloat(minScoreEl?.value) || DEFAULTS.min_score)); + const limit = Math.max(1, Math.min(100, parseInt(limitEl?.value, 10) || DEFAULTS.limit)); + try { + localStorage.setItem(SETTINGS_KEY, JSON.stringify({ min_score, limit })); + if (minScoreEl) minScoreEl.value = String(min_score); + if (limitEl) limitEl.value = String(limit); + feedbackEl.textContent = '保存しました'; + feedbackEl.classList.remove('error'); + setTimeout(() => { feedbackEl.textContent = ''; }, 2000); + } catch (e) { + feedbackEl.textContent = '保存に失敗しました'; + feedbackEl.classList.add('error'); + } + }); + } + // Activity Log accordion behavior const activityToggle = this.querySelector('.activity-toggle'); const activityContent = this.querySelector('.activity-content'); @@ -80,6 +175,244 @@ } }); } + + // --- 文書管理パネル --- + const API_BASE = typeof window !== 'undefined' && window.API_BASE ? window.API_BASE : 'http://127.0.0.1:3001'; + + const callMcp = async (method, params = {}) => { + const res = await fetch(`${API_BASE}/messages`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + method, + params, + id: Date.now(), + }), + }); + const data = await res.json(); + if (data.error) throw new Error(data.error.message || JSON.stringify(data.error)); + return data.result; + }; + + const parseResultText = (result) => { + const content = result?.content; + if (!content || !Array.isArray(content) || content.length === 0) return null; + if (content[0].type === 'text' && content[0].text) { + try { return JSON.parse(content[0].text); } catch (_) { return content[0].text; } + } + return null; + }; + + let editingDocumentId = null; + let docsEditorInstance = null; + + const docsListEl = this.querySelector('#docs-list'); + const docsEditModal = this.querySelector('#docs-edit-modal'); + const docsFormArea = this.querySelector('#docs-form-area'); + const docsPathEl = this.querySelector('#docs-path'); + const docsEditorContainer = this.querySelector('#docs-editor-container'); + const docsFormLegend = this.querySelector('#docs-form-legend'); + const docsFormFeedback = this.querySelector('#docs-form-feedback'); + const docsFileInput = this.querySelector('#docs-file-input'); + const docsImportFileBtn = this.querySelector('#docs-import-file-btn'); + const docsImportFeedback = this.querySelector('#docs-import-feedback'); + + const loadDocsList = async () => { + if (!docsListEl) return; + docsListEl.innerHTML = '
一覧を読み込み中...
'; + try { + const result = await callMcp('list_documents', {}); + const list = parseResultText(result); + if (!Array.isArray(list)) { + docsListEl.innerHTML = '
一覧の取得に失敗しました
'; + return; + } + if (list.length === 0) { + docsListEl.innerHTML = '
登録された文書はありません
'; + return; + } + docsListEl.innerHTML = ` + + + + + + ${list.map(d => ` + + + + + + + `).join('')} + +
パスMIMEチャンク数操作
${escapeHtml(d.path || '')}${escapeHtml(d.mime || '')}${Number(d.chunk_count) || 0} + + +
+ `; + docsListEl.querySelectorAll('.docs-btn-edit').forEach(btn => { + btn.addEventListener('click', () => openDocEdit(btn.dataset.id)); + }); + docsListEl.querySelectorAll('.docs-btn-delete').forEach(btn => { + btn.addEventListener('click', () => deleteDoc(btn.dataset.id)); + }); + } catch (e) { + docsListEl.innerHTML = `
エラー: ${escapeHtml(e.message)}
`; + } + }; + + function escapeHtml(s) { + const div = document.createElement('div'); + div.textContent = s; + return div.innerHTML; + } + + const destroyDocsEditor = () => { + if (docsEditorInstance) { + try { docsEditorInstance.destroy(); } catch (_) {} + docsEditorInstance = null; + } + }; + + const createDocsEditor = (initialValue = '') => { + if (!docsEditorContainer) return; + destroyDocsEditor(); + if (typeof toastui === 'undefined' || !toastui.Editor) { + docsEditorContainer.innerHTML = '

Toast UI Editor を読み込んでください。npm run build-editor を実行し、vendor/toast-ui/ にバンドルを生成してください。

'; + return; + } + docsEditorInstance = new toastui.Editor({ + el: docsEditorContainer, + initialValue: initialValue || '', + height: '400px', + initialEditType: 'wysiwyg', + previewStyle: 'tab', + usageStatistics: false, + theme: 'dark', + }); + }; + + const getDocsEditorMarkdown = () => docsEditorInstance ? docsEditorInstance.getMarkdown() : ''; + const setDocsEditorMarkdown = (markdown) => { if (docsEditorInstance) docsEditorInstance.setMarkdown(markdown || ''); }; + + const showDocsForm = (legend, path = '', content = '', documentId = null) => { + editingDocumentId = documentId; + if (docsFormLegend) docsFormLegend.textContent = legend; + if (docsPathEl) docsPathEl.value = path; + if (docsFormFeedback) { docsFormFeedback.textContent = ''; docsFormFeedback.classList.remove('error'); } + if (docsEditModal) docsEditModal.classList.remove('hidden'); + createDocsEditor(content); + }; + + const hideDocsForm = () => { + destroyDocsEditor(); + editingDocumentId = null; + if (docsEditModal) docsEditModal.classList.add('hidden'); + loadDocsList(); + }; + + const openDocEdit = async (id) => { + const docId = typeof id === 'string' ? parseInt(id, 10) : id; + if (!Number.isFinite(docId)) { + if (docsFormFeedback) docsFormFeedback.textContent = '無効な文書IDです'; + return; + } + showDocsForm('編集', '', '読み込み中...', docId); + try { + const result = await callMcp('get_document', { document_id: docId }); + const doc = parseResultText(result); + if (doc && typeof doc === 'object') { + if (docsPathEl) docsPathEl.value = doc.path || ''; + setDocsEditorMarkdown(doc.content != null ? String(doc.content) : ''); + if (docsFormFeedback) { docsFormFeedback.textContent = ''; docsFormFeedback.classList.remove('error'); } + } else { + if (docsFormFeedback) docsFormFeedback.textContent = '文書の取得に失敗しました'; + } + } catch (e) { + if (docsFormFeedback) docsFormFeedback.textContent = 'エラー: ' + e.message; + if (docsFormFeedback) docsFormFeedback.classList.add('error'); + } + }; + + const deleteDoc = async (id) => { + const docId = typeof id === 'string' ? parseInt(id, 10) : id; + if (!Number.isFinite(docId)) { + alert('無効な文書IDです'); + return; + } + if (!confirm('この文書を削除しますか?')) return; + try { + await callMcp('delete_document', { document_id: docId }); + loadDocsList(); + if (typeof window !== 'undefined' && window.updateDocCount) window.updateDocCount(); + } catch (e) { + alert('削除に失敗しました: ' + e.message); + } + }; + + this.querySelector('#docs-add-btn')?.addEventListener('click', () => { + showDocsForm('新規登録', '', ''); + }); + this.querySelector('#docs-refresh-btn')?.addEventListener('click', () => { + loadDocsList(); + }); + this.querySelector('#docs-save-btn')?.addEventListener('click', async () => { + const path = docsPathEl?.value?.trim(); + const content = getDocsEditorMarkdown(); + if (!path) { + if (docsFormFeedback) { docsFormFeedback.textContent = 'パスを入力してください'; docsFormFeedback.classList.add('error'); } + return; + } + if (docsFormFeedback) { docsFormFeedback.textContent = '保存中...'; docsFormFeedback.classList.remove('error'); } + try { + if (editingDocumentId) { + await callMcp('delete_document', { document_id: editingDocumentId }); + } + await callMcp('add_item_text', { path, content }); + if (docsFormFeedback) { docsFormFeedback.textContent = '保存しました'; docsFormFeedback.classList.remove('error'); } + setTimeout(() => hideDocsForm(), 600); + if (typeof window !== 'undefined' && window.updateDocCount) window.updateDocCount(); + } catch (e) { + if (docsFormFeedback) { docsFormFeedback.textContent = 'エラー: ' + e.message; docsFormFeedback.classList.add('error'); } + } + }); + this.querySelector('#docs-cancel-btn')?.addEventListener('click', hideDocsForm); + docsFileInput?.addEventListener('change', () => { + const file = docsFileInput.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + const text = typeof reader.result === 'string' ? reader.result : ''; + if (!docsPathEl?.value?.trim()) docsPathEl.value = file.name; + setDocsEditorMarkdown(text); + if (docsImportFeedback) { + docsImportFeedback.textContent = `「${escapeHtml(file.name)}」を読み込みました`; + docsImportFeedback.classList.remove('error'); + setTimeout(() => { docsImportFeedback.textContent = ''; }, 3000); + } + docsFileInput.value = ''; + }; + reader.onerror = () => { + if (docsImportFeedback) { + docsImportFeedback.textContent = 'ファイルの読み込みに失敗しました'; + docsImportFeedback.classList.add('error'); + } + docsFileInput.value = ''; + }; + reader.readAsText(file, 'UTF-8'); + }); + docsImportFileBtn?.addEventListener('click', () => docsFileInput?.click()); + + const backdrop = this.querySelector('.docs-edit-modal-backdrop'); + backdrop?.addEventListener('click', hideDocsForm); + + const originalShowPanel = this.showPanel; + this.showPanel = (panelId) => { + originalShowPanel.call(this, panelId); + if (panelId === 'docs') loadDocsList(); + }; } } diff --git a/src/frontend/index.html b/src/frontend/index.html index 4bac050..574f84c 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -11,6 +11,7 @@ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&family=Outfit:wght@400;700;800&display=swap" rel="stylesheet"> + @@ -43,6 +44,7 @@ +