diff --git a/README.md b/README.md index 2dae2f8..5ca1092 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TelosDB -**Version 0.3.3** — ローカル完結型セマンティック検索基盤 & MCPサーバー +**Version 0.3.3.1** — ローカル完結型セマンティック検索基盤 & MCPサーバー --- @@ -26,8 +26,8 @@ ## 主な機能 -- **セマンティック検索**: キーワードや自然文で意味に基づいた検索。文書単位で結合して返すオプションあり。 -- **MCP SSE サーバー内蔵**: AI クライアント(Cursor 等)からツールとして検索・CRUDが可能。 +- **セマンティック検索**: キーワードや自然文で意味に基づいた検索。文書単位で結合して返すオプションあり。空クエリ・0 件時も統一形式で応答。 +- **MCP SSE サーバー内蔵**: AI クライアント(Cursor 等)からツールとして検索・一覧取得(ページング)・カテゴリ一覧・CRUD が可能。 - **自動ヒーリング**: DB 内のテキストとベクトルの不一致を検出し、バックグラウンドで同期。 - **完全ローカル・プライバシー**: 計算・ベクトル空間はすべてローカルで完結。GPU 不要。 - **システムトレイ常駐**: MCP サーバーをバックグラウンドで待機。 @@ -49,7 +49,7 @@ } ``` -接続後、`search_text`(検索)、`add_item_text`(追加)、`update_item`(更新)、`lsa_retrain`(RE-INDEX)などのツールが利用できます。 +接続後、`search_text`(検索)、`list_documents`(文書一覧・ページング)、`list_categories`(カテゴリ一覧)、`add_item_text`(追加)、`update_item`(更新)、`lsa_retrain`(RE-INDEX)などのツールが利用できます。 --- diff --git a/RELEASE_v0.3.0.md b/RELEASE_v0.3.0.md deleted file mode 100644 index 2e343a9..0000000 --- a/RELEASE_v0.3.0.md +++ /dev/null @@ -1,61 +0,0 @@ -# TelosDB v0.3.0 Release Notes - -TelosDB v0.3.0 へようこそ! -今回のリリースでは、データベース構造の抜本的な正規化、検索精度の向上、そして「MIMEタイプ対応」による将来的なファイル形式拡張への基盤を構築しました。 -加えて、配布パッケージ(NSIS インストーラ)の安定化と UI の改善を行いました。 - -## 🚀 新機能 (Major Features) - -### 1. MIMEタイプ対応 (MIME Type Support) - -- `documents` テーブルに `mime` カラムを追加し、ファイル形式を自動的に記録するようにしました。 -- ファイル拡張子(.md, .rs, .js 等)から適切な MIME タイプを自動判別します。 -- これにより、将来的な PDF や画像、多言語ソースコードへの対応が容易になりました。 - -### 2. データベース構造の正規化 (DB Normalization - Issue-2) - -- ドキュメントメタデータ(パス、MIME等)を管理する `documents` テーブルを `items` テーブル(チャンク)から分離しました。 -- 1ドキュメントに対して複数のチャンクを紐付ける 1:N 構造を確立し、データの冗長性を排除。 -- ドキュメント単位の一括削除や管理が可能になりました。 - -### 3. セマンティック検索の精度向上 (Search Accuracy) - -- LSA(潜在意味解析)の類似度計算ロジックを改善。 -- ベクトルの正規化を考慮し、L2距離をコサイン類似度へ正確に変換する計算式 `1.0 - (distance / 2.0)` を採用しました。 -- SVD(特異値分解)の安定性を向上させ、より一貫性のある検索結果を実現。 - -### 4. 日本語解析エンジンの刷新 (Vibrato Integration) - -- 日本語形態素解析エンジンを `Lindera` から `Vibrato` へ移行。 -- 辞書の整合性と分かち書きの精度が向上し、より自然な検索が可能になりました。 - ---- - -## 🛠 改善・修正 (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)。 -- **HNSWインデックス**: 大規模データ向けの高速近傍探索インデックス HNSW を統合し、検索速度を維持。 - ---- - -## 📦 インストール方法 - -添付の `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/RELEASE_v0.3.2_Community.md b/RELEASE_v0.3.2_Community.md deleted file mode 100644 index 850f0cf..0000000 --- a/RELEASE_v0.3.2_Community.md +++ /dev/null @@ -1,71 +0,0 @@ -# TelosDB Community 版 v0.3.2 — 商品紹介 - -## そのまま使える、ローカル完結の意味検索 - -**TelosDB Community 版**は、追加のモデルや設定なしで、すぐに意味検索を始められるデスクトップアプリです。データはすべてお使いの PC 内に留まり、クラウドへ一切送信しません。 - ---- - -## こんな方におすすめ - -- まずは手軽に意味検索を試したい -- モデルのダウンロードや GPU 環境を用意したくない -- 個人のメモやドキュメントを「意味で」探したい -- Cursor や Claude Desktop など AI ツールと MCP で連携したい - ---- - -## Community 版の特長 - -### モデル不要で動く - -LSA(潜在意味解析)により、**50 次元のベクトル**で文書を表現します。大きな AI モデルは不要。インストール後、そのまま検索できます。 - -### キーワードも自然な文も、そのまま検索 - -「春の歌」でも「桜の季節に聴きたい曲」のような文でも検索可能。結果は**文書単位**でまとめて返すため、どのドキュメントがヒットしたかが分かりやすくなっています。 - -### MCP で AI とつながる - -Model Context Protocol(MCP)に対応。Cursor や Claude Desktop から「検索」「追加」「RE-INDEX」などのツールとして TelosDB を呼び出せます。AI エージェントの「記憶」として利用するのに最適です。 - -### 自動でインデックスを整える - -ドキュメントの増減に応じて、バックグラウンドで LSA を再学習。画面上では「LSA学習中…」「ベクトル同期中…」の表示で進行状況を確認できます。必要に応じて RE-INDEX で手動再構築も可能です。 - -### ログイン時に自動起動(オプション) - -設定で「OSにログインしたときに自動で起動する」をオンにすると、Windows にサインインしたタイミングで TelosDB が起動し、トレイで待機。いつでも MCP 経由で検索できます。 - ---- - -## 含まれる機能(v0.3.2) - -- 意味検索(LSA 50 次元)+ 全文検索(FTS5)のハイブリッド検索 -- 検索結果の文書単位表示(`group_by_document`) -- MCP ツール: `search_text` / `add_item_text` / `update_item` / `delete_item` / `get_item_by_id` / `get_document_count` / `lsa_retrain`(RE-INDEX) -- ドキュメント件数の表示(ヘッダー「X docs」・`get_document_count`) -- インデックス状態の表示(LSA学習中・ベクトル同期中) -- 文書一覧のプレビュー(先頭 15 文字表示) -- トレイ常駐・バージョン表示の改善 - ---- - -## ご利用方法 - -**インストーラ**: `TelosDB-Community_0.3.2_x64-setup.exe` を実行してインストールしてください。 - -**ソースからビルドする場合**: - -```bash -npm install -npm run build:community -``` - -- 出力: `src/backend/target/release/bundle/nsis/TelosDB-Community_0.3.2_x64-setup.exe` -- 初回起動時に vec0.dll がアプリデータフォルダへコピーされます。不具合時は `%APPDATA%\com.telosdb.app\logs\telos.log` をご確認ください。 - ---- - -より高精度な意味検索をご希望の方は **Pro 版**をご検討ください。 -→ [TelosDB Pro 版 商品紹介](RELEASE_v0.3.2_Pro.md) diff --git a/RELEASE_v0.3.2_Pro.md b/RELEASE_v0.3.2_Pro.md deleted file mode 100644 index 37b50e6..0000000 --- a/RELEASE_v0.3.2_Pro.md +++ /dev/null @@ -1,85 +0,0 @@ -# TelosDB Pro 版 v0.3.2 — 商品紹介 - -## 日本語に強い、高精度な意味検索 - -**TelosDB Pro 版**は、日本語向けの埋め込みモデル(768 次元)を**インストーラに同梱**したエディションです。インストールするだけで、言い回しの違いに強い高精度な意味検索が使えます。ビジネス文書や学術メモの検索に適し、データはすべてローカルで完結。クラウドへ送信しません。 - ---- - -## こんな方におすすめ - -- 検索精度をより高めたい -- 「似た意味の文」でしっかりヒットさせたい -- 報告書・論文・仕様書などフォーマルな日本語を扱う -- Cursor や Claude Desktop と MCP で連携し、高品質な検索結果を AI に渡したい - ---- - -## Pro 版の特長 - -### 日本語特化の埋め込みで高精度 - -**sentence-BERT 系の日本語モデル**(768 次元)で文書をベクトル化。キーワードの一致だけでなく、「言い換え」や「関連する表現」にも強く、自然な文で検索できます。結果は**文書単位**で返るため、どのドキュメントが該当したかが一目で分かります。 - -### 量子化モデルで軽く・速く(インストーラに同梱) - -推論用に **INT8 量子化した ONNX モデル**を使用。GPU は不要で、一般的な PC の CPU だけで動作します。**モデルはインストーラに含まれており、別途ダウンロードは不要**。インストール後すぐに高精度検索をご利用いただけます。 - -### MCP で AI とつながる - -Model Context Protocol(MCP)に対応。Cursor や Claude Desktop から「検索」「追加」「RE-INDEX」などのツールとして TelosDB を呼び出せます。高精度な検索結果を AI のコンテキストとしてそのまま利用できます。 - -### インデックス状態が分かる - -起動時や RE-INDEX 時に、HNSW の構築状況などが画面上で確認できます。「Pro」バッジでエディションが識別でき、モデルの読み込み状態も把握しやすくなっています。 - -### ログイン時に自動起動(オプション) - -設定で「OSにログインしたときに自動で起動する」をオンにすると、Windows にサインインしたタイミングで TelosDB が起動。トレイで待機し、いつでも MCP 経由で高精度検索を利用できます。 - ---- - -## インストーラにモデル同梱(v0.3.2) - -Pro 版のインストーラ(`TelosDB-Pro_0.3.2_x64-setup.exe`)には、**埋め込み用の量子化 ONNX モデル**が同梱されています。 - -- **利用者**: 別途モデルのダウンロードは不要。インストールするだけで、インストール先のリソースから自動的にモデルが参照され、高精度な意味検索が利用できます。 -- **同梱内容**: `model_quantized.onnx`(日本語 sentence-BERT 量子化モデル)・`vocab.txt`(語彙)。インストール後にアプリが正しいパスでこれらを参照するよう、リソース解決を修正済みです。 -- モデルが読み込めない場合でも、全文検索(FTS)のみで検索は可能です。`TELOS_EMBEDDING_NO_OPTIMIZE=1` で最適化をスキップして読み込む方法も README・仕様書で案内しています。 - ---- - -## 含まれる機能(v0.3.2) - -- **埋め込みモデルのインストーラ同梱**(別途ダウンロード不要。インストール先から自動参照) -- 意味検索(埋め込み 768 次元)+ 全文検索(FTS5)のハイブリッド検索 -- 検索結果の文書単位表示(`group_by_document`) -- MCP ツール: `search_text` / `add_item_text` / `update_item` / `delete_item` / `get_item_by_id` / `get_document_count` / `lsa_retrain`(RE-INDEX) -- ドキュメント件数の表示(ヘッダー「X docs」・`get_document_count`) -- インデックス状態の表示(ベクトル同期中・HNSW 構築状況) -- 文書一覧のプレビュー(先頭 15 文字表示) -- トレイ常駐・バージョン表示の改善 - ---- - -## ご利用方法 - -**インストーラ**: `TelosDB-Pro_0.3.2_x64-setup.exe` を実行してインストールしてください。埋め込みモデルはインストーラに含まれており、追加のダウンロードは不要です。 - -**ソースからビルドする場合**(配布用インストーラを作る開発者向け): - -1. [sentence-bert-base-ja-mean-tokens-v2-int8](https://gitbucket.tmworks.club/dtmoyaji/sentence-bert-base-ja-mean-tokens-v2-int8) から `target/` 一式(`model_quantized.onnx`・`vocab.txt`)を取得し、プロジェクトの **`embedding_model/`** に配置します。 -2. `npm run build:pro` を実行すると、それらがインストーラに同梱された Pro 版がビルドされます。 - -```bash -npm install -npm run build:pro -``` - -- 出力: `src/backend/target/release/bundle/nsis/TelosDB-Pro_0.3.2_x64-setup.exe`(モデル同梱) -- 初回起動時に vec0.dll がアプリデータフォルダへコピーされます。不具合時は `%APPDATA%\com.telosdb.app\logs\telos.log` をご確認ください。 - ---- - -モデル不要で手軽に始めたい方は **Community 版**をご利用ください。 -→ [TelosDB Community 版 商品紹介](RELEASE_v0.3.2_Community.md) diff --git a/RELEASE_v0.3.3.1_Community.md b/RELEASE_v0.3.3.1_Community.md new file mode 100644 index 0000000..853f7d4 --- /dev/null +++ b/RELEASE_v0.3.3.1_Community.md @@ -0,0 +1,95 @@ +# TelosDB Community 版 v0.3.3.1 — 商品紹介 + +## そのまま使える、ローカル完結の意味検索 + +**TelosDB Community 版**は、追加のモデルや設定なしで、すぐに意味検索を始められるデスクトップアプリです。データはすべてお使いの PC 内に留まり、クラウドへ一切送信しません。 + +--- + +## こんな方におすすめ + +- まずは手軽に意味検索を試したい +- モデルのダウンロードや GPU 環境を用意したくない +- 個人のメモやドキュメントを「意味で」探したい +- Cursor や Claude Desktop など AI ツールと MCP で連携したい + +--- + +## Community 版の特長 + +### モデル不要で動く + +LSA(潜在意味解析)により、**50 次元のベクトル**で文書を表現します。大きな AI モデルは不要。インストール後、そのまま検索できます。 + +### キーワードも自然な文も、そのまま検索 + +「春の歌」でも「桜の季節に聴きたい曲」のような文でも検索可能。結果は**文書単位**でまとめて返すため、どのドキュメントがヒットしたかが分かりやすくなっています。検索結果 0 件や空クエリでもエラーにならず、統一された形式で返します。 + +### MCP で AI とつながる + +Model Context Protocol(MCP)に対応。Cursor や Claude Desktop から「検索」「一覧取得」「カテゴリ一覧」「RE-INDEX」などのツールとして TelosDB を呼び出せます。AI エージェントの「記憶」として利用するのに最適です。 + +### 文書一覧のページング + +文書一覧は **1 ページ 20 件**で区切り、前へ・次へでページ移動できます。大量登録時も UI が重くなりにくい設計です。 + +### 自動でインデックスを整える + +ドキュメントの増減に応じて、バックグラウンドで LSA を再学習。画面上では「LSA学習中…」「ベクトル同期中…」の表示で進行状況を確認できます。必要に応じて RE-INDEX で手動再構築も可能です。 + +### ログイン時に自動起動(オプション) + +設定で「OSにログインしたときに自動で起動する」をオンにすると、Windows にサインインしたタイミングで TelosDB が起動し、トレイで待機。いつでも MCP 経由で検索できます。 + +### フォルダ監視で自動取り込み + +指定したフォルダを常時監視し、ファイルの追加・更新・削除を自動検知してインデックスに反映します。テキストやマークダウンを保存するだけで、検索対象に自動登録。フォルダごとにカテゴリ名を付けられ、MCP の `list_categories` で取得して検索時の絞り込みに利用できます。 + +--- + +## v0.3.3.1 の主な変更点 + +### 新機能 + +- **list_categories**: MCP で登録済みカテゴリ名の一覧を取得。`search_text` の `category` パラメータと組み合わせて絞り込み検索が可能。 +- **文書一覧のページング(UI)**: 文書管理パネルで「前へ」「次へ」によるページ切り替え。1 ページあたり 20 件表示。 + +### 改善 + +- **list_documents のページング**: MCP の `list_documents` が `limit`・`page` に対応。戻り値に `items`・`total_count`・`total_pages`・`page` を含み、全件取得による負荷を回避。 +- **検索エラー時の対応(Issue #11)**: 空クエリやマッチ 0 件のときもエラーにせず、`items: []` と `vector_search_used` を含む統一 JSON 形式で返す。クライアントは常に配列として扱える。 + +--- + +## 含まれる機能(v0.3.3.1) + +- 意味検索(LSA 50 次元)+ 全文検索(FTS5)のハイブリッド検索 +- 検索結果の文書単位表示(`group_by_document`)。0 件・空クエリ時も統一形式で応答 +- **フォルダ監視**: 指定フォルダの自動取り込み・カテゴリ指定・拡張子フィルタ +- MCP ツール: `search_text` / `add_item_text` / `update_item` / `delete_item` / `get_item_by_id` / **`list_documents`(ページング)** / **`list_categories`** / `get_document_count` / `get_document` / `delete_document` / `lsa_retrain`(RE-INDEX) +- ドキュメント件数の表示(ヘッダー「X docs」・`get_document_count`) +- **文書一覧 UI のページング**(前へ・次へ、20 件/ページ) +- インデックス状態の表示(LSA学習中・ベクトル同期中) +- ログイン時の自動起動(オプション) +- トレイ常駐・バージョン表示 + +--- + +## ご利用方法 + +**インストーラ**: `TelosDB-Community_0.3.3.1_x64-setup.exe` を実行してインストールしてください。 + +**ソースからビルドする場合**: + +```bash +npm install +npm run build:community +``` + +- 出力: `target/release/bundle/nsis/TelosDB-Community_0.3.3.1_x64-setup.exe` +- 初回起動時に vec0.dll がアプリデータフォルダへコピーされます。不具合時は `%APPDATA%\com.telosdb.app\logs\telos.log` をご確認ください。 + +--- + +より高精度な意味検索をご希望の方は **Pro 版**をご検討ください。 +→ [TelosDB Pro 版 商品紹介](RELEASE_v0.3.3.1_Pro.md) diff --git a/RELEASE_v0.3.3.1_Pro.md b/RELEASE_v0.3.3.1_Pro.md new file mode 100644 index 0000000..b5eced7 --- /dev/null +++ b/RELEASE_v0.3.3.1_Pro.md @@ -0,0 +1,109 @@ +# TelosDB Pro 版 v0.3.3.1 — 商品紹介 + +## 日本語に強い、高精度な意味検索 + +**TelosDB Pro 版**は、日本語向けの埋め込みモデル(768 次元)を**インストーラに同梱**したエディションです。インストールするだけで、言い回しの違いに強い高精度な意味検索が使えます。ビジネス文書や学術メモの検索に適し、データはすべてローカルで完結。クラウドへ送信しません。 + +--- + +## こんな方におすすめ + +- 検索精度をより高めたい +- 「似た意味の文」でしっかりヒットさせたい +- 報告書・論文・仕様書などフォーマルな日本語を扱う +- Cursor や Claude Desktop と MCP で連携し、高品質な検索結果を AI に渡したい + +--- + +## Pro 版の特長 + +### 日本語特化の埋め込みで高精度 + +**sentence-BERT 系の日本語モデル**(768 次元)で文書をベクトル化。キーワードの一致だけでなく、「言い換え」や「関連する表現」にも強く、自然な文で検索できます。結果は**文書単位**で返るため、どのドキュメントが該当したかが一目で分かります。検索結果 0 件や空クエリでもエラーにならず、統一された形式で返します。 + +### 量子化モデルで軽く・速く(インストーラに同梱) + +推論用に **INT8 量子化した ONNX モデル**を使用。GPU は不要で、一般的な PC の CPU だけで動作します。**モデルはインストーラに含まれており、別途ダウンロードは不要**。インストール後すぐに高精度検索をご利用いただけます。 + +### MCP で AI とつながる + +Model Context Protocol(MCP)に対応。Cursor や Claude Desktop から「検索」「一覧取得」「カテゴリ一覧」「RE-INDEX」などのツールとして TelosDB を呼び出せます。高精度な検索結果を AI のコンテキストとしてそのまま利用できます。 + +### 文書一覧のページング + +文書一覧は **1 ページ 20 件**で区切り、前へ・次へでページ移動できます。大量登録時も UI が重くなりにくい設計です。 + +### インデックス状態が分かる + +起動時や RE-INDEX 時に、HNSW の構築状況などが画面上で確認できます。「Pro」バッジでエディションが識別でき、モデルの読み込み状態も把握しやすくなっています。 + +### ログイン時に自動起動(オプション) + +設定で「OSにログインしたときに自動で起動する」をオンにすると、Windows にサインインしたタイミングで TelosDB が起動。トレイで待機し、いつでも MCP 経由で高精度検索を利用できます。 + +### フォルダ監視で自動取り込み + +指定したフォルダを常時監視し、ファイルの追加・更新・削除を自動検知してインデックスに反映します。テキストやマークダウンを保存するだけで、高精度な意味検索の対象に自動登録。フォルダごとにカテゴリ名を付けられ、MCP の `list_categories` で取得して検索時の絞り込みに利用できます。 + +--- + +## v0.3.3.1 の主な変更点 + +### 新機能 + +- **list_categories**: MCP で登録済みカテゴリ名の一覧を取得。`search_text` の `category` パラメータと組み合わせて絞り込み検索が可能。 +- **文書一覧のページング(UI)**: 文書管理パネルで「前へ」「次へ」によるページ切り替え。1 ページあたり 20 件表示。 + +### 改善 + +- **list_documents のページング**: MCP の `list_documents` が `limit`・`page` に対応。戻り値に `items`・`total_count`・`total_pages`・`page` を含み、全件取得による負荷を回避。 +- **検索エラー時の対応(Issue #11)**: 空クエリやマッチ 0 件のときもエラーにせず、`items: []` と `vector_search_used` を含む統一 JSON 形式で返す。クライアントは常に配列として扱える。 + +--- + +## インストーラにモデル同梱 + +Pro 版のインストーラ(`TelosDB-Pro_0.3.3.1_x64-setup.exe`)には、**埋め込み用の量子化 ONNX モデル**が同梱されています。 + +- **利用者**: 別途モデルのダウンロードは不要。インストールするだけで、インストール先のリソースから自動的にモデルが参照され、高精度な意味検索が利用できます。 +- **同梱内容**: `model_quantized.onnx`(日本語 sentence-BERT 量子化モデル)・`vocab.txt`(語彙)。 +- モデルが読み込めない場合でも、全文検索(FTS)のみで検索は可能です。 + +--- + +## 含まれる機能(v0.3.3.1) + +- **埋め込みモデルのインストーラ同梱**(別途ダウンロード不要。インストール先から自動参照) +- 意味検索(埋め込み 768 次元)+ 全文検索(FTS5)のハイブリッド検索 +- 検索結果の文書単位表示(`group_by_document`)。0 件・空クエリ時も統一形式で応答 +- **フォルダ監視**: 指定フォルダの自動取り込み・カテゴリ指定・拡張子フィルタ +- MCP ツール: `search_text` / `add_item_text` / `update_item` / `delete_item` / `get_item_by_id` / **`list_documents`(ページング)** / **`list_categories`** / `get_document_count` / `get_document` / `delete_document` / `lsa_retrain`(RE-INDEX) +- ドキュメント件数の表示(ヘッダー「X docs」・`get_document_count`) +- **文書一覧 UI のページング**(前へ・次へ、20 件/ページ) +- インデックス状態の表示(ベクトル同期中・HNSW 構築状況) +- ログイン時の自動起動(オプション) +- トレイ常駐・バージョン表示 + +--- + +## ご利用方法 + +**インストーラ**: `TelosDB-Pro_0.3.3.1_x64-setup.exe` を実行してインストールしてください。埋め込みモデルはインストーラに含まれており、追加のダウンロードは不要です。 + +**ソースからビルドする場合**(配布用インストーラを作る開発者向け): + +1. [sentence-bert-base-ja-mean-tokens-v2-int8](https://gitbucket.tmworks.club/dtmoyaji/sentence-bert-base-ja-mean-tokens-v2-int8) から `target/` 一式(`model_quantized.onnx`・`vocab.txt`)を取得し、プロジェクトの **`embedding_model/`** に配置します。 +2. `npm run build:pro` を実行すると、それらがインストーラに同梱された Pro 版がビルドされます。 + +```bash +npm install +npm run build:pro +``` + +- 出力: `target/release/bundle/nsis/TelosDB-Pro_0.3.3.1_x64-setup.exe`(モデル同梱) +- 初回起動時に vec0.dll がアプリデータフォルダへコピーされます。不具合時は `%APPDATA%\com.telosdb.app\logs\telos.log` をご確認ください。 + +--- + +モデル不要で手軽に始めたい方は **Community 版**をご利用ください。 +→ [TelosDB Community 版 商品紹介](RELEASE_v0.3.3.1_Community.md) diff --git a/RELEASE_v0.3.3_Community.md b/RELEASE_v0.3.3_Community.md deleted file mode 100644 index 6192eb3..0000000 --- a/RELEASE_v0.3.3_Community.md +++ /dev/null @@ -1,93 +0,0 @@ -# TelosDB Community 版 v0.3.3 — 商品紹介 - -## そのまま使える、ローカル完結の意味検索 - -**TelosDB Community 版**は、追加のモデルや設定なしで、すぐに意味検索を始められるデスクトップアプリです。データはすべてお使いの PC 内に留まり、クラウドへ一切送信しません。 - ---- - -## こんな方におすすめ - -- まずは手軽に意味検索を試したい -- モデルのダウンロードや GPU 環境を用意したくない -- 個人のメモやドキュメントを「意味で」探したい -- Cursor や Claude Desktop など AI ツールと MCP で連携したい - ---- - -## Community 版の特長 - -### モデル不要で動く - -LSA(潜在意味解析)により、**50 次元のベクトル**で文書を表現します。大きな AI モデルは不要。インストール後、そのまま検索できます。 - -### キーワードも自然な文も、そのまま検索 - -「春の歌」でも「桜の季節に聴きたい曲」のような文でも検索可能。結果は**文書単位**でまとめて返すため、どのドキュメントがヒットしたかが分かりやすくなっています。 - -### MCP で AI とつながる - -Model Context Protocol(MCP)に対応。Cursor や Claude Desktop から「検索」「追加」「RE-INDEX」などのツールとして TelosDB を呼び出せます。AI エージェントの「記憶」として利用するのに最適です。 - -### 自動でインデックスを整える - -ドキュメントの増減に応じて、バックグラウンドで LSA を再学習。画面上では「LSA学習中…」「ベクトル同期中…」の表示で進行状況を確認できます。必要に応じて RE-INDEX で手動再構築も可能です。 - -### ログイン時に自動起動(オプション) - -設定で「OSにログインしたときに自動で起動する」をオンにすると、Windows にサインインしたタイミングで TelosDB が起動し、トレイで待機。いつでも MCP 経由で検索できます。 - -### フォルダ監視で自動取り込み(New) - -指定したフォルダを常時監視し、ファイルの追加・更新・削除を自動検知してインデックスに反映します。テキストやマークダウンを保存するだけで、検索対象に自動登録。フォルダごとにカテゴリ名を付けられるため、検索時の分類にも活用できます。 - ---- - -## v0.3.3 の主な変更点 - -### 新機能 - -- **フォルダ監視**: 指定フォルダ内のファイル追加・更新・削除を自動検知し、インデックスに反映。設定パネルからフォルダの追加・削除・カテゴリ指定が可能。取込対象の拡張子もカスタマイズ可能(デフォルト: txt, md, json, html, css, js, mjs, ts, rs)。 -- **接続状態表示**: MCP サーバーへの接続状態を UI に表示。 - -### 改善 - -- **自動起動の安定化**: 設定ファイル読み込み失敗時にレジストリエントリが消える問題を修正。`isEnabled()` で OS の実状態と比較し、不一致時のみ登録を更新するように改善。失敗時のエラー表示を追加。 -- **バックエンドリファクタリング**: MCP ツールの登録を `registry.rs` に集約し、検索ロジックを `search.rs` に整理。DB アクセス関数を `db/mod.rs` に追加。 -- **DB マイグレーション**: ドキュメントパスの正規化マイグレーションを追加(テーブル未存在時はスキップ)。 -- **バージョン表示の安定化**: フッターのバージョン取得でサーバー未応答時のビジーウェイトとフォールバック未設定の問題を修正。 -- **E2E テスト**: 自動起動・フォルダ監視・パネル操作・文書編集のテストを追加。 - ---- - -## 含まれる機能(v0.3.3) - -- 意味検索(LSA 50 次元)+ 全文検索(FTS5)のハイブリッド検索 -- 検索結果の文書単位表示(`group_by_document`) -- **フォルダ監視**: 指定フォルダの自動取り込み・カテゴリ指定・拡張子フィルタ -- MCP ツール: `search_text` / `add_item_text` / `update_item` / `delete_item` / `get_item_by_id` / `get_document_count` / `lsa_retrain`(RE-INDEX) -- ドキュメント件数の表示(ヘッダー「X docs」・`get_document_count`) -- インデックス状態の表示(LSA学習中・ベクトル同期中) -- ログイン時の自動起動(オプション) -- トレイ常駐・バージョン表示 - ---- - -## ご利用方法 - -**インストーラ**: `TelosDB-Community_0.3.3_x64-setup.exe` を実行してインストールしてください。 - -**ソースからビルドする場合**: - -```bash -npm install -npm run build:community -``` - -- 出力: `src/backend/target/release/bundle/nsis/TelosDB-Community_0.3.3_x64-setup.exe` -- 初回起動時に vec0.dll がアプリデータフォルダへコピーされます。不具合時は `%APPDATA%\com.telosdb.app\logs\telos.log` をご確認ください。 - ---- - -より高精度な意味検索をご希望の方は **Pro 版**をご検討ください。 -→ [TelosDB Pro 版 商品紹介](RELEASE_v0.3.3_Pro.md) diff --git a/RELEASE_v0.3.3_Pro.md b/RELEASE_v0.3.3_Pro.md deleted file mode 100644 index 686a66a..0000000 --- a/RELEASE_v0.3.3_Pro.md +++ /dev/null @@ -1,107 +0,0 @@ -# TelosDB Pro 版 v0.3.3 — 商品紹介 - -## 日本語に強い、高精度な意味検索 - -**TelosDB Pro 版**は、日本語向けの埋め込みモデル(768 次元)を**インストーラに同梱**したエディションです。インストールするだけで、言い回しの違いに強い高精度な意味検索が使えます。ビジネス文書や学術メモの検索に適し、データはすべてローカルで完結。クラウドへ送信しません。 - ---- - -## こんな方におすすめ - -- 検索精度をより高めたい -- 「似た意味の文」でしっかりヒットさせたい -- 報告書・論文・仕様書などフォーマルな日本語を扱う -- Cursor や Claude Desktop と MCP で連携し、高品質な検索結果を AI に渡したい - ---- - -## Pro 版の特長 - -### 日本語特化の埋め込みで高精度 - -**sentence-BERT 系の日本語モデル**(768 次元)で文書をベクトル化。キーワードの一致だけでなく、「言い換え」や「関連する表現」にも強く、自然な文で検索できます。結果は**文書単位**で返るため、どのドキュメントが該当したかが一目で分かります。 - -### 量子化モデルで軽く・速く(インストーラに同梱) - -推論用に **INT8 量子化した ONNX モデル**を使用。GPU は不要で、一般的な PC の CPU だけで動作します。**モデルはインストーラに含まれており、別途ダウンロードは不要**。インストール後すぐに高精度検索をご利用いただけます。 - -### MCP で AI とつながる - -Model Context Protocol(MCP)に対応。Cursor や Claude Desktop から「検索」「追加」「RE-INDEX」などのツールとして TelosDB を呼び出せます。高精度な検索結果を AI のコンテキストとしてそのまま利用できます。 - -### インデックス状態が分かる - -起動時や RE-INDEX 時に、HNSW の構築状況などが画面上で確認できます。「Pro」バッジでエディションが識別でき、モデルの読み込み状態も把握しやすくなっています。 - -### ログイン時に自動起動(オプション) - -設定で「OSにログインしたときに自動で起動する」をオンにすると、Windows にサインインしたタイミングで TelosDB が起動。トレイで待機し、いつでも MCP 経由で高精度検索を利用できます。 - -### フォルダ監視で自動取り込み(New) - -指定したフォルダを常時監視し、ファイルの追加・更新・削除を自動検知してインデックスに反映します。テキストやマークダウンを保存するだけで、高精度な意味検索の対象に自動登録。フォルダごとにカテゴリ名を付けられるため、検索時の分類にも活用できます。 - ---- - -## v0.3.3 の主な変更点 - -### 新機能 - -- **フォルダ監視**: 指定フォルダ内のファイル追加・更新・削除を自動検知し、インデックスに反映。設定パネルからフォルダの追加・削除・カテゴリ指定が可能。取込対象の拡張子もカスタマイズ可能(デフォルト: txt, md, json, html, css, js, mjs, ts, rs)。 -- **接続状態表示**: MCP サーバーへの接続状態を UI に表示。 - -### 改善 - -- **自動起動の安定化**: 設定ファイル読み込み失敗時にレジストリエントリが消える問題を修正。`isEnabled()` で OS の実状態と比較し、不一致時のみ登録を更新するように改善。失敗時のエラー表示を追加。 -- **バックエンドリファクタリング**: MCP ツールの登録を `registry.rs` に集約し、検索ロジックを `search.rs` に整理。DB アクセス関数を `db/mod.rs` に追加。 -- **DB マイグレーション**: ドキュメントパスの正規化マイグレーションを追加(テーブル未存在時はスキップ)。 -- **バージョン表示の安定化**: フッターのバージョン取得でサーバー未応答時のビジーウェイトとフォールバック未設定の問題を修正。 -- **E2E テスト**: 自動起動・フォルダ監視・パネル操作・文書編集のテストを追加。 - ---- - -## インストーラにモデル同梱 - -Pro 版のインストーラ(`TelosDB-Pro_0.3.3_x64-setup.exe`)には、**埋め込み用の量子化 ONNX モデル**が同梱されています。 - -- **利用者**: 別途モデルのダウンロードは不要。インストールするだけで、インストール先のリソースから自動的にモデルが参照され、高精度な意味検索が利用できます。 -- **同梱内容**: `model_quantized.onnx`(日本語 sentence-BERT 量子化モデル)・`vocab.txt`(語彙)。 -- モデルが読み込めない場合でも、全文検索(FTS)のみで検索は可能です。 - ---- - -## 含まれる機能(v0.3.3) - -- **埋め込みモデルのインストーラ同梱**(別途ダウンロード不要。インストール先から自動参照) -- 意味検索(埋め込み 768 次元)+ 全文検索(FTS5)のハイブリッド検索 -- 検索結果の文書単位表示(`group_by_document`) -- **フォルダ監視**: 指定フォルダの自動取り込み・カテゴリ指定・拡張子フィルタ -- MCP ツール: `search_text` / `add_item_text` / `update_item` / `delete_item` / `get_item_by_id` / `get_document_count` / `lsa_retrain`(RE-INDEX) -- ドキュメント件数の表示(ヘッダー「X docs」・`get_document_count`) -- インデックス状態の表示(ベクトル同期中・HNSW 構築状況) -- ログイン時の自動起動(オプション) -- トレイ常駐・バージョン表示 - ---- - -## ご利用方法 - -**インストーラ**: `TelosDB-Pro_0.3.3_x64-setup.exe` を実行してインストールしてください。埋め込みモデルはインストーラに含まれており、追加のダウンロードは不要です。 - -**ソースからビルドする場合**(配布用インストーラを作る開発者向け): - -1. [sentence-bert-base-ja-mean-tokens-v2-int8](https://gitbucket.tmworks.club/dtmoyaji/sentence-bert-base-ja-mean-tokens-v2-int8) から `target/` 一式(`model_quantized.onnx`・`vocab.txt`)を取得し、プロジェクトの **`embedding_model/`** に配置します。 -2. `npm run build:pro` を実行すると、それらがインストーラに同梱された Pro 版がビルドされます。 - -```bash -npm install -npm run build:pro -``` - -- 出力: `src/backend/target/release/bundle/nsis/TelosDB-Pro_0.3.3_x64-setup.exe`(モデル同梱) -- 初回起動時に vec0.dll がアプリデータフォルダへコピーされます。不具合時は `%APPDATA%\com.telosdb.app\logs\telos.log` をご確認ください。 - ---- - -モデル不要で手軽に始めたい方は **Community 版**をご利用ください。 -→ [TelosDB Community 版 商品紹介](RELEASE_v0.3.3_Community.md) diff --git a/docs/specification/01_system_overview.md b/docs/specification/01_system_overview.md index c8d7ea8..0d53ba6 100644 --- a/docs/specification/01_system_overview.md +++ b/docs/specification/01_system_overview.md @@ -20,7 +20,7 @@ ## 3. 主要機能 - **セマンティック検索**: クエリをベクトル化し、類似度でランキング。結果は文書単位で結合して返すオプションあり。 -- **MCP サーバー**: ポート 3001 で SSE。`search_text`・`add_item_text`・`update_item`・`delete_item`・`get_item_by_id`・`get_document_count`・`lsa_retrain`(RE-INDEX)等。 +- **MCP サーバー**: ポート 3001 で SSE。`search_text`・`add_item_text`・`update_item`・`delete_item`・`get_item_by_id`・`list_documents`・`list_categories`・`get_document_count`・`get_document`・`delete_document`・`lsa_retrain`(RE-INDEX)等。 - **セルフヒーリング**: テキストと FTS/ベクトルの不整合を検出し、起動時・手動 heal で同期。 - **常駐 UI**: システムトレイ常駐。検索・文書管理・設定。エディション表示(Community / Pro)。 diff --git a/docs/specification/04_mcp_api_specification.md b/docs/specification/04_mcp_api_specification.md index 2895710..dc5652b 100644 --- a/docs/specification/04_mcp_api_specification.md +++ b/docs/specification/04_mcp_api_specification.md @@ -12,35 +12,80 @@ | ツール名 | 役割 | |----------|------| -| `search_text` | 意味・全文ハイブリッド検索。結果は文書単位で結合可能。 | -| `add_item_text` | テキストをチャンクとして登録。ベクトル化はエディションに応じて LSA または埋め込み。 | +| `search_text` | 意味・全文ハイブリッド検索。結果は文書単位で結合可能。カテゴリで絞り込み可。 | +| `add_item_text` | テキストをチャンクとして登録。オプションで category を指定可能。ベクトル化はエディションに応じて LSA または埋め込み。 | | `update_item` | 指定 ID のチャンクを更新。ベクトル再計算。 | | `delete_item` | 指定 ID のチャンクを削除。 | -| `get_item_by_id` | 指定 ID のメタデータ・本文を取得。 | +| `get_item_by_id` | 指定 ID のメタデータ・本文・category を取得。 | +| `list_documents` | ドキュメント一覧をページングで取得(id, path, mime, chunk_count, category 等)。`limit`・`page` で指定。戻りに `items`・`total_count`・`total_pages`・`page` を含む。 | +| `list_categories` | ドキュメントに設定されているカテゴリ名の一覧(重複なし)。`search_text` の category フィルタ用。 | | `get_document_count` | 文書(documents)の総件数。 | +| `get_document` | 文書 ID で全文・チャンク一覧を取得。 | +| `delete_document` | 文書とその全チャンクを削除。 | | `lsa_retrain` | RE-INDEX。FTS/ベクトルを再構築(Community: LSA 再学習、Pro: vec_items 再投入・HNSW 再構築)。 | ## 3. search_text パラメータ | パラメータ | 型 | 必須 | 既定値 | 説明 | |------------|-----|------|--------|------| -| `content` | string | ○ | — | 検索クエリ。短い語句・自然文どちらも可。 | -| `limit` | integer | — | 10 | 返却最大件数(1〜100)。 | +| `content` | string | ○ | — | 検索クエリ。短い語句・自然文どちらも可。空の場合は `items: []` で返す。 | +| `limit` | integer | — | 5 | 返却最大件数。 | | `min_score` | number | — | 0.3 | 類似度の足切り(0〜1)。 | | `group_by_document` | boolean | — | true | true のとき文書単位で結合して返す。 | +| `category` | string | — | — | 指定時はそのカテゴリのドキュメントのみに絞り込む。`list_categories` で取得した値を使用。 | - 結果の `similarity` は 0〜1。ベクトルと FTS のスコアの大きい方を採用。 -## 4. HTTP API(補助) +### 3.1 search_text レスポンス形式(統一) + +成功時は常に次の形で返す。**マッチ 0 件・空クエリ・内部エラー時もエラーにせず、同一形式で返す**(Issue #11 対応)。 + +- `content[0].text` は JSON 文字列。パースすると: + - `items`: 配列。各要素は `id`, `document_id`, `path`, `mime`, `category`, `content`, `similarity` を持つ。0 件のときは `[]`。 + - `vector_search_used`: boolean。ベクトル検索が使われたか。 + +クライアントは `parsed.items` が常に配列であることを前提に処理できる。 + +## 4. list_documents パラメータ・戻り値(ページング) + +全件取得は避け、ページングで取得する。 + +| パラメータ | 型 | 必須 | 既定値 | 説明 | +|------------|-----|------|--------|------| +| `limit` | integer | — | 20 | 1 ページあたりの件数(1〜100)。 | +| `page` | integer | — | 1 | ページ番号(1 始まり)。範囲外のときは `items: []` のまま返す。 | + +**戻り値**(`content[0].text` を JSON パースしたオブジェクト): + +| キー | 型 | 説明 | +|------|-----|------| +| `items` | array | そのページのドキュメント配列。範囲外ページは `[]`。 | +| `total_count` | number | ドキュメント総数。 | +| `total_pages` | number | 総ページ数(0 件のとき 0)。 | +| `page` | number | 現在のページ番号(1 始まり)。 | + +## 5. add_item_text パラメータ(補足) + +| パラメータ | 型 | 必須 | 説明 | +|------------|-----|------|------| +| `content` | string | ○ | 登録する本文。 | +| `path` | string | ○ | ドキュメントの論理パス(一意)。 | +| `mime` | string | — | MIME タイプ。未指定時は拡張子から推測。 | +| `category` | string | — | カテゴリラベル。フォルダ監視で割り当てたものと合わせて利用可能。 | + +## 6. HTTP API(補助) | メソッド | パス | 説明 | |----------|------|------| -| GET | `/edition` | 起動中エディション(`community` / `pro`)。 | +| GET | `/edition` | 起動中エディション(`community` / `pro`)。Pro 時は `embedding_loaded` も含む。 | | GET | `/version` | アプリバージョン(例: `{"version":"0.3.3"}`)。 | | GET | `/heal` | FTS 同期実行。`{"synced": n}`。 | -| GET | `/model_name` | Pro 時は埋め込みモデル名等。 | +| GET | `/model_name` | エディション名・Pro 時は埋め込みモデル名等。 | +| GET | `/settings` | 設定取得(monitor_paths, run_on_login, min_score, limit 等)。 | +| POST | `/settings` | 設定保存。フォルダ監視・カテゴリ割り当ての更新を含む。 | +| GET | `/indexing_status` | インデックス状態(idle / training 等)。 | -## 5. レスポンス形式 +## 7. レスポンス形式 ツール呼び出しは JSON-RPC 2.0。結果は MCP の `content` 配列(`type: "text"`, `text` に JSON 文字列)で返します。 diff --git a/docs/specification/14_folder_monitor.md b/docs/specification/14_folder_monitor.md index a817877..27b26f2 100644 --- a/docs/specification/14_folder_monitor.md +++ b/docs/specification/14_folder_monitor.md @@ -24,7 +24,7 @@ | **対象ファイル** | 拡張子でフィルタ。デフォルト: `txt`, `md`, `json`, `html`, `css`, `js`, `mjs`, `ts`, `rs`。設定で変更可能。 | | **動作タイミング** | アプリ起動中の常時監視。設定でモニター先を空にすることで実質オフにできる。 | | **デフォルト** | モニター先フォルダなし(`monitor_paths: []`)。 | -| **MCP との関係** | 能動的なプッシュ通知はしない。クライアントは `search_text` や `get_document_count` 等で現在状態をプルする。 | +| **MCP との関係** | 能動的なプッシュ通知はしない。クライアントは `search_text` や `get_document_count` 等で現在状態をプルする。`list_categories` でカテゴリ一覧を取得し、`search_text` の `category` パラメータで絞り込み可能。 | | **対象エディション** | Community 版・Pro 版の両方で利用可能。 | ## 3. 技術構成 diff --git a/package-lock.json b/package-lock.json index fc79a4b..f983af2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "telos-db", - "version": "0.3.3", + "version": "0.3.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "telos-db", - "version": "0.3.3", + "version": "0.3.3.1", "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", "@tauri-apps/api": "^2.10.1", diff --git a/package.json b/package.json index b54fa2d..9949a05 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "telos-db", "private": true, - "version": "0.3.3", + "version": "0.3.3.1", "type": "module", "scripts": { "tauri": "tauri", diff --git a/src/backend/src/mcp/mod.rs b/src/backend/src/mcp/mod.rs index 1356465..6330275 100644 --- a/src/backend/src/mcp/mod.rs +++ b/src/backend/src/mcp/mod.rs @@ -230,7 +230,7 @@ log::info!("MCP Request: {} (Actual: {}, Session: {:?})", method, actual_method, query.session_id); // tools/call をブロードキャストして UI の MCP ACTIVITY に通知 - if method == "tools/call" || matches!(actual_method, "get_item_by_id" | "add_item_text" | "search_text" | "lsa_search" | "update_item" | "delete_item" | "list_documents" | "get_document_count" | "get_document" | "delete_document" | "lsa_retrain") { + if method == "tools/call" || matches!(actual_method, "get_item_by_id" | "add_item_text" | "search_text" | "lsa_search" | "update_item" | "delete_item" | "list_documents" | "list_categories" | "get_document_count" | "get_document" | "delete_document" | "lsa_retrain") { let _ = state.tx.send(format!("mcp:call:{}", actual_method)); } @@ -262,7 +262,7 @@ "resources/list" => Some(serde_json::json!({ "resources": [] })), "prompts/list" => Some(serde_json::json!({ "prompts": [] })), "tools/list" => Some(serde_json::json!({ "tools": tools::registry::tool_list() })), - "tools/call" | "get_item_by_id" | "add_item_text" | "search_text" | "lsa_search" | "update_item" | "delete_item" | "list_documents" | "get_document_count" | "get_document" | "delete_document" | "lsa_retrain" => { + "tools/call" | "get_item_by_id" | "add_item_text" | "search_text" | "lsa_search" | "update_item" | "delete_item" | "list_documents" | "list_categories" | "get_document_count" | "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 9b4a85a..ffa0a1f 100644 --- a/src/backend/src/mcp/tools/items.rs +++ b/src/backend/src/mcp/tools/items.rs @@ -422,16 +422,51 @@ // Document-level API (list, get full content, delete by document) // ---------------------------------------------------------------------------- +/// ドキュメント一覧をページングで返す。limit(1ページあたり件数)・page(1始まり)を指定。範囲外のページは items: [] で返す。 pub async fn handle_list_documents( state: &AppState, + args: &serde_json::Map, ) -> Option { + let limit = args + .get("limit") + .and_then(|v| v.as_i64()) + .unwrap_or(20) + .clamp(1, 100); + let page = args + .get("page") + .and_then(|v| v.as_i64()) + .unwrap_or(1) + .max(1); + let offset = (page - 1) * limit; + + let total_count: i64 = match sqlx::query_scalar("SELECT COUNT(*) FROM documents") + .fetch_one(&state.db_pool) + .await + { + Ok(n) => n, + Err(e) => { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Failed to count documents: {}", e) }], + "isError": true + })); + } + }; + + let total_pages = if total_count == 0 { + 0 + } else { + ((total_count as f64) / (limit as f64)).ceil() as i64 + }; + 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, (SELECT substr(i.content, 1, 15) FROM items i WHERE i.document_id = d.id ORDER BY i.chunk_index ASC LIMIT 1) AS chunk0_preview, COALESCE(d.category, '') AS category - FROM documents d ORDER BY d.path" + FROM documents d ORDER BY d.path LIMIT ? OFFSET ?", ) + .bind(limit) + .bind(offset) .fetch_all(&state.db_pool) .await { @@ -444,7 +479,7 @@ } }; - let list: Vec = rows.iter().map(|row| { + let items: Vec = rows.iter().map(|row| { let id: i64 = row.get(0); let path: String = row.get(1); let mime: Option = row.get(2); @@ -463,7 +498,35 @@ }) }).collect(); - let text = serde_json::to_string_pretty(&list).unwrap_or_else(|_| "[]".to_string()); + let payload = serde_json::json!({ + "items": items, + "total_count": total_count, + "total_pages": total_pages, + "page": page + }); + let text = serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string()); + Some(serde_json::json!({ + "content": [{ "type": "text", "text": text }] + })) +} + +/// ドキュメントに設定されているカテゴリ名の一覧(重複なし・空でないもの)を返す。 +pub async fn handle_list_categories(state: &AppState) -> Option { + let rows = match sqlx::query_scalar::<_, String>( + "SELECT DISTINCT COALESCE(category, '') FROM documents WHERE COALESCE(category, '') != '' ORDER BY category", + ) + .fetch_all(&state.db_pool) + .await + { + Ok(r) => r, + Err(e) => { + return Some(serde_json::json!({ + "content": [{ "type": "text", "text": format!("Failed to list categories: {}", e) }], + "isError": true + })); + } + }; + let text = serde_json::to_string_pretty(&rows).unwrap_or_else(|_| "[]".to_string()); Some(serde_json::json!({ "content": [{ "type": "text", "text": text }] })) diff --git a/src/backend/src/mcp/tools/mod.rs b/src/backend/src/mcp/tools/mod.rs index eae3688..90e4922 100644 --- a/src/backend/src/mcp/tools/mod.rs +++ b/src/backend/src/mcp/tools/mod.rs @@ -15,10 +15,14 @@ match actual_method { "get_item_by_id" => items::handle_get_item_by_id(state, args).await, "add_item_text" => items::handle_add_item_text(state, args).await, - "search_text" | "lsa_search" => search::handle_search_text(state, actual_method, args).await, + "search_text" | "lsa_search" => search::handle_search_text(state, actual_method, args).await + .or_else(|| Some(serde_json::json!({ + "content": [{ "type": "text", "text": r#"{"items":[],"vector_search_used":false}"# }] + }))), "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, + "list_documents" => items::handle_list_documents(state, args).await, + "list_categories" => items::handle_list_categories(state).await, "get_document_count" => items::handle_get_document_count(state).await, "get_document" => items::handle_get_document(state, args).await, "delete_document" => items::handle_delete_document(state, args).await, diff --git a/src/backend/src/mcp/tools/registry.rs b/src/backend/src/mcp/tools/registry.rs index 911e530..faf6058 100644 --- a/src/backend/src/mcp/tools/registry.rs +++ b/src/backend/src/mcp/tools/registry.rs @@ -60,7 +60,18 @@ }), serde_json::json!({ "name": "list_documents", - "description": "List all documents (path, mime, chunk count)", + "description": "List documents with paging (path, mime, chunk count, category). Returns items, total_count, total_pages, page.", + "inputSchema": { + "type": "object", + "properties": { + "limit": { "type": "integer", "default": 20, "description": "Items per page (1–100). Default 20." }, + "page": { "type": "integer", "default": 1, "description": "1-based page number. Out-of-range returns empty items." } + } + } + }), + serde_json::json!({ + "name": "list_categories", + "description": "List distinct category names assigned to documents (for search_text category filter)", "inputSchema": { "type": "object", "properties": {} } }), serde_json::json!({ @@ -110,6 +121,7 @@ "update_item", "delete_item", "list_documents", + "list_categories", "get_document_count", "get_document", "delete_document", diff --git a/src/backend/src/mcp/tools/search.rs b/src/backend/src/mcp/tools/search.rs index 2d4e857..2d2215f 100644 --- a/src/backend/src/mcp/tools/search.rs +++ b/src/backend/src/mcp/tools/search.rs @@ -67,8 +67,13 @@ if search_content.is_empty() { log::info!("[search] query=empty -> skipped"); + let empty_payload = serde_json::json!({ + "items": [], + "vector_search_used": false + }); + let empty_text = serde_json::to_string_pretty(&empty_payload).unwrap_or_else(|_| r#"{"items":[],"vector_search_used":false}"#.to_string()); return Some(serde_json::json!({ - "content": [{ "type": "text", "text": "Empty search query provided." }] + "content": [{ "type": "text", "text": empty_text }] })); } diff --git a/src/backend/tests/search_api.rs b/src/backend/tests/search_api.rs index 166496d..b226998 100644 --- a/src/backend/tests/search_api.rs +++ b/src/backend/tests/search_api.rs @@ -44,3 +44,34 @@ } } } + +/// Issue #11: 空クエリでもエラーにせず、統一形式 { items: [], vector_search_used: false } で返ることの検証。 +#[tokio::test] +#[ignore = "requires MCP server on 127.0.0.1:3001; run with: cargo test --test search_api -- --ignored"] +async fn test_search_text_empty_query_returns_unified_shape() { + let client = Client::new(); + let req = json!({ + "jsonrpc": "2.0", + "method": "search_text", + "params": { "content": "", "limit": 5 }, + "id": 2 + }); + let resp = client + .post("http://127.0.0.1:3001/messages") + .json(&req) + .send() + .await + .expect("API呼び出し失敗"); + assert!(resp.status().is_success(), "空クエリでも 200 であること"); + let body: serde_json::Value = resp.json().await.expect("JSONデコード失敗"); + assert!(body.get("error").is_none(), "JSON-RPC error が無いこと"); + let text = body["result"]["content"][0]["text"].as_str().expect("content[0].text が存在すること"); + let parsed: serde_json::Value = serde_json::from_str(text).expect("content[0].text は JSON であること"); + let obj = parsed.as_object().expect("トップレベルはオブジェクト"); + assert!(obj.contains_key("items"), "items キーがあること"); + assert!(obj.contains_key("vector_search_used"), "vector_search_used キーがあること"); + let items = &obj["items"]; + assert!(items.is_array(), "items は配列"); + assert_eq!(items.as_array().unwrap().len(), 0, "空クエリでは items は空配列"); + assert_eq!(obj["vector_search_used"], false, "vector_search_used は false"); +} diff --git a/src/frontend/components/main-panel.js b/src/frontend/components/main-panel.js index 637be51..b4cfbc3 100644 --- a/src/frontend/components/main-panel.js +++ b/src/frontend/components/main-panel.js @@ -508,6 +508,8 @@ let docsEditorInstance = null; const docsListEl = this.querySelector('#docs-list'); + const DOCS_PAGE_SIZE = 20; + let docsCurrentPage = 1; const docsEditModal = this.querySelector('#docs-edit-modal'); const docsFormArea = this.querySelector('#docs-form-area'); const docsPathEl = this.querySelector('#docs-path'); @@ -522,17 +524,36 @@ if (!docsListEl) return; docsListEl.innerHTML = '
一覧を読み込み中...
'; try { - const result = await callMcp('list_documents', {}); - const list = parseResultText(result); + const result = await callMcp('list_documents', { limit: DOCS_PAGE_SIZE, page: docsCurrentPage }); + const raw = parseResultText(result); + const list = Array.isArray(raw) ? raw : (raw?.items ?? []); + const totalPages = Math.max(0, raw?.total_pages ?? 1); + const totalCount = raw?.total_count ?? list.length; + const currentPage = raw?.page ?? docsCurrentPage; + docsCurrentPage = currentPage; + if (!Array.isArray(list)) { docsListEl.innerHTML = '
一覧の取得に失敗しました
'; return; } if (list.length === 0) { - docsListEl.innerHTML = '
登録された文書はありません
'; + docsListEl.innerHTML = totalCount > 0 + ? '
このページには文書がありません
' + : '
登録された文書はありません
'; return; } - docsListEl.innerHTML = ` + + const prevDisabled = currentPage <= 1; + const nextDisabled = currentPage >= totalPages; + const pagerHtml = totalPages > 1 + ? `
+ + ${currentPage} / ${totalPages} ページ(全 ${totalCount} 件) + +
` + : (totalCount > 0 ? `

全 ${totalCount} 件

` : ''); + + docsListEl.innerHTML = pagerHtml + ` @@ -553,6 +574,17 @@
パスMIMEチャンク数先頭(chunk0)操作
`; + + docsListEl.querySelectorAll('[data-action="prev"]').forEach(btn => { + btn.addEventListener('click', () => { + if (docsCurrentPage > 1) { docsCurrentPage--; loadDocsList(); } + }); + }); + docsListEl.querySelectorAll('[data-action="next"]').forEach(btn => { + btn.addEventListener('click', () => { + if (docsCurrentPage < totalPages) { docsCurrentPage++; loadDocsList(); } + }); + }); docsListEl.querySelectorAll('.docs-btn-edit').forEach(btn => { btn.addEventListener('click', () => openDocEdit(btn.dataset.id)); }); diff --git a/src/frontend/index.html b/src/frontend/index.html index c237e35..2e540b1 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -274,9 +274,13 @@ } else if (parsed && Array.isArray(parsed.items)) { vectorSearchUsed = parsed.vector_search_used === true; results = parsed.items; + } else { + // 統一形式でないレスポンス(items が無い/配列でない)は 0 件として扱う + results = []; } } catch (e) { - // 単なるテキストメッセージ(エラー等)の場合はそのまま表示へ + // パース失敗時も 0 件として扱い、エラーにしない + results = []; } } diff --git a/src/frontend/styles.css b/src/frontend/styles.css index 8c06de4..8575343 100644 --- a/src/frontend/styles.css +++ b/src/frontend/styles.css @@ -441,6 +441,35 @@ overflow: auto; margin-bottom: 16px; } +.docs-pager { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; + flex-wrap: wrap; +} +.docs-pager-btn { + padding: 6px 14px; + font-size: 0.9rem; + background: var(--bg-surface); + border: 1px solid var(--border-base); + border-radius: 6px; + color: var(--text-secondary); + cursor: pointer; +} +.docs-pager-btn:hover:not(:disabled) { + background: var(--border-base); + border-color: var(--accent-blue); + color: var(--text-primary); +} +.docs-pager-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.docs-pager-info { + color: var(--text-dim); + font-size: 0.9rem; +} .docs-table { width: 100%; border-collapse: collapse; diff --git a/tests/test_mcp_client.mjs b/tests/test_mcp_client.mjs index 17f93c7..c7cd127 100644 --- a/tests/test_mcp_client.mjs +++ b/tests/test_mcp_client.mjs @@ -134,6 +134,47 @@ console.log(" Pro vectorization E2E: vector_search_used=true, スコア差あり (OK)"); } +/** + * Issue #11: 検索結果が取得できないときはエラーにせず、統一形式 { items: [], vector_search_used } で返すことの検証。 + */ +async function testSearchEmptyAndNoMatchReturnsUnifiedShape(axiosInstance) { + // 空クエリ: 必ず items: [] の統一形式で返ること + const emptyRes = await postMessage(axiosInstance, "tools/call", { + name: "search_text", + arguments: { content: "", limit: 5 } + }); + if (emptyRes.error) throw new Error("Issue #11 empty query: JSON-RPC error " + JSON.stringify(emptyRes.error)); + const emptyText = emptyRes.result?.content?.[0]?.text; + if (!emptyText) throw new Error("Issue #11 empty query: no content[0].text"); + let parsed; + try { + parsed = JSON.parse(emptyText); + } catch (e) { + throw new Error("Issue #11 empty query: content[0].text is not JSON: " + emptyText.slice(0, 80)); + } + if (!Array.isArray(parsed.items)) throw new Error("Issue #11 empty query: parsed.items is not array"); + if (typeof parsed.vector_search_used !== "boolean") throw new Error("Issue #11 empty query: vector_search_used is not boolean"); + if (parsed.items.length !== 0) throw new Error("Issue #11 empty query: expected items.length 0, got " + parsed.items.length); + console.log(" Empty query: 200, items=[], vector_search_used=" + parsed.vector_search_used + " (OK)"); + + // マッチしないクエリ: 同じく統一形式で返り、items は配列であること(0件可) + const noMatchRes = await postMessage(axiosInstance, "tools/call", { + name: "search_text", + arguments: { content: "xyznonexistent_phrase_12345", limit: 5 } + }); + if (noMatchRes.error) throw new Error("Issue #11 no-match: JSON-RPC error " + JSON.stringify(noMatchRes.error)); + const noMatchText = noMatchRes.result?.content?.[0]?.text; + if (!noMatchText) throw new Error("Issue #11 no-match: no content[0].text"); + try { + parsed = JSON.parse(noMatchText); + } catch (e) { + throw new Error("Issue #11 no-match: content[0].text is not JSON"); + } + if (!Array.isArray(parsed.items)) throw new Error("Issue #11 no-match: parsed.items is not array"); + if (typeof parsed.vector_search_used !== "boolean") throw new Error("Issue #11 no-match: vector_search_used is not boolean"); + console.log(" No-match query: 200, items.length=" + parsed.items.length + ", vector_search_used=" + parsed.vector_search_used + " (OK)"); +} + function parseResultText(result) { const text = result?.result?.content?.[0]?.text; if (!text) return null; @@ -164,9 +205,9 @@ console.log("\n[7] add_item_text → list_documents → get_document → get_item_by_id → update_item → delete_item → delete_document..."); await postMessage(axiosInstance, "tools/call", { name: "add_item_text", arguments: { path, content } }); - const listRes = await postMessage(axiosInstance, "tools/call", { name: "list_documents", arguments: {} }); + const listRes = await postMessage(axiosInstance, "tools/call", { name: "list_documents", arguments: { limit: 100, page: 1 } }); const list = parseResultText(listRes); - const docList = Array.isArray(list) ? list : []; + const docList = Array.isArray(list) ? list : (list?.items ?? []); const doc = docList.find((d) => d.path === path); if (!doc || doc.id == null) throw new Error("list_documents: added doc not found or no id. list=" + JSON.stringify(docList).slice(0, 200)); @@ -188,9 +229,9 @@ await postMessage(axiosInstance, "tools/call", { name: "delete_item", arguments: { id: chunkId } }); await postMessage(axiosInstance, "tools/call", { name: "delete_document", arguments: { document_id: doc.id } }); - const listRes2 = await postMessage(axiosInstance, "tools/call", { name: "list_documents", arguments: {} }); + const listRes2 = await postMessage(axiosInstance, "tools/call", { name: "list_documents", arguments: { limit: 100, page: 1 } }); const list2 = parseResultText(listRes2); - const docList2 = Array.isArray(list2) ? list2 : []; + const docList2 = Array.isArray(list2) ? list2 : (list2?.items ?? []); if (docList2.some((d) => d.path === path)) throw new Error("delete_document: document still in list"); console.log(" CRUD flow OK."); } @@ -232,6 +273,9 @@ const content = searchResult.result?.content?.[0]?.text; console.log(" Result:\n", content || " No results"); + console.log("\n[2b] Issue #11: search_text empty / no-match returns 200 with items[]..."); + await testSearchEmptyAndNoMatchReturnsUnifiedShape(axiosInstance); + console.log("\n[3] Pro: assert vector search contributes (not all scores 0.4)..."); await assertProVectorScoresNotAllFallback(axiosInstance); diff --git a/tools/scripts/sync_issues.mjs b/tools/scripts/sync_issues.mjs index 4310165..2f17b9e 100644 --- a/tools/scripts/sync_issues.mjs +++ b/tools/scripts/sync_issues.mjs @@ -1,29 +1,15 @@ +/** + * GitBucket / GitHub 互換 API で Issue を docs/issues/ と同期する。 + * 環境変数(.env): GITBUCKET_TOKEN(必須), GITBUCKET_API_BASE, GITBUCKET_REPO(任意・未設定時は dtmoyaji/TelosDB) + */ import fs from 'fs'; import path from 'path'; +import { loadEnv, getToken, getIssuesApiBase } from './gitbucket_env.mjs'; -// If running Node < 20.6, we might need dotenv, but let's try to load .env manually for zero-dependency -function loadEnv() { - const envPath = path.join(process.cwd(), '.env'); - if (fs.existsSync(envPath)) { - const content = fs.readFileSync(envPath, 'utf-8'); - content.split('\n').forEach(line => { - const match = line.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/); - if (match) { - const key = match[1]; - let value = match[2] || ''; - if (value.length > 0 && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') { - value = value.replace(/\\n/gm, '\n'); - } - value = value.replace(/(^['"]|['"]$)/g, '').trim(); - process.env[key] = value; - } - }); - } -} loadEnv(); -const TOKEN = process.env.GITBUCKET_TOKEN; -const API_BASE = "https://gitbucket.tmworks.club/api/v3/repos/dtmoyaji/TelosDB/issues"; +const TOKEN = getToken(); +const API_BASE = getIssuesApiBase(); const ISSUES_DIR = path.join(process.cwd(), 'docs', 'issues'); if (!fs.existsSync(ISSUES_DIR)) { @@ -89,7 +75,7 @@ // 公式 GitBucket では Issue の PATCH(編集)API は全バージョンで未実装のため 404 になる。 // 代替: Issue にコメントを投稿する API(Create an issue comment)は対応している。 -const COMMENTS_BASE = "https://gitbucket.tmworks.club/api/v3/repos/dtmoyaji/TelosDB/issues"; +const COMMENTS_BASE = API_BASE; async function postIssueComment(issueNumber, commentBody) { if (!TOKEN) { @@ -110,7 +96,9 @@ return await apiCall(`${API_BASE}/${number}`, 'PATCH', { title, body, state }); } catch (err) { if (err.message.includes('404')) { - const htmlUrl = `https://gitbucket.tmworks.club/dtmoyaji/TelosDB/issues/${number}`; + const repo = process.env.GITBUCKET_REPO || 'dtmoyaji/TelosDB'; + const base = (process.env.GITBUCKET_API_BASE || 'https://gitbucket.tmworks.club/api/v3').replace(/\/api\/v3\/?$/, ''); + const htmlUrl = `${base}/${repo}/issues/${number}`; console.warn(`\n[Info] GitBucket API (PATCH) returned 404. Issue edit is not implemented in official GitBucket.`); console.warn(`Posting current body as a comment instead...`); const posted = await postIssueComment(number, body);