From 784ce8cf9044cab23903fb31c63bcb674b5b209e Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 15:49:03 +0000 Subject: [PATCH 01/14] chore: remove non-plexus workflows and skills --- .claude/skills/agent-tool-test/SKILL.md | 166 --------- .../skills/agent-tool-test/run_tool_matrix.py | 322 ------------------ .claude/skills/github-raw-fetch/SKILL.md | 49 --- .github/workflows/ci-backend.yml | 49 --- .github/workflows/ci-browser-extension.yml | 50 --- .github/workflows/ci-ingest.yml | 44 --- .github/workflows/claude-review-back.yml | 94 ----- .github/workflows/deploy-backend.yml | 69 ---- .github/workflows/job-ingest-github.yml | 125 ------- .../workflows/job-ingest-google-youtube.yml | 152 --------- .github/workflows/job-ingest-spotify.yml | 120 ------- .github/workflows/opencode-review-back.yml | 91 ----- 12 files changed, 1331 deletions(-) delete mode 100644 .claude/skills/agent-tool-test/SKILL.md delete mode 100644 .claude/skills/agent-tool-test/run_tool_matrix.py delete mode 100644 .claude/skills/github-raw-fetch/SKILL.md delete mode 100644 .github/workflows/ci-backend.yml delete mode 100644 .github/workflows/ci-browser-extension.yml delete mode 100644 .github/workflows/ci-ingest.yml delete mode 100644 .github/workflows/claude-review-back.yml delete mode 100644 .github/workflows/deploy-backend.yml delete mode 100644 .github/workflows/job-ingest-github.yml delete mode 100644 .github/workflows/job-ingest-google-youtube.yml delete mode 100644 .github/workflows/job-ingest-spotify.yml delete mode 100644 .github/workflows/opencode-review-back.yml diff --git a/.claude/skills/agent-tool-test/SKILL.md b/.claude/skills/agent-tool-test/SKILL.md deleted file mode 100644 index b044db9..0000000 --- a/.claude/skills/agent-tool-test/SKILL.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -name: "agent-tool-test" -description: "ToolCall テスト実行スキル。AIが自律的にTmuxでBEを起動し、ログを収集しながら全モデル×全ツールのテストを実行し、テスト結果とログから不具合原因を特定する。" -allowed-tools: "Bash, Read, Write" ---- - -# Agent Tool Test - -ToolCall テストを自律実行するスキル。バックエンドで定義されている全ツールについて、全モデルでの実行テストを行います。 - -## 概要 - -- Tmuxでバックエンドサーバーを起動 -- バックエンドで定義されている全ツール × 全モデルの組み合わせでテスト実行 -- ログを収集しながら結果を分析 -- 不具合が発生した場合はログから原因を特定 - -## ワークフロー - -### 1. 事前確認 - -以下の情報を確認します: - -- サービスポート(デフォルト: 8000) -- テスト対象モデル(未指定なら全モデル) -- テストパラメータ(日付範囲、limit、granularity) - -### 2. 利用可能なツール・モデルの確認 - -```bash -# モデル一覧取得 -curl -s http://127.0.0.1:8000/v1/chat/models | jq '.models[].id' - -# ツール一覧取得(動的) -curl -s http://127.0.0.1:8000/v1/chat/tools | jq '.tools[].name' -``` - -### 3. ポート確保とBE起動 - -```bash -# ポート解放 -fuser -k 8000/tcp 2>/dev/null || true - -# tmuxセッションでBE起動 -cd /root/workspace/ego-graph -tmux new-session -d -s "egograph-backend" "uv run python -m backend.main" -``` - -### 4. 起動確認 - -```bash -# 健康チェック -sleep 3 -curl -s http://127.0.0.1:8000/health || echo "BE起動失敗" -``` - -### 5. ツールマトリクステスト実行 - -スキルフォルダ内のスクリプトを実行します: - -```bash -# スキルフォルダのスクリプトを実行 -uv run python .claude/skills/agent-tool-test/run_tool_matrix.py \ - --api-url http://127.0.0.1:8000 \ - --models "glm-4.7" \ - --start-date 2026-01-01 \ - --end-date 2026-01-31 \ - --limit 5 -``` - -または、プロジェクトのスクリプトを使用: - -```bash -uv run python backend/scripts/run_tool_matrix.py \ - --api-url http://127.0.0.1:8000 \ - --models "" \ - --start-date 2026-01-01 \ - --end-date 2026-01-31 \ - --limit 5 \ - --granularity day -``` - -### 6. ログ収集と分析 - -```bash -# tmuxログ取得 -tmux capture-pane -p -t "egograph-backend" -S -500 - -# 結果ファイル確認(スクリプト使用時) -cat tool_matrix_results_*.json | jq '.results[] | select(.llm_response | test("error|Error|ERROR"; "i"))' -``` - -### 7. 結果分析と不具合特定 - -以下の観点で分析します: - -- **ステータスコード**: HTTP 200以外のレスポンス -- **ツール呼び出し成功率**: 各ツールが正しく呼び出されたか -- **LLM応答内容**: 期待通りのツール呼び出しが行われたか -- **エラーログ**: バックエンド側のエラーログ -- **モデル別傾向**: 特定のモデルでのみ失敗するケース - -### 8. クリーンアップ - -```bash -# セッション終了 -tmux kill-session -t "egograph-backend" -``` - -## 期待出力 - -- テスト実行のサマリー(成功/失敗数) -- 失敗ケースの詳細(モデル、ツール、エラー内容) -- 不具合原因の特定結果 -- 必要に応じて修正提案 - -## ガードレール - -- シークレット(APIキー等)をログに含めない -- 1変更ずつテストし、影響範囲を明確にする -- 最小限の再現ケースから始める -- ツール定義の変更を検知した場合は再テスト - -## コマンドテンプレート - -### 特定モデルのみテスト - -```bash -uv run python .claude/skills/agent-tool-test/run_tool_matrix.py \ - --api-url http://127.0.0.1:8000 \ - --models "glm-4.7" \ - --limit 3 -``` - -### 複数モデルを指定 - -```bash -uv run python .claude/skills/agent-tool-test/run_tool_matrix.py \ - --api-url http://127.0.0.1:8000 \ - --models "glm-4.7,xiaomi/mimo-v2-flash:free" -``` - -### タイムアウト延长 - -```bash -uv run python .claude/skills/agent-tool-test/run_tool_matrix.py \ - --api-url http://127.0.0.1:8000 \ - --timeout 300.0 -``` - -### 単一ツールの直接テスト(デバッグ用) - -```bash -curl -N -s -X POST http://127.0.0.1:8000/v1/chat \ - -H "Content-Type: application/json" \ - -d '{ - "model_name": "glm-4.7", - "stream": true, - "messages": [ - { - "role": "user", - "content": "Call get_top_tracks with start_date=2026-01-01, end_date=2026-01-31, limit=5. Return only tool calls." - } - ] - }' -``` diff --git a/.claude/skills/agent-tool-test/run_tool_matrix.py b/.claude/skills/agent-tool-test/run_tool_matrix.py deleted file mode 100644 index 7b3670a..0000000 --- a/.claude/skills/agent-tool-test/run_tool_matrix.py +++ /dev/null @@ -1,322 +0,0 @@ -#!/usr/bin/env python3 -"""全モデル x 全ツールの実行テストを行う。 - -使い方: - uv run python .claude/skills/agent-tool-test/run_tool_matrix.py \ - --api-url http://127.0.0.1:8000 \ - --models glm-4.7,xiaomi/mimo-v2-flash:free \ - --start-date 2026-01-01 --end-date 2026-01-31 \ - --limit 5 --granularity day - -モデル指定が無い場合は全モデルが対象になります。 -ツールはAPIから動的に取得します。 -""" - -from __future__ import annotations - -import argparse -import json -import os -import sys -from dataclasses import dataclass -from datetime import date -from typing import Any - -import httpx - -DEFAULT_API_URL = "http://127.0.0.1:8000" - - -@dataclass(frozen=True) -class ToolCase: - name: str - prompt: str - - -def _build_tool_cases( - tools: list[dict[str, Any]], - start_date: str, - end_date: str, - limit: int, - granularity: str, -) -> list[ToolCase]: - """APIから取得したツール情報からテストケースを構築します。 - - Args: - tools: ツール情報のリスト(APIから取得) - start_date: 開始日 - end_date: 終了日 - limit: リミット - granularity: 粒度 - - Returns: - テストケースのリスト - """ - tool_cases: list[ToolCase] = [] - - for tool in tools: - tool_name = tool["name"] - description = tool.get("description", "") - - # ツール名に応じたプロンプトを構築 - if "watch_history" in tool_name: - prompt = ( - f"Call {tool_name} with start_date={start_date}, " - f"end_date={end_date}, limit={limit}. " - f"Return only tool calls. Tool description: {description}" - ) - elif "watching_stats" in tool_name or "listening_stats" in tool_name: - prompt = ( - f"Call {tool_name} with start_date={start_date}, " - f"end_date={end_date}, granularity={granularity}. " - f"Return only tool calls. Tool description: {description}" - ) - elif "top_tracks" in tool_name or "top_channels" in tool_name: - prompt = ( - f"Call {tool_name} with start_date={start_date}, " - f"end_date={end_date}, limit={limit}. " - f"Return only tool calls. Tool description: {description}" - ) - elif "activity_stats" in tool_name: - prompt = ( - f"Call {tool_name} with start_date={start_date}, " - f"end_date={end_date}, granularity={granularity}. " - f"Return only tool calls. Tool description: {description}" - ) - elif "repositories" in tool_name: - # repositoriesは日付パラメータが不要 - prompt = ( - f"Call {tool_name} to get repository list. " - f"Return only tool calls. Tool description: {description}" - ) - else: - # デフォルト: ツール名のみを指定 - prompt = ( - f"Call {tool_name} with appropriate parameters. " - f"Return only tool calls. Tool description: {description}" - ) - - tool_cases.append(ToolCase(name=tool_name, prompt=prompt)) - - return tool_cases - - -def _iter_sse_events(response: httpx.Response): - event_name: str | None = None - for line in response.iter_lines(): - if not line: - event_name = None - continue - if line.startswith("event:"): - event_name = line.split(":", 1)[1].strip() - continue - if line.startswith("data:"): - payload = line.split(":", 1)[1].strip() - yield event_name or "message", payload - - -def _stream_tool_call( - client: httpx.Client, - url: str, - headers: dict[str, str], - model_name: str, - prompt: str, - tool_name: str, - timeout: float, -) -> dict[str, Any]: - payload = { - "model_name": model_name, - "stream": True, - "messages": [ - { - "role": "user", - "content": ( - "You are a tool runner. " - "You MUST call the specified tool exactly once and nothing else. " - f"Tool: {tool_name}. {prompt}" - ), - } - ], - } - - result: dict[str, Any] = { - "model": model_name, - "tool": tool_name, - "llm_response": None, - "events": [], - "error": None, - } - - try: - with client.stream( - "POST", url, json=payload, headers=headers, timeout=timeout - ) as resp: - if resp.status_code != 200: - result["llm_response"] = f"HTTP {resp.status_code}: {resp.text}" - return result - - for event, data in _iter_sse_events(resp): - if not data: - continue - try: - chunk = json.loads(data) - except json.JSONDecodeError: - continue - - result["events"].append({"event": event, "data": chunk}) - - if chunk.get("delta"): - if not result["llm_response"]: - result["llm_response"] = "" - result["llm_response"] += chunk["delta"] - - return result - except httpx.RequestError as exc: - result["error"] = f"HTTP error: {exc}" - return result - - -def _get_models(client: httpx.Client, url: str, headers: dict[str, str]) -> list[str]: - resp = client.get(url, headers=headers, timeout=30.0) - resp.raise_for_status() - data = resp.json() - return [model["id"] for model in data.get("models", [])] - - -def _get_tools( - client: httpx.Client, api_url: str, headers: dict[str, str] -) -> list[dict[str, Any]]: - """APIからツール一覧を取得します。 - - Args: - client: httpx クライアント - api_url: API URL - headers: リクエストヘッダー - - Returns: - ツール情報のリスト - """ - try: - resp = client.get(f"{api_url}/v1/chat/tools", headers=headers, timeout=30.0) - resp.raise_for_status() - data = resp.json() - return data.get("tools", []) - except httpx.HTTPError as exc: - print(f"Failed to fetch tools: {exc}", file=sys.stderr) - return [] - - -def _parse_args() -> argparse.Namespace: - today = date.today().strftime("%Y-%m-%d") - parser = argparse.ArgumentParser(description="Run tool matrix test.") - parser.add_argument("--api-url", default=DEFAULT_API_URL) - parser.add_argument("--api-key", default=os.getenv("API_KEY")) - parser.add_argument( - "--api-key-header", - default=os.getenv("API_KEY_HEADER", "Authorization"), - ) - parser.add_argument( - "--models", - default=None, - help="Comma-separated model list (default: all models)", - ) - parser.add_argument( - "--tools", - default=None, - help="Comma-separated tool list (default: all tools)", - ) - parser.add_argument("--start-date", default="2026-01-01") - parser.add_argument("--end-date", default="2026-01-31") - parser.add_argument("--limit", type=int, default=5) - parser.add_argument("--granularity", default="day") - parser.add_argument("--timeout", type=float, default=120.0) - parser.add_argument("--output", default=f"tool_matrix_results_{today}.json") - return parser.parse_args() - - -def main() -> int: - args = _parse_args() - api_url = args.api_url.rstrip("/") - headers: dict[str, str] = {} - if args.api_key: - headers[args.api_key_header] = args.api_key - - with httpx.Client() as client: - # モデルリストを取得 - if args.models: - # 引数で指定されたモデルを使用 - models = [m.strip() for m in args.models.split(",")] - else: - # 全モデルを取得 - try: - models = _get_models(client, f"{api_url}/v1/chat/models", headers) - except httpx.HTTPError as exc: - print(f"Failed to fetch models: {exc}", file=sys.stderr) - return 1 - - # ツールリストをAPIから取得 - tools = _get_tools(client, api_url, headers) - if not tools: - print("No tools available for testing.", file=sys.stderr) - return 1 - - # --toolsオプションで指定されたツールのみを対象にする - if args.tools: - specified_tools = [t.strip() for t in args.tools.split(",")] - tools = [t for t in tools if t["name"] in specified_tools] - if not tools: - print(f"None of the specified tools found: {specified_tools}", file=sys.stderr) - return 1 - - print(f"Found {len(tools)} tools: {[t['name'] for t in tools]}") - print(f"Testing {len(models)} models: {models}") - print() - - # ツールケースを構築 - tool_cases = _build_tool_cases( - tools, - args.start_date, - args.end_date, - args.limit, - args.granularity, - ) - results: list[dict[str, Any]] = [] - - for model in models: - for tool_case in tool_cases: - print(f"Testing: {model} - {tool_case.name}") - result = _stream_tool_call( - client, - f"{api_url}/v1/chat", - headers, - model, - tool_case.prompt, - tool_case.name, - args.timeout, - ) - results.append(result) - - # LLM応答を表示 - if result["llm_response"]: - print(f" Response: {result['llm_response'][:200]}") - else: - print(" Response: (empty)") - print() - - with open(args.output, "w", encoding="utf-8") as f: - json.dump( - { - "api_url": api_url, - "models": models, - "results": results, - }, - f, - ensure_ascii=False, - indent=2, - ) - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.claude/skills/github-raw-fetch/SKILL.md b/.claude/skills/github-raw-fetch/SKILL.md deleted file mode 100644 index f9768fe..0000000 --- a/.claude/skills/github-raw-fetch/SKILL.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: "github-raw-fetch" -description: "OSSのからrawファイルを直接取得。Claude Codeの最新CHANGELOG確認など、OSSの最新情報や仕様を手軽に取得が可能" -allowed-tools: "Bash" ---- - -# GitHub Raw Fetch - -GitHub リポジトリからファイルを直接取得する。clone 不要で素早く中身を確認できる。 - -## 基本形 - -```bash -curl -s "https://raw.githubusercontent.com////" -``` - -| パラメータ | 説明 | -|-----------|------| -| `` | 組織・ユーザー名 | -| `` | リポジトリ名 | -| `` | ブランチ名(通常 `main` か `master`) | -| `` | ファイルパス | - -## よく使う例 - -### CHANGELOG の確認(先頭50行) -```bash -curl -s "https://raw.githubusercontent.com///main/CHANGELOG.md" | head -50 -``` - -### Claude Code の最新 CHANGELOG -```bash -curl -s "https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md" | head -100 -``` - -### README の取得 -```bash -curl -s "https://raw.githubusercontent.com///main/README.md" -``` - -### package.json からバージョン取得 -```bash -curl -s "https://raw.githubusercontent.com///main/package.json" | jq -r '.version' -``` - -### ファイル内キーワード検索 -```bash -curl -s "https://raw.githubusercontent.com///main/README.md" | grep -i "keyword" -``` diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml deleted file mode 100644 index 9cb9fa0..0000000 --- a/.github/workflows/ci-backend.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Test Backend - -on: - workflow_dispatch: - push: - branches: [main] - paths: - - "backend/**" - pull_request: - branches: [main] - paths: - - "backend/**" - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - enable-cache: true - - - name: Set up Python 3.13 - run: uv python install 3.13 - - - name: Install dependencies - run: uv sync --all-packages - - - name: Run tests - env: - R2_ENDPOINT_URL: "https://test.r2.cloudflarestorage.com" - R2_ACCESS_KEY_ID: "test-access-key" - R2_SECRET_ACCESS_KEY: "test-secret-key" - R2_BUCKET_NAME: "test-bucket" - run: | - cd backend - uv run pytest tests/ -v --cov=backend --cov-report=xml --cov-report=term - - # Future: Enable Codecov when needed (requires CODECOV_TOKEN in GitHub Secrets) - # - name: Upload coverage - # uses: codecov/codecov-action@v4 - # if: always() - # with: - # token: ${{ secrets.CODECOV_TOKEN }} - # file: ./backend/coverage.xml - # fail_ci_if_error: false diff --git a/.github/workflows/ci-browser-extension.yml b/.github/workflows/ci-browser-extension.yml deleted file mode 100644 index 5a7910f..0000000 --- a/.github/workflows/ci-browser-extension.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Test Browser Extension - -on: - workflow_dispatch: - push: - branches: [main] - paths: - - "browser-extension/chromium-history/**" - - ".github/workflows/ci-browser-extension.yml" - pull_request: - branches: [main] - paths: - - "browser-extension/chromium-history/**" - - ".github/workflows/ci-browser-extension.yml" - -jobs: - test-and-build: - runs-on: ubuntu-latest - - defaults: - run: - working-directory: browser-extension/chromium-history - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: "browser-extension/chromium-history/package-lock.json" - - - name: Install dependencies - run: npm ci - - - name: Run tests - run: npm test - - - name: Build extension - run: npm run build - - - name: Upload browser extension artifact - uses: actions/upload-artifact@v4 - with: - name: chromium-history-extension-dist - path: browser-extension/chromium-history/dist/ - if-no-files-found: error - retention-days: 14 diff --git a/.github/workflows/ci-ingest.yml b/.github/workflows/ci-ingest.yml deleted file mode 100644 index 73fa8ce..0000000 --- a/.github/workflows/ci-ingest.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Test Ingestion - -on: - workflow_dispatch: - push: - branches: [main] - paths: - - 'ingest/**' - pull_request: - branches: [main] - paths: - - 'ingest/**' - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - enable-cache: true - - - name: Set up Python 3.13 - run: uv python install 3.13 - - - name: Install dependencies - run: uv sync --all-packages - - - name: Run tests - run: | - cd ingest - uv run pytest tests/ -v --cov=ingest --cov-report=xml --cov-report=term - - # Future: Enable Codecov when needed (requires CODECOV_TOKEN in GitHub Secrets) - # - name: Upload coverage - # uses: codecov/codecov-action@v4 - # if: always() - # with: - # token: ${{ secrets.CODECOV_TOKEN }} - # file: ./ingest/coverage.xml - # fail_ci_if_error: false diff --git a/.github/workflows/claude-review-back.yml b/.github/workflows/claude-review-back.yml deleted file mode 100644 index d232703..0000000 --- a/.github/workflows/claude-review-back.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Claude Review Back - -on: - workflow_dispatch: - inputs: - pr_number: - description: "PR number to process" - required: true - type: string - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - pull_request_review: - types: [submitted] - -jobs: - review-back: - timeout-minutes: 30 - concurrency: - group: review-back-claude-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} - cancel-in-progress: true - if: | - github.event_name == 'workflow_dispatch' || - ( - !endsWith(github.actor, '[bot]') && - !contains(github.event.comment.body || github.event.review.body || '', '') && - ( - contains(github.event.comment.body || '', '@claude') || - contains(github.event.review.body || '', '@claude') - ) && - ( - github.event_name != 'issue_comment' || - github.event.issue.pull_request != null - ) - ) - runs-on: ubuntu-latest - permissions: - actions: read - contents: write - pull-requests: write - issues: write - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Resolve PR number - id: pr - shell: bash - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - PR_NUMBER="${{ inputs.pr_number }}" - elif [ "${{ github.event_name }}" = "issue_comment" ]; then - PR_NUMBER="${{ github.event.issue.number }}" - else - PR_NUMBER="${{ github.event.pull_request.number }}" - fi - - if [ -z "$PR_NUMBER" ]; then - echo "PR number could not be resolved" >&2 - exit 1 - fi - - echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - - - name: Checkout PR branch - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - gh pr checkout "${{ steps.pr.outputs.pr_number }}" - - - name: Run Claude Code review-back skill - uses: anthropics/claude-code-action@v1 - env: - ANTHROPIC_BASE_URL: https://api.z.ai/api/anthropic - with: - claude_code_oauth_token: ${{ secrets.ANTHROPIC_API_KEY }} - additional_permissions: | - actions: read - prompt: | - Use the `pr-review-back-workflow` skill. - This environment is non-interactive. - Never use `AskUserQuestion`. - If the workflow would normally ask the user to confirm a plan, make reasonable assumptions and continue. - claude_args: | - --max-turns 20 - --allowedTools Bash,Read,Edit,Write - --append-system-prompt "You are running in GitHub Actions for repository ${{ github.repository }}. Pull request #${{ steps.pr.outputs.pr_number }} is already checked out in the working directory. This environment is non-interactive, so do not wait for user answers." diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml deleted file mode 100644 index c211409..0000000 --- a/.github/workflows/deploy-backend.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Deploy Backend - -on: - workflow_dispatch: - push: - branches: [main] - paths: - - "backend/**" - - "pyproject.toml" - - "uv.lock" - - ".github/workflows/deploy-backend.yml" - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - enable-cache: true - - - name: Set up Python 3.13 - run: uv python install 3.13 - - - name: Install dependencies - run: uv sync --all-packages - - - name: Run backend tests - run: | - cd backend - uv run pytest tests/ -v --cov=backend --cov-report=xml --cov-report=term - env: - R2_ENDPOINT_URL: https://example.invalid - R2_ACCESS_KEY_ID: test - R2_SECRET_ACCESS_KEY: test - - deploy: - runs-on: ubuntu-latest - needs: test - steps: - - uses: actions/checkout@v4 - - - name: Join Tailscale - uses: tailscale/github-action@v2 - with: - authkey: ${{ secrets.TS_AUTHKEY }} - - - name: Deploy via SSH - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USER }} - key: ${{ secrets.SSH_KEY }} - script: | - set -euo pipefail - cd /opt/egograph/repo - git fetch origin main - git checkout main - git reset --hard origin/main - /root/.local/bin/uv sync --all-packages - sudo systemctl restart egograph-backend - sleep 2 - curl -fsS http://127.0.0.1:8000/health - - - name: Cleanup Tailscale - if: always() - run: sudo tailscale logout diff --git a/.github/workflows/job-ingest-github.yml b/.github/workflows/job-ingest-github.yml deleted file mode 100644 index ff03549..0000000 --- a/.github/workflows/job-ingest-github.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: GitHub Worklog Ingestion to R2 Data Lake - -on: - schedule: - # 1日1回実行(深夜) - - cron: '0 15 * * *' # 15:00 UTC = 00:00 JST (深夜) - workflow_dispatch: - -jobs: - ingest: - runs-on: ubuntu-latest - timeout-minutes: 60 - permissions: - contents: read - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - enable-cache: true - cache-dependency-glob: "uv.lock" - - - name: Set up Python 3.13 - run: uv python install 3.13 - - - name: Install dependencies - run: | - uv sync --all-packages - - - name: Run GitHub Worklog ingestion pipeline - env: - GITHUB_PAT: ${{ secrets.EGOGRAPH_GITHUB_PAT }} - GITHUB_LOGIN: ${{ secrets.EGOGRAPH_GITHUB_LOGIN }} - DUCKDB_PATH: data/analytics.duckdb - R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} - R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} - LOG_LEVEL: INFO - run: | - [ -n "$GITHUB_PAT" ] || { echo "EGOGRAPH_GITHUB_PAT secret is required"; exit 1; } - [ -n "$GITHUB_LOGIN" ] || { echo "EGOGRAPH_GITHUB_LOGIN secret is required"; exit 1; } - - uv run python -m ingest.github.main - - - name: Compact GitHub parquet datasets - env: - R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} - R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} - LOG_LEVEL: INFO - run: | - uv run python -m ingest.github.compact - - - name: Verify Compact Parquet Data Integrity (R2) - if: success() - env: - R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} - R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} - run: | - uv run python - << 'PY' - import duckdb - import os - - # R2設定を環境変数から直接読み取り - endpoint_url = os.environ["R2_ENDPOINT_URL"] - access_key_id = os.environ["R2_ACCESS_KEY_ID"] - secret_access_key = os.environ["R2_SECRET_ACCESS_KEY"] - bucket_name = os.environ["R2_BUCKET_NAME"] - access_key_id = access_key_id.replace("'", "''") - secret_access_key = secret_access_key.replace("'", "''") - endpoint_clean = endpoint_url.replace("https://", "").replace("'", "''") - - conn = duckdb.connect(":memory:") - conn.execute("INSTALL httpfs;") - conn.execute("LOAD httpfs;") - conn.execute(f""" - CREATE SECRET ( - TYPE S3, - KEY_ID '{access_key_id}', - SECRET '{secret_access_key}', - REGION 'auto', - ENDPOINT '{endpoint_clean}', - URL_STYLE 'path' - ); - """) - - bucket_clean = bucket_name.replace("'", "''") - commits_url = f"s3://{bucket_clean}/compacted/events/github/commits/**/*.parquet" - prs_url = f"s3://{bucket_clean}/compacted/events/github/pull_requests/**/*.parquet" - commits_url_escaped = commits_url.replace("'", "''") - prs_url_escaped = prs_url.replace("'", "''") - print(f"🧐 Querying: {commits_url}") - - try: - count = conn.execute(f"SELECT COUNT(*) FROM read_parquet('{commits_url_escaped}')").fetchone()[0] - latest = conn.execute(f"SELECT MAX(committed_at_utc) FROM read_parquet('{commits_url_escaped}')").fetchone()[0] - pr_count = conn.execute(f"SELECT COUNT(*) FROM read_parquet('{prs_url_escaped}')").fetchone()[0] - - print(f"✅ Total compacted commits in R2: {count}") - print(f"✅ Latest compacted commit in R2: {latest}") - print(f"✅ Total compacted pull requests in R2: {pr_count}") - except Exception as e: - print(f"⚠️ Verification failed or no data: {e}") - # 初回実行時はデータがない可能性があるため、エラーにはせず警告に留める - finally: - conn.close() - PY - - - name: Upload logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: pipeline-logs-${{ github.run_number }} - path: | - *.log - logs/ - retention-days: 7 - if-no-files-found: ignore diff --git a/.github/workflows/job-ingest-google-youtube.yml b/.github/workflows/job-ingest-google-youtube.yml deleted file mode 100644 index eb86b5d..0000000 --- a/.github/workflows/job-ingest-google-youtube.yml +++ /dev/null @@ -1,152 +0,0 @@ -# name: Google YouTube Data Ingestion - -# on: -# schedule: -# - cron: '0 14 * * *' # 14:00 UTC = 23:00 JST -# workflow_dispatch: - -# jobs: -# ingest-google-youtube: -# runs-on: ubuntu-latest -# timeout-minutes: 60 -# env: -# R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} -# R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} -# R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} -# R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} -# YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY }} -# GOOGLE_COOKIE_ACCOUNT1: ${{ secrets.GOOGLE_COOKIE_ACCOUNT1 }} -# GOOGLE_COOKIE_ACCOUNT2: ${{ secrets.GOOGLE_COOKIE_ACCOUNT2 }} -# steps: -# - name: Checkout repository -# uses: actions/checkout@v4 - -# - name: Install uv -# uses: astral-sh/setup-uv@v4 -# with: -# enable-cache: true -# cache-dependency-glob: "uv.lock" - -# - name: Set up Python 3.13 -# run: uv python install 3.13 - -# - name: Install Playwright and dependencies -# run: | -# uv sync -# uv run playwright install --with-deps chromium - -# - name: Run Google YouTube ingestion -# run: uv run python -m ingest.google_activity.main - -# - name: Verify Parquet Data Integrity (R2) -# if: success() -# env: -# R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} -# R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} -# R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} -# R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} -# run: | -# uv run python - << 'PY' -# import duckdb -# import os - -# endpoint_url = os.environ["R2_ENDPOINT_URL"] -# access_key_id = os.environ["R2_ACCESS_KEY_ID"] -# secret_access_key = os.environ["R2_SECRET_ACCESS_KEY"] -# bucket_name = os.environ["R2_BUCKET_NAME"] - -# conn = duckdb.connect(":memory:") -# conn.execute("INSTALL httpfs; LOAD httpfs;") -# conn.execute(f""" -# CREATE SECRET ( -# TYPE S3, -# KEY_ID '{access_key_id}', -# SECRET '{secret_access_key}', -# REGION 'auto', -# ENDPOINT '{endpoint_url.replace("https://", "")}', -# URL_STYLE 'path' -# ); -# """) - -# parquet_url = f"s3://{bucket_name}/events/youtube/watch_history/**/*.parquet" -# print(f"🧐 Querying: {parquet_url}") - -# try: -# count = conn.execute(f"SELECT COUNT(*) FROM read_parquet('{parquet_url}')").fetchone()[0] -# latest = conn.execute(f"SELECT MAX(watched_at_utc) FROM read_parquet('{parquet_url}')").fetchone()[0] -# unique_videos = conn.execute(f"SELECT COUNT(DISTINCT video_id) FROM read_parquet('{parquet_url}')").fetchone()[0] - -# print(f"✅ Total watch events in R2: {count}") -# print(f"✅ Unique videos in R2: {unique_videos}") -# print(f"✅ Latest watch in R2: {latest}") -# except Exception as e: -# print(f"⚠️ Verification failed or no data: {e}") -# finally: -# conn.close() -# PY - -# - name: Verify YouTube Master Data Integrity (R2) -# if: success() -# env: -# R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} -# R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} -# R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} -# R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} -# R2_MASTER_PATH: ${{ secrets.R2_MASTER_PATH }} -# run: | -# uv run python - << 'PY' -# import duckdb -# import os - -# endpoint_url = os.environ["R2_ENDPOINT_URL"] -# access_key_id = os.environ["R2_ACCESS_KEY_ID"] -# secret_access_key = os.environ["R2_SECRET_ACCESS_KEY"] -# bucket_name = os.environ["R2_BUCKET_NAME"] -# master_path = os.getenv("R2_MASTER_PATH", "master/") -# if not master_path.endswith("/"): -# master_path = f"{master_path}/" - -# conn = duckdb.connect(":memory:") -# conn.execute("INSTALL httpfs; LOAD httpfs;") -# conn.execute(f""" -# CREATE SECRET ( -# TYPE S3, -# KEY_ID '{access_key_id}', -# SECRET '{secret_access_key}', -# REGION 'auto', -# ENDPOINT '{endpoint_url.replace("https://", "")}', -# URL_STYLE 'path' -# ); -# """) - -# videos_url = f"s3://{bucket_name}/{master_path}youtube/videos/**/*.parquet" -# channels_url = f"s3://{bucket_name}/{master_path}youtube/channels/**/*.parquet" -# print(f"🧐 Querying: {videos_url}") -# print(f"🧐 Querying: {channels_url}") - -# try: -# video_count = conn.execute( -# f"SELECT COUNT(*) FROM read_parquet('{videos_url}')" -# ).fetchone()[0] -# channel_count = conn.execute( -# f"SELECT COUNT(*) FROM read_parquet('{channels_url}')" -# ).fetchone()[0] - -# print(f"✅ Total video masters in R2: {video_count}") -# print(f"✅ Total channel masters in R2: {channel_count}") -# except Exception as e: -# print(f"⚠️ Master data verification failed or no data: {e}") -# finally: -# conn.close() -# PY - -# - name: Upload logs on failure -# if: failure() -# uses: actions/upload-artifact@v4 -# with: -# name: youtube-pipeline-logs-${{ github.run_number }} -# path: | -# *.log -# logs/ -# retention-days: 7 -# if-no-files-found: ignore diff --git a/.github/workflows/job-ingest-spotify.yml b/.github/workflows/job-ingest-spotify.yml deleted file mode 100644 index 9c6e7d6..0000000 --- a/.github/workflows/job-ingest-spotify.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Spotify Data Ingestion to R2 Data Lake - -on: - schedule: - # 深夜帯を薄くしつつ、日中帯を細かく収集する(50曲以上の取りこぼしを防ぐ) - - cron: '0 22 * * *' # 22:00 UTC = 07:00 JST - - cron: '0 2 * * *' # 02:00 UTC = 11:00 JST - - cron: '0 6 * * *' # 06:00 UTC = 15:00 JST - - cron: '0 10 * * *' # 10:00 UTC = 19:00 JST - - cron: '0 14 * * *' # 14:00 UTC = 23:00 JST - workflow_dispatch: - -jobs: - ingest: - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - enable-cache: true - cache-dependency-glob: "uv.lock" - - - name: Set up Python 3.13 - run: uv python install 3.13 - - - name: Install dependencies - run: | - uv sync --all-packages - - - name: Run Spotify DuckDB ingestion pipeline - env: - SPOTIFY_CLIENT_ID: ${{ secrets.SPOTIFY_CLIENT_ID }} - SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }} - SPOTIFY_REFRESH_TOKEN: ${{ secrets.SPOTIFY_REFRESH_TOKEN }} - DUCKDB_PATH: data/analytics.duckdb - R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} - R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} - LOG_LEVEL: INFO - run: | - uv run python -m ingest.spotify.main - - - name: Compact Spotify parquet datasets - env: - R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} - R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} - LOG_LEVEL: INFO - run: | - uv run python -m ingest.spotify.compact - - - name: Verify Compact Parquet Data Integrity (R2) - if: success() - env: - R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} - R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} - run: | - uv run python - << 'PY' - import duckdb - import os - - # R2設定を環境変数から直接読み取り - endpoint_url = os.environ["R2_ENDPOINT_URL"] - access_key_id = os.environ["R2_ACCESS_KEY_ID"] - secret_access_key = os.environ["R2_SECRET_ACCESS_KEY"] - bucket_name = os.environ["R2_BUCKET_NAME"] - conn = duckdb.connect(":memory:") - conn.execute("INSTALL httpfs; LOAD httpfs;") - conn.execute(f""" - CREATE SECRET ( - TYPE S3, - KEY_ID '{access_key_id}', - SECRET '{secret_access_key}', - REGION 'auto', - ENDPOINT '{endpoint_url.replace("https://", "")}', - URL_STYLE 'path' - ); - """) - - plays_url = f"s3://{bucket_name}/compacted/events/spotify/plays/**/*.parquet" - tracks_url = f"s3://{bucket_name}/compacted/master/spotify/tracks/**/*.parquet" - artists_url = f"s3://{bucket_name}/compacted/master/spotify/artists/**/*.parquet" - print(f"🧐 Querying: {plays_url}") - - try: - count = conn.execute(f"SELECT COUNT(*) FROM read_parquet('{plays_url}')").fetchone()[0] - latest = conn.execute(f"SELECT MAX(played_at_utc) FROM read_parquet('{plays_url}')").fetchone()[0] - track_count = conn.execute(f"SELECT COUNT(*) FROM read_parquet('{tracks_url}')").fetchone()[0] - artist_count = conn.execute(f"SELECT COUNT(*) FROM read_parquet('{artists_url}')").fetchone()[0] - - print(f"✅ Total compacted plays in R2: {count}") - print(f"✅ Latest compacted play in R2: {latest}") - print(f"✅ Total compacted tracks in R2: {track_count}") - print(f"✅ Total compacted artists in R2: {artist_count}") - except Exception as e: - print(f"⚠️ Verification failed or no data: {e}") - # 初回実行時はデータがない可能性があるため、エラーにはせず警告に留める - finally: - conn.close() - PY - - - name: Upload logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: pipeline-logs-${{ github.run_number }} - path: | - *.log - logs/ - retention-days: 7 - if-no-files-found: ignore diff --git a/.github/workflows/opencode-review-back.yml b/.github/workflows/opencode-review-back.yml deleted file mode 100644 index c359b4f..0000000 --- a/.github/workflows/opencode-review-back.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: OpenCode Review Back - -on: - workflow_dispatch: - inputs: - pr_number: - description: "PR number to process" - required: true - type: string - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - pull_request_review: - types: [submitted] - -jobs: - review-back: - timeout-minutes: 30 - concurrency: - group: review-back-opencode-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} - cancel-in-progress: true - - # 手動トリガー or PRコメント/レビューに /oc or /opencode が含まれる場合のみ - if: | - github.event_name == 'workflow_dispatch' || - ( - !endsWith(github.actor, '[bot]') && - !contains(github.event.comment.body || github.event.review.body || '', '') && - ( - contains(github.event.comment.body || '', '/oc') || - contains(github.event.comment.body || '', '/opencode') || - contains(github.event.review.body || '', '/oc') || - contains(github.event.review.body || '', '/opencode') - ) && - ( - github.event_name != 'issue_comment' || - github.event.issue.pull_request != null - ) - ) - - runs-on: ubuntu-latest - permissions: - id-token: write - contents: write - pull-requests: write - issues: write - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Resolve PR number - id: pr - shell: bash - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - PR_NUMBER="${{ inputs.pr_number }}" - elif [ "${{ github.event_name }}" = "issue_comment" ]; then - PR_NUMBER="${{ github.event.issue.number }}" - elif [ "${{ github.event_name }}" = "pull_request_review" ]; then - PR_NUMBER="${{ github.event.pull_request.number }}" - else - PR_NUMBER="${{ github.event.pull_request.number }}" - fi - - if [ -z "$PR_NUMBER" ]; then - echo "PR number could not be resolved" >&2 - exit 1 - fi - - echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - - - name: Checkout PR branch - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - gh pr checkout "${{ steps.pr.outputs.pr_number }}" - - - name: Run OpenCode review-back (GLM-4.7) - uses: anomalyco/opencode/github@latest - env: - ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }} - with: - model: zai-coding-plan/glm-4.7 - share: false - prompt: "pr-review-back-workflow Skillをロードし、指示に従って作業を進めてください。ここはGithub Action環境のため、ユーザーと対話することはできません" From 6c49c57f9220a31b11c200cd58a450a859430826 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 15:49:10 +0000 Subject: [PATCH 02/14] docs: add plexus runtime and operations guides --- .../openclaw_worker_orchestration_spec_v2.md | 715 +++++++++++++++++ docs/40.deploy/frontend-android.md | 81 ++ .../fcm-token-management-production.md | 273 +++++++ .../firebase-fcm-notification-architecture.md | 97 +++ docs/70.knowledge/mvvm-architecture-guide.md | 726 ++++++++++++++++++ docs/70.knowledge/webhook-guide.md | 677 ++++++++++++++++ 6 files changed, 2569 insertions(+) create mode 100644 docs/00.requirements/openclaw_worker_orchestration_spec_v2.md create mode 100644 docs/40.deploy/frontend-android.md create mode 100644 docs/70.knowledge/fcm-token-management-production.md create mode 100644 docs/70.knowledge/firebase-fcm-notification-architecture.md create mode 100644 docs/70.knowledge/mvvm-architecture-guide.md create mode 100644 docs/70.knowledge/webhook-guide.md diff --git a/docs/00.requirements/openclaw_worker_orchestration_spec_v2.md b/docs/00.requirements/openclaw_worker_orchestration_spec_v2.md new file mode 100644 index 0000000..d1a5b0a --- /dev/null +++ b/docs/00.requirements/openclaw_worker_orchestration_spec_v2.md @@ -0,0 +1,715 @@ +--- +title: OpenClaw 外部 Worker オーケストレーション仕様書 v2 +aliases: + - OpenClaw Worker Orchestration Spec v2 + - OpenClaw Orchestration Gateway Spec v2 +tags: + - openclaw + - gateway + - orchestration + - tmux + - git-worktree + - sqlite + - worker + - control-plane +status: draft +version: 0.2.0 +created: 2026-03-25 +updated: 2026-03-25 +supersedes: + - docs/00.project/features/openclaw_worker_orchestration_spec.md +--- + +# OpenClaw 外部 Worker オーケストレーション仕様書 v2 + +## 1. Summary + +本仕様は、OpenClaw を「会話・判断」に特化させ、実際の開発作業を外部 Worker に委譲するための実装可能な v2 設計を定義する。 + +v2 の主眼は以下である。 + +- 司令塔と実行基盤を明確に分離する +- Gateway 内に `terminal surface` と `orchestration surface` を分離配置する +- Task ではなく **Task Attempt** を実行の最小単位として扱う +- Worker の二重実行を防ぐため、**claim / lease / heartbeat / reconcile** を導入する +- tmux は観測・介入可能な実行基盤として使い、attempt 単位で隔離する +- git worktree は attempt 単位で分離し、作業衝突を避ける +- 初期構成は単一 Gateway ノード + SQLite で完結させる +- 将来の別 repo / 別 service 化に耐える境界で設計する + +--- + +## 2. Goal / Non-Goal + +### 2.1 Goal + +- OpenClaw Main / Adviser が実装作業をせずに Worker を指揮できる +- Gateway が Worker 実行の control plane として振る舞える +- Worker が task を安全に claim し、attempt 単位で実行できる +- task 状態、question、artifact、監査情報を durable に追跡できる +- 人間が必要時のみ tmux attach で介入できる +- 将来、orchestration surface だけを別 repo に分離できる + +### 2.2 Non-Goal + +- 複数 Gateway ノードによる分散協調 +- マルチテナント運用 +- 大規模ジョブスケジューラ相当の汎用 orchestrator 化 +- Worker 内部 CLI の詳細標準化 +- AgentMail の本実装 +- 本番 CI/CD や secrets manager の最終決定 + +--- + +## 3. Design Principles + +1. **会話と実行を分離する** +OpenClaw は会話・判断、Gateway は制御、Worker は実行を担う。 + +2. **Task と Attempt を分離する** +Task は依頼の論理単位、Attempt は実行の物理単位とする。 + +3. **実行権は lease で管理する** +Worker は task を claim して lease を取得した attempt のみ実行できる。 + +4. **tmux は身体、SQLite は durable state、Gateway は reconcile 役である** +真実は DB 単体に閉じず、Gateway が外部実態との差分を収束させる。 + +5. **人間介入は常に可能にする** +自動化を優先するが、途中 attach・確認・再開の経路を必ず残す。 + +6. **今は同 repo、将来は分離可能にする** +モジュール境界・API 境界・DB 境界は最初から分ける。 + +--- + +## 4. System Context + +```text +[ User ] + | + v +[ OpenClaw ] + |- Main + \- Adviser + | + v +[ Gateway ] + |- Terminal Surface + | \- human terminal access + | + \- Orchestration Surface + |- Task API + |- Dispatcher + |- Worker Registry + |- Lease Manager + |- State Manager + |- Reconciler + \- SQLite + | + v +[ Worker LXC ] + |- runtime wrapper + |- git worktree + |- tmux session + \- gateway client +``` + +### 4.1 Terminal Surface + +既存の mobile terminal gateway に近い責務を持つ。 + +- tmux session 一覧 +- attach / resize / input / output +- snapshot +- push notification +- 人間向け認証 + +### 4.2 Orchestration Surface + +本仕様の主役。Worker 制御に特化する。 + +- task 作成 +- worker claim / assign +- attempt 生成 +- lease / heartbeat +- question / escalation +- artifact / receipt 記録 +- reconcile + +### 4.3 境界ルール + +- Terminal Surface は task scheduling を持たない +- Orchestration Surface は人間向け terminal UX を持たない +- 共通化するのは tmux 操作、ログ基盤、最低限の認証ユーティリティまでとする + +--- + +## 5. Architecture Decision + +### 5.1 採用方針 + +`gateway` リポジトリ内に orchestration surface を追加する。ただし内部構造は独立 service に近い境界で切る。 + +### 5.2 採用理由 + +- いま gateway / repo を増やしすぎない +- 既存の tmux / tailscale / notification の知見を活用できる +- 将来の OpenClaw 寄せに向けた足場を作れる + +### 5.3 将来の分離条件 + +以下が複数満たされた場合、orchestration surface の別 repo / 別 service 化を検討する。 + +- DB スキーマが terminal surface と明確に分離された +- 主な利用者が人間ではなく Worker / OpenClaw になった +- デプロイ頻度や障害影響範囲を分けたい +- mobile terminal 側の変更と独立してリリースしたい +- 共通部分が tmux adapter 程度まで縮退した + +--- + +## 6. Core Domain Model + +### 6.1 Task + +ユーザーや Main が依頼した論理作業単位。複数回の実行を内包しうる。 + +例: + +- 認証リフレッシュ処理の修正 +- Gateway の接続エラー調査 + +### 6.2 Task Attempt + +Worker が実際に task を引き受けて走らせる物理単位。tmux session、worktree、lease は attempt に紐づく。 + +例: + +- 1回目の実行で crash +- 2回目の再試行で別 Worker が再実行 + +### 6.3 Worker + +task を claim して attempt を実行するエージェント実行ノード。 + +### 6.4 Lease + +attempt を実行する権利。一定時間ごとに heartbeat で延長され、失効すると再割当可能になる。 + +### 6.5 Question + +task / attempt 実行中に解決不能となった論点。内部回答とユーザー回答を区別する。 + +### 6.6 Artifact / Receipt + +- Artifact: patch, log summary, test report, build result などの成果物 +- Receipt: claim, attach, retry, escalation などの監査記録 + +--- + +## 7. Lifecycle + +### 7.1 Task Lifecycle + +```text +draft -> queued -> in_progress -> completed + | | + v v + blocked failed + | + v + awaiting_answer / awaiting_review +``` + +### 7.2 Attempt Lifecycle + +```text +created -> claimed -> preparing -> running -> succeeded + | | | + v v v + failed blocked superseded + | + v + expired +``` + +### 7.3 重要ルール + +- Task は複数 Attempt を持てる +- 同時に `running` になれる Attempt は原則 1つ +- 新 Attempt 作成時、旧 Attempt は `superseded` または `expired` に遷移する +- tmux session と worktree は Attempt に 1対1 で対応する + +--- + +## 8. Claim / Lease / Heartbeat / Reconcile + +### 8.1 Claim + +Worker は `queued` task を直接実行してはならない。まず Gateway に claim を要求する。 + +Gateway は以下を原子的に行う。 + +1. task が claim 可能か確認 +2. attempt を作成 +3. lease を発行 +4. task を `in_progress` に遷移 +5. claim receipt を記録 + +### 8.2 Lease + +- lease は短命とし、heartbeat で延長する +- 失効した attempt は実行権を失う +- lease を失った Worker は新規変更を書き込んではならない + +### 8.3 Heartbeat + +Worker は一定間隔で heartbeat を送る。 + +Heartbeat に含めるもの: + +- attempt_id +- worker_id +- lease_id +- status summary +- current phase +- optional tmux session health + +### 8.4 Reconcile + +Gateway は定期的に以下を確認し、DB 状態と実態を収束させる。 + +- heartbeat が途絶えた attempt +- tmux session の存在 +- worktree path の存在 +- 完了 webhook 未反映 +- claim 済みだが preparing から進まない attempt + +Reconcile は「DB が絶対」ではなく、「DB を運用上正しい状態に戻す」責務を持つ。 + +--- + +## 9. tmux / git worktree Policy + +### 9.1 tmux Policy + +- tmux session は Attempt 単位で作成する +- 別 task / 別 attempt で tmux session を再利用しない +- 再利用は「同一 attempt の resume」のみ許可する +- session 名は attempt を含む一意名とする + +推奨形式: + +`wk--` + +### 9.2 worktree Policy + +- worktree は Attempt 単位で作成する +- path は task_id ではなく attempt_id を含める +- attempt が終了しても即削除せず、保守期間を設ける +- cleanup は Gateway ではなく worker runtime か専用 cleaner が担う + +推奨形式: + +`/srv/worktrees///` + +### 9.3 branch Policy + +- branch は Task 単位で維持する +- 同一 task の再試行 attempt は原則同一 branch を継続利用する +- 新 attempt は既存 branch を fetch し直したうえで作業を継続する + +推奨形式: + +`task/` + +### 9.4 理由 + +- tmux は実行隔離が重要 +- branch は論理成果物の継続性が重要 +- worktree は物理隔離が重要 + +この3者は同じ粒度に揃えない。 + +--- + +## 10. State Model + +### 10.1 Task Status + +- `draft` +- `queued` +- `in_progress` +- `awaiting_internal_answer` +- `awaiting_user_answer` +- `awaiting_human_review` +- `blocked` +- `completed` +- `failed` +- `cancelled` + +### 10.2 Attempt Status + +- `created` +- `claimed` +- `preparing` +- `running` +- `blocked` +- `succeeded` +- `failed` +- `expired` +- `superseded` +- `cancelled` + +### 10.3 状態設計ルール + +- Task status はユーザーに見せる要約状態 +- Attempt status は運用・実行状態 +- Task の `blocked` は、「有効 attempt が進められない」ことを意味する +- Task を `completed` にできるのは、有効 attempt が `succeeded` になった時のみ + +--- + +## 11. Data Model + +### 11.1 tasks + +```sql +CREATE TABLE tasks ( + task_id TEXT PRIMARY KEY, + parent_task_id TEXT, + title TEXT NOT NULL, + description TEXT, + requested_by TEXT NOT NULL, + repo_name TEXT, + repo_path TEXT, + branch_name TEXT, + priority INTEGER NOT NULL DEFAULT 50, + risk_level TEXT NOT NULL DEFAULT 'normal', + status TEXT NOT NULL, + latest_attempt_id TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); +``` + +### 11.2 task_attempts + +```sql +CREATE TABLE task_attempts ( + attempt_id TEXT PRIMARY KEY, + task_id TEXT NOT NULL, + worker_id TEXT, + lease_id TEXT, + status TEXT NOT NULL, + tmux_session TEXT, + worktree_path TEXT, + run_branch_name TEXT, + started_at TEXT, + finished_at TEXT, + last_heartbeat_at TEXT, + failure_reason TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(task_id) +); +``` + +### 11.3 workers + +```sql +CREATE TABLE workers ( + worker_id TEXT PRIMARY KEY, + worker_type TEXT NOT NULL, + host_name TEXT NOT NULL, + lxc_name TEXT NOT NULL, + status TEXT NOT NULL, + capabilities_json TEXT, + current_attempt_id TEXT, + last_heartbeat_at TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); +``` + +### 11.4 leases + +```sql +CREATE TABLE leases ( + lease_id TEXT PRIMARY KEY, + attempt_id TEXT NOT NULL, + worker_id TEXT NOT NULL, + expires_at TEXT NOT NULL, + status TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY(attempt_id) REFERENCES task_attempts(attempt_id) +); +``` + +### 11.5 task_events + +```sql +CREATE TABLE task_events ( + event_id TEXT PRIMARY KEY, + task_id TEXT NOT NULL, + attempt_id TEXT, + event_type TEXT NOT NULL, + actor TEXT NOT NULL, + payload_json TEXT, + created_at TEXT NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(task_id) +); +``` + +### 11.6 questions + +```sql +CREATE TABLE questions ( + question_id TEXT PRIMARY KEY, + task_id TEXT NOT NULL, + attempt_id TEXT, + target TEXT NOT NULL, + kind TEXT NOT NULL, + status TEXT NOT NULL, + body TEXT NOT NULL, + answer TEXT, + asked_by TEXT NOT NULL, + answered_by TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(task_id) +); +``` + +### 11.7 artifacts / receipts + +artifact と receipt は v1 案を踏襲するが、attempt_id を追加して物理実行と結びつける。 + +--- + +## 12. API Design + +### 12.1 OpenClaw / Main 向け + +- `POST /api/orchestration/tasks` +- `GET /api/orchestration/tasks/{task_id}` +- `GET /api/orchestration/tasks/{task_id}/events` +- `POST /api/orchestration/questions/{question_id}/answer` + +### 12.2 Worker 向け + +- `POST /api/orchestration/workers/register` +- `POST /api/orchestration/workers/{worker_id}/claim-next` +- `POST /api/orchestration/attempts/{attempt_id}/heartbeat` +- `POST /api/orchestration/attempts/{attempt_id}/events` +- `POST /api/orchestration/attempts/{attempt_id}/complete` +- `POST /api/orchestration/attempts/{attempt_id}/fail` +- `POST /api/orchestration/attempts/{attempt_id}/question` + +### 12.3 Human Ops 向け + +- `POST /api/orchestration/tasks/{task_id}/cancel` +- `POST /api/orchestration/attempts/{attempt_id}/force-expire` +- `POST /api/orchestration/attempts/{attempt_id}/reconcile` + +### 12.4 設計ルール + +- state mutation API は idempotency key を受け取る +- event は client-generated `event_id` を必須とする +- attempt に紐づく更新は `attempt_id + lease_id` で認証・検証する +- Worker は task status を直接書き換えず、attempt API を通じて反映する + +--- + +## 13. Question / Approval Policy + +### 13.1 原則 + +通常の inner loop は自動で進める。ただし外部影響は厳密に分離する。 + +### 13.2 自動で進めてよいもの + +- ローカルファイル編集 +- lint / format / unit test +- ローカル build +- static analysis +- docs 変更 +- patch / report / artifact 生成 + +### 13.3 自動で進めてはいけないもの + +- 外部ネットワーク送信を伴う操作 +- 共有環境への書き込み +- package publish +- public push / PR 作成 +- 本番 DB 変更 +- secrets 参照 / 更新 +- destructive migration +- 課金発生操作 + +### 13.4 質問優先順位 + +1. Worker 自力解決 +2. Main / Adviser に内部質問 +3. それでも解けない場合のみユーザー質問 + +### 13.5 Human Review 条件 + +- 外部へ出る変更 +- security-sensitive な変更 +- 不可逆操作 +- user-specific な判断が必要な場合 + +--- + +## 14. Failure Handling + +### 14.1 Worker Crash + +- heartbeat timeout で lease 失効 +- attempt を `blocked` または `expired` に遷移 +- reconcile が tmux / worktree の残骸を確認 +- 必要なら新 attempt を作成 + +### 14.2 Duplicate Execution + +- lease により防ぐ +- 二重 claim を検知した場合、後発 attempt を `superseded` にする +- receipt と event に必ず記録する + +### 14.3 tmux 残存 + +- tmux session が残っても lease が切れていれば再開不可 +- 同一 attempt resume は明示オペレーションでのみ許可する + +### 14.4 Webhook 遅延 / 重複 + +- idempotency key で吸収する +- completion より古い blocked event は破棄する + +### 14.5 Reconcile Failure + +- reconcile 自体の結果を receipt に残す +- 自動修復不能なら `awaiting_human_review` に昇格する + +--- + +## 15. Security Model + +### 15.1 認証境界 + +- Terminal Surface: 人間クライアント認証 +- Orchestration Surface: Worker / OpenClaw 用の machine auth + +同じ gateway 配下でも認証方式と権限境界は分離する。 + +### 15.2 最小権限 + +- Worker は自分の claim した attempt のみ更新可能 +- Main は task / question 参照と回答は可能、tmux attach は不要 +- 人間 Ops は force 操作を持つ + +### 15.3 Tailscale の役割 + +- 通常バスではなく保守経路 +- 人間の attach / SSH / 障害調査に用いる + +--- + +## 16. Technology Selection + +### 16.1 採用 + +- **Gateway framework**: 既存 `gateway` を拡張 +- **Runtime session**: tmux +- **Workspace isolation**: git worktree +- **Durable state**: SQLite +- **Transport**: HTTP + WebSocket + webhook + +### 16.2 採用理由 + +- tmux: 観測・再接続・人間介入に強い +- git worktree: 並列 task の物理分離に強い +- SQLite: 単一ノード control plane として十分軽量 +- HTTP / WebSocket / webhook: 現行資産と相性が良い + +### 16.3 不採用 + +- Postgres: v2 では早い +- Redis を primary state store とする案: 監査性が弱い +- Kubernetes Job / Temporal などの重量 orchestrator: 要件に対して過剰 +- 自前 PTY supervision: tmux より実装負荷が高い + +### 16.4 将来の移行条件 + +- 複数 Gateway ノード運用が必要になった +- SQLite の lock / throughput が支配的制約になった +- lease 管理の一貫性要件が上がった + +この場合は Postgres 移行を第一候補とする。 + +--- + +## 17. Phase Plan + +### Phase 1 + +最小 orchestration surface を作る。 + +- task / attempt / worker / lease schema +- worker register / claim / heartbeat / complete API +- tmux / worktree attempt 単位運用 +- reconcile 最小版 + +### Phase 2 + +OpenClaw Main / Adviser 連携を強化する。 + +- question flow +- internal answer / user escalation +- result summarization + +### Phase 3 + +運用機能を整える。 + +- human ops API +- artifact preview +- richer receipts +- cleaner / retention + +### Phase 4 + +必要に応じて別 repo / 別 service 化を検討する。 + +--- + +## 18. Acceptance Criteria + +- Main が task を作成できる +- Worker が task を claim し、attempt / lease を取得できる +- 同一 task の二重実行が防止される +- attempt ごとに tmux session と worktree が分離される +- heartbeat timeout により stale attempt を失効できる +- reconcile により不整合を検知できる +- question が internal / user / human review に正しく振り分けられる +- human が必要時のみ tmux attach で介入できる + +--- + +## 19. Open Questions + +- secrets 配布方式 +- Worker capability routing の詳細 +- branch を task 単位に固定するか、attempt ごとに派生させるか +- artifact retention 期間 +- AgentMail mirror の詳細粒度 +- OpenClaw Main / Adviser の prompt / skill 設計 + +--- + +## 20. One-Sentence Summary + +**Gateway 内に orchestration surface を新設し、Task と Attempt を分離した lease ベース制御で Worker を運用する。tmux は attempt 単位の実行基盤、git worktree は attempt 単位の作業分離、SQLite は単一ノード control plane の durable state として用い、将来の別 repo 分離に耐える境界で設計する。** diff --git a/docs/40.deploy/frontend-android.md b/docs/40.deploy/frontend-android.md new file mode 100644 index 0000000..3e9cc94 --- /dev/null +++ b/docs/40.deploy/frontend-android.md @@ -0,0 +1,81 @@ +# Frontend Deploy (Android) + +本番フロントエンドを Android アプリ(KMP)としてビルド・デプロイする手順。 +Kotlin Multiplatform + Compose Multiplatform を使用し、Android ネイティブアプリとして配布する。 + +## 1. 前提条件 + +- **JDK**: 17 以上(推奨: JDK 21) +- **Android SDK**: API 34(コマンドラインツール) +- **Gradle**: 8.8+ (Wrapper 同梱) + +### 1.1 Android SDK セットアップ + +Android Studio を使わずに CLI で開発する場合: + +```bash +# Android SDK Command-line Tools をダウンロード +# https://developer.android.com/studio#command-tools + +# SDK マネージャーで必要なパッケージをインストール +sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" + +# 環境変数設定 +export ANDROID_HOME=$HOME/Android/Sdk +export PATH=$PATH:$ANDROID_HOME/platform-tools +``` + +## 2. ビルド手順 + +### 2.1 デバッグビルド + +```bash +cd frontend +./gradlew :androidApp:assembleDebug +# 成果物: androidApp/build/outputs/apk/debug/androidApp-debug.apk +``` + +### 2.2 リリースビルド + +署名付きリリースビルドを作成するには、キーストアが必要です。 + +#### A. キーストア作成(初回のみ) + +```bash +keytool -genkey -v \ + -keystore release.keystore \ + -alias egograph \ + -keyalg RSA -keysize 2048 -validity 10000 +``` + +#### B. ビルド実行 + +環境変数を設定してビルドします。 + +```bash +export KEYSTORE_PASSWORD="your-password" +export KEY_PASSWORD="your-password" + +./gradlew :androidApp:assembleRelease +# 成果物: androidApp/build/outputs/apk/release/androidApp-release.apk +``` + +## 3. インストール + +### デバイスへのインストール + +```bash +./gradlew :androidApp:installDebug +``` + +## 4. CI/CD + +`ci-frontend.yml` ワークフローにより、GitHub Actions 上で自動テストとビルドが行われます。 + +## 5. 旧手順(Capacitor) + +React + Capacitor 時代のデプロイ手順は、分離済み legacy repo を前提に以下を参照してください: + +- [Legacy deploy doc](https://github.com/endo-ava/egograph-frontend-capacitor-legacy/blob/main/docs/40.deploy/frontend-android-capacitor.md) +- [Legacy Capacitor architecture doc](https://github.com/endo-ava/egograph-frontend-capacitor-legacy/blob/main/docs/40.deploy/capacitor.md) +- External repo: diff --git a/docs/70.knowledge/fcm-token-management-production.md b/docs/70.knowledge/fcm-token-management-production.md new file mode 100644 index 0000000..ac4428d --- /dev/null +++ b/docs/70.knowledge/fcm-token-management-production.md @@ -0,0 +1,273 @@ +# FCM Token Management - Production Design + +## 本番環境での一般的な設計 + +### 現状の課題 + +- **開発環境**: Logcatでトークン確認(開発者のみ) +- **本番環境**: トークンが見えない、管理できない + +--- + +## 一般的な設計パターン + +### パターン1: 自動登録 + 管理画面(推奨) + +**フロー:** +```text +1. アプリ起動 → FCMトークン取得 +2. 自動でGatewayに登録 +3. 管理画面でトークン一覧を確認 +4. 通知テスト機能で動作確認 +``` + +**実装:** +- Androidアプリ: 現在の自動登録を維持 +- Gateway: 管理用APIエンドポイントを追加 +- 管理画面: トークン一覧・削除・テスト通知機能 + +### パターン2: ダッシュボード + 監視 + +**フロー:** +```text +1. アプリで自動登録 +2. 管理ダッシュボードで登録状況を可視化 +3. 通知送信数・成功率をモニタリング +4. エラートークンを自動検出 +``` + +**実装:** +- Grafana + Prometheus で監視 +- GatewayのDBを直接参照 + +### パターン3: ユーザー管理画面 + +**フロー:** +``` +1. ユーザーがWeb画面で自身のデバイスを管理 +2. デバイス名を変更・削除 +3. 通知設定をON/OFF +4. 通知履歴を確認 +``` + +--- + +## 推奨実装:管理APIの追加 + +### Gatewayに管理用エンドポイントを追加 + +#### GET /v1/push/devices + +登録済みデバイス一覧を取得 + +```bash +curl -X GET http://localhost:8001/v1/push/devices \ + -H "X-API-Key: $GATEWAY_API_KEY" +``` + +**レスポンス:** +```json +{ + "devices": [ + { + "id": 1, + "user_id": "default_user", + "device_name": "Pixel 6", + "platform": "android", + "enabled": true, + "last_seen_at": "2026-02-25T14:00:00", + "created_at": "2026-02-25T10:00:00" + } + ], + "total": 1 +} +``` + +#### DELETE /v1/push/devices/:id + +デバイスを削除 + +```bash +curl -X DELETE http://localhost:8001/v1/push/devices/1 \ + -H "X-API-Key: $GATEWAY_API_KEY" +``` + +#### POST /v1/push/test + +テスト通知を送信 + +```bash +curl -X POST http://localhost:8001/v1/push/test \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $GATEWAY_API_KEY" \ + -d '{ + "title": "テスト通知", + "body": "これはテストです" + }' +``` + +--- + +## Android側の改善 + +### 1. トークン登録成功時のUI通知 + +```kotlin +// FcmTokenManager.kt +private fun registerTokenWithRetry(...) { + while (attempt < MAX_RETRY_COUNT) { + try { + sendTokenToGateway(token, deviceName) + currentToken = token + Log.i(TAG, "FCM token registered successfully") + + // ユーザーに通知(初回登録時のみ) + if (attempt == 0) { + showTokenRegisteredNotification() + } + return + } catch (e: IOException) { + // リトライ処理 + } + } +} + +private fun showTokenRegisteredNotification() { + // 通知チャンネルで「プッシュ通知が有効になりました」と表示 +} +``` + +### 2. 設定画面でトークン状態を表示 + +```kotlin +// GatewaySettingsScreen.kt +@Composable +fun PushNotificationStatus() { + val isRegistered = remember { mutableStateOf(false) } + + Column { + Text("プッシュ通知: ${if (isRegistered.value) "有効" else "無効"}") + + if (!isRegistered.value) { + Button(onClick = { /* トークン再取得 */ }) { + Text("有効化する") + } + } + } +} +``` + +--- + +## 本番環境での運用フロー + +### 初期セットアップ + +1. **アプリをインストール** + - 初回起動時にFCMトークンを自動取得 + - Gatewayに自動登録 + - 通知で「プッシュ通知が有効になりました」を表示 + +2. **管理者が確認** + - 管理APIで登録済みデバイスを確認 + - テスト通知を送信して動作確認 + +3. **監視** + - 通知送信成功率をモニタリング + - 失敗したトークンを自動検出 + +### 運用中の管理 + +**定期タスク:** +- 週1回: アクティブデバイス数を確認 +- 月1回: 未使用デバイス(30日以上アクセスなし)を削除 +- エラートークンが増えたら調査 + +**トラブルシューティング:** +- 通知が届かない: 管理画面でデバイスが有効か確認 +- トークン無効エラー: アプリを再起動して再登録 + +--- + +## セキュリティ考慮事項 + +### FCMトークンの取り扱い + +**FCMトークンは機密情報ではありませんが、以下の点に注意:** + +- ✅ トークンをログに出力してもOK(開発時のみ) +- ❌ 本番環境のログに出力しない +- ✅ データベースに暗号化せず保存してOK +- ❌ ログファイルを外部に公開しない + +**理由:** +- FCMトークンは公開情報で、誰でも取得可能 +- ただし、トークンがあれば誰でも通知を送れる可能性がある +- そのため、Gateway API Keyで保護する必要がある + +### アクセス制御 + +```python +# 管理APIはGateway API Keyで保護 +async def list_devices(request: Request): + await verify_gateway_request(request) # X-API-Key チェック + + # 管理者のみアクセス可能にする場合 + # user_id = get_user_id_from_token(request) + # if user_id != "admin": + # raise HTTPException(403, "Admin only") +``` + +--- + +## 他社事例 + +### Slack + +- ユーザー設定画面で「モバイル通知」をON/OFF +- デバイス管理画面で登録済みデバイスを一覧表示 +- 各デバイスからテスト通知を送信可能 + +### Discord + +- ユーザー設定で通知設定を管理 +- サーバーごとに通知設定を変更可能 +- 通知履歴を確認可能 + +### GitHub + +- Settings > Notifications で通知設定を管理 +- デバイスは非公開(裏で自動登録) +- 通知が届かない場合のトラブルシューティングガイドあり + +--- + +## 推奨アーキテクチャ + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Android │────────→│ Gateway API │────────→│ FCM │ +│ App │ Token │ │ Push │ │ +└─────────────┘ └──────────────┘ └─────────────┘ + ↑ + │ Admin API + │ + ┌──────┴──────┐ + │ Admin Panel │ + │ (Optional) │ + └─────────────┘ +``` + +**フェーズ1(現在):** 基本機能 +- ✅ 自動トークン登録 +- ✅ Webhookで通知送信 + +**フェーズ2(推奨):** 管理機能 +- 🔲 管理用APIエンドポイント +- 🔲 デバイス一覧確認 +- 🔲 テスト通知機能 + +**フェーズ3(将来的):** ユーザー管理 +- 🔲 Web管理画面 +- 🔲 ユーザー設定画面 +- 🔲 通知履歴表示 diff --git a/docs/70.knowledge/firebase-fcm-notification-architecture.md b/docs/70.knowledge/firebase-fcm-notification-architecture.md new file mode 100644 index 0000000..04f34f2 --- /dev/null +++ b/docs/70.knowledge/firebase-fcm-notification-architecture.md @@ -0,0 +1,97 @@ +# Firebase / FCM 通知アーキテクチャ + +このドキュメントは、EgoGraph の通知機能における Firebase / FCM の役割と、実装上の責務分離を整理したものです。 +Webhook の手順詳細は `docs/70.knowledge/webhook-guide.md` を参照してください。 + +## Firebase とは + +Firebase は Google が提供する BaaS 群です。 +本プロジェクトでは、そのうち **Firebase Cloud Messaging (FCM)** を通知配信経路として利用しています。 + +## なぜこのプロジェクトで FCM を使うか + +- Android 端末へのプッシュ通知配信を標準経路で実現できる +- 端末トークンを使ったサーバー送信モデルに適合している +- Gateway 側で通知送信を一元化でき、Webhook イベントと接続しやすい + +## セキュリティ境界(重要) + +### Frontend 側 + +- ファイル: `frontend/androidApp/google-services.json` +- 用途: クライアント SDK 初期化、FCM トークン取得 +- 注意: これは **Admin 権限キーではない** + +### Gateway 側 + +- ファイル: `gateway/firebase-service-account.json`(既定) +- 環境変数: `FCM_CREDENTIALS_PATH` +- 用途: Firebase Admin SDK による通知送信 +- 注意: これは **秘密鍵を含む機密情報**(Git 管理禁止) + +結論: + +- `google-services.json` とサービスアカウント JSON は別物 +- フロントとバックに同じ鍵を置く設計は不可 + +## このリポジトリでの通知フロー + +### 1) 端末トークン登録 + +1. Android アプリが FCM トークンを取得 +2. `PUT /v1/push/token` へ登録(`X-API-Key` 必須) +3. Gateway が `push_devices` テーブルへ保存 + +関連実装: + +- `frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmService.kt` +- `frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmTokenManager.kt` +- `gateway/api/push.py` (`register_token`) +- `gateway/infrastructure/repositories.py` (`PushTokenRepository.save_token`) + +### 2) Webhook から通知送信 + +1. 外部システムが `POST /v1/push/webhook` を呼ぶ(`X-Webhook-Secret` 必須) +2. Gateway が有効トークンを取得 +3. `FcmService` が Firebase Admin SDK で送信 +4. 失敗トークンは無効化処理に回す + +関連実装: + +- `gateway/api/push.py` (`send_webhook`) +- `gateway/services/fcm_service.py` +- `gateway/domain/models.py` + +## 必須/重要な環境変数 + +- `GATEWAY_API_KEY`: 端末トークン登録 API の認証 +- `GATEWAY_WEBHOOK_SECRET`: Webhook API の認証 +- `FCM_PROJECT_ID`: Firebase プロジェクト ID +- `FCM_CREDENTIALS_PATH`: サービスアカウント JSON パス(既定: `gateway/firebase-service-account.json`) +- `GATEWAY_HOST`, `GATEWAY_PORT`: Gateway バインド設定 +- `GATEWAY_RELOAD`: 開発時のみ `true`(通常運用は `false`) + +## よくある障害と切り分け + +### `success_count=0, failure_count=0` + +- 有効トークン未登録の可能性が高い +- `push_devices` の `enabled=TRUE` を確認 + +### `failure_count` がトークン数ぶん増える + +- FCM 未初期化、または認証情報不備 +- Gateway ログで以下を確認: + - `FCM project ID not configured` + - `Failed to initialize Firebase Admin SDK` + +### Gateway が固まる / health timeout + +- `GATEWAY_RELOAD=true` による監視負荷を疑う +- 運用では `GATEWAY_RELOAD=false` を使用 + +## 運用メモ + +- 起動は `uv run python -m gateway.main` を使用(実行ディレクトリを固定) +- キー更新時は `GATEWAY_API_KEY` / `GATEWAY_WEBHOOK_SECRET` を同時ローテーション +- サービスアカウント JSON はリポジトリ外管理を推奨 diff --git a/docs/70.knowledge/mvvm-architecture-guide.md b/docs/70.knowledge/mvvm-architecture-guide.md new file mode 100644 index 0000000..1b13cbd --- /dev/null +++ b/docs/70.knowledge/mvvm-architecture-guide.md @@ -0,0 +1,726 @@ +# MVVMアーキテクチャガイド(React開発者向け) + +このドキュメントは、EgoGraph フロントエンドで採用している MVVM (Model-View-ViewModel) アーキテクチャについて、React 開発者の視点から解説したものです。 + +--- + +## 1. MVVM とは + +**MVVM** は、UI アプリケーションを3つのレイヤーに分割する設計パターンです。 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ MVVM アーキテクチャ │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │ +│ │ View │───▶│ ViewModel │───▶│ Model │ │ +│ │ (UI層) │ │ (ロジック層) │ │ (データ層) │ │ +│ │ │◀───│ │◀───│ │ │ +│ │ • UI描画 │ │ • 状態管理 │ │ • データ │ │ +│ │ • ユーザー操作 │ │ • ビジネスロジック│ │ • API通信 │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────┘ │ +│ │ │ │ +│ │ observe │ update │ +│ ▼ ▼ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ State │ │ Effect │ │ +│ │ (状態) │ │ (イベント)│ │ +│ └─────────┘ └─────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. React との用語対応 + +| 概念 | React | Kotlin (Compose Multiplatform) | +|------|-------|-------------------------------| +| **コンポーネント** | コンポーネント関数 | `@Composable` 関数 | +| **状態** | `useState` | `StateFlow` / `MutableStateFlow` | +| **副作用** | `useEffect` | `LaunchedEffect` | +| ** Props** | 関数引数 | `@Composable` 関数引数 | +| **イベントハンドラ** | `onClick={...}` | `onClick = { ... }` | +| **状態ホイスティング** | 親コンポーネントで管理 | ViewModelで管理 | +| **コンテキスト** | `useContext` | Koin (DIコンテナ) | + +--- + +## 3. 各レイヤーの詳細 + +### 3.1 Model(データ層) + +**担当**: データの永続化、API通信、ビジネスロジック + +```kotlin +// Repositoryインターフェース +interface ThreadRepository { + fun getThreads(limit: Int, offset: Int): Flow> + fun getThread(threadId: String): Flow> +} +``` + +**React での相当概念**: +- Redux の `thunk` / `async thunks` +- React Query (`useQuery`) +- SWR + +**主な違い**: +- Kotlin は `Flow` (ストリーム) でデータを扱う +- `Result` で成功/失敗を表現(`try-catch` 不要) + +--- + +### 3.2 ViewModel(ロジック層) + +**担当**: 状態管理、ビジネスロジック、Repositoryとの連携 + +```kotlin +class ChatScreenModel( + private val threadRepository: ThreadRepository, + private val messageRepository: MessageRepository, + private val chatRepository: ChatRepository, +) : ScreenModel { + // 状態: StateFlowでUIに公開 + private val _state = MutableStateFlow(ChatState()) + val state: StateFlow = _state.asStateFlow() + + // One-shotイベント: ChannelでUIに公開 + private val _effect = Channel() + val effect: Flow = _effect.receiveAsFlow() + + // ユーザーアクション + fun sendMessage(content: String) { + screenModelScope.launch { + // ビジネスロジック + // ... + } + } +} +``` + +**React での相当概念**: +- Redux の `reducer` + `actions` +- Custom Hooks (`useChat`, `useThreads`) +- `useState` + `useReducer` + +**主な違い**: + +| Kotlin | React | 説明 | +|--------|-------|------| +| `StateFlow` | `useState` | 常に最新の値を持つストリーム | +| `Channel` | イベントハンドラ | 消費するとなくなるOne-shotイベント | +| `screenModelScope` | コンポーネントスコープ | 画面生存期間中の Coroutine スコープ | + +--- + +### 3.3 View(UI層) + +**担当**: UI描画、ユーザー操作の受付 + +```kotlin +@Composable +fun ChatScreen() { + // 1. ViewModelの取得(DI: Koin) + val screenModel = koinScreenModel() + + // 2. 状態の購読 + val state by screenModel.state.collectAsState() + + // 3. Effectの処理 + LaunchedEffect(Unit) { + screenModel.effect.collect { effect -> + when (effect) { + is ChatEffect.ShowMessage -> { + // Snackbar表示 + } + } + } + } + + // 4. UI構築 + Scaffold( + bottomBar = { + ChatComposer( + models = state.composer.models, + onSendMessage = { text -> + screenModel.sendMessage(text) + }, + isLoading = state.composer.isSending, + ) + }, + ) { /* ... */ } +} +``` + +**React での相当概念**: +```tsx +function ChatScreen() { + // 1. 状態の購読 + const { threads, messages, isSending } = useChatState(); + + // 2. One-shotイベントの処理 + const { showSnackbar } = useSnackbar(); + useEffect(() => { + if (error) { + showSnackbar(error.message); + } + }, [error]); + + // 3. UI構築 + return ( +
+ sendMessage(text)} + isLoading={isSending} + /> +
+ ); +} +``` + +--- + +## 4. State と Effect + +### 4.1 State(状態) + +**担当**: UIの状態を表現する不変データクラス + +```kotlin +data class ChatState( + val threadList: ThreadListState = ThreadListState(), + val messageList: MessageListState = MessageListState(), + val composer: ComposerState = ComposerState(), +) { + // 派生プロパティ + val hasSelectedThread: Boolean + get() = threadList.selectedThread != null + + val isLoading: Boolean + get() = threadList.isLoading || messageList.isLoading || composer.isSending +} +``` + +**React での相当概念**: +```tsx +// Redux Selector に相当 +const hasSelectedThread = useSelector(state => state.threadList.selectedThread != null); +const isLoading = useSelector(state => + state.threadList.isLoading || state.messageList.isLoading || state.composer.isSending +); +``` + +--- + +### 4.2 Effect(One-shotイベント) + +**担当**: 状態として保持しない単発のイベント + +```kotlin +sealed class ChatEffect { + data class ShowMessage(val message: String) : ChatEffect() + data class NavigateToThread(val threadId: String) : ChatEffect() +} +``` + +**React での相当概念**: +```tsx +// 直接イベントハンドラを呼ぶか、Custom Hookから返す +const { showSnackbar, navigate } = useAppEffects(); +``` + +**なぜ State と Effect を分けるのか**: + +| 特徴 | State | Effect | +|------|-------|--------| +| **保持期間** | 常に保持 | 消費すると削除 | +| **再生成時** | 自動で復元 | 再実行されない | +| **主な用途** | UI表示 | ナビゲーション、Snackbar | +| **データ構造** | 不変データクラス | `sealed class` | +| **React相当** | `useState` / Redux State | イベントハンドラ / Callback | + +--- + +## 5. 状態更新のパターン + +### Kotlin (Immutable State) + +```kotlin +// 不変データクラスのコピーで状態更新 +private fun updateThreadList(transform: (ThreadListState) -> ThreadListState) { + _state.update { state -> + state.copy(threadList = transform(state.threadList)) + } +} + +// 使用例 +updateThreadList { current -> + current.copy( + threads = newThreads, + selectedThread = newSelectedThread, + isLoading = false, + ) +} +``` + +**React での相当概念**: +```tsx +// useState + スプレッド演算子 +setThreadList(current => ({ + ...current, + threads: newThreads, + selectedThread: newSelectedThread, + isLoading: false, +})); + +// または Redux Reducer +case 'LOAD_THREADS_SUCCESS': + return { + ...state, + threadList: { + ...state.threadList, + threads: action.payload.threads, + isLoading: false, + } + }; +``` + +--- + +## 6. 非同期処理のパターン + +### Kotlin (Coroutines + Flow) + +```kotlin +fun sendMessage(content: String) { + screenModelScope.launch { + // 1. ローカルで即座にUI更新 + updateMessageList { + it.copy(messages = it.messages + userMessage) + } + + // 2. API呼び出し(ストリーミング) + chatRepository.sendMessage(request).collect { result -> + result.onSuccess { chunk -> + // 3. ストリーミングレスポンスでUI更新 + updateMessageList { current -> + current.copy(messages = current.messages.map { msg -> + if (msg.id == streamingMessageId) { + msg.copy(content = msg.content + chunk.delta) + } else msg + }) + } + } + result.onFailure { error -> + // 4. エラー処理 + _effect.send(ChatEffect.ShowMessage(error.message)) + } + } + } +} +``` + +**React での相当概念**: +```tsx +function sendMessage(content) { + // 1. 即時UI更新 + setMessages(prev => [...prev, userMessage]); + + // 2. API呼び出し(SSEストリーミング) + fetchEventSource('/api/chat', { + method: 'POST', + body: JSON.stringify({ messages }), + onmessage(msg) { + const chunk = JSON.parse(msg.data); + // 3. ストリーミングレスポンスでUI更新 + setMessages(prev => prev.map(msg => + msg.id === streamingMessageId + ? { ...msg, content: msg.content + chunk.delta } + : msg + )); + }, + onerror(error) { + // 4. エラー処理 + showSnackbar(error.message); + } + }); +} +``` + +--- + +## 7. DI(依存性注入) + +### Kotlin (Koin) + +```kotlin +// モジュール定義 +val chatModule = module { + // Repositoryの実装を注入 + single { ThreadRepositoryImpl(get(), get(), get()) } + single { MessageRepositoryImpl(get(), get(), get()) } + single { ChatRepositoryImpl(get(), get(), get()) } + + // ViewModelをFactoryスコープで注入 + factory { ChatScreenModel(get(), get(), get(), get()) } +} + +// 使用側 +@Composable +fun ChatScreen() { + val screenModel = koinScreenModel() + // ... +} +``` + +**React での相当概念**: +```tsx +// React Context + Custom Hooks +const ChatContext = createContext(); + +function ChatProvider({ children }) { + const threadRepository = useMemo(() => new ThreadRepositoryImpl(), []); + const messageRepository = useMemo(() => new MessageRepositoryImpl(), []); + const chatRepository = useMemo(() => new ChatRepositoryImpl(), []); + + const screenModel = useMemo(() => + new ChatScreenModel(threadRepository, messageRepository, chatRepository), + [threadRepository, messageRepository, chatRepository] + ); + + return ( + + {children} + + ); +} + +// 使用側 +function ChatScreen() { + const screenModel = useContext(ChatContext); + // ... +} +``` + +--- + +## 8. 実装例:チャット画面 + +### 完全なデータフロー + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ユーザー操作: メッセージ送信 │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ View (ChatScreen) │ +│ ChatComposer( │ +│ onSendMessage = { text -> screenModel.sendMessage(text) } │ +│ ) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ 呼び出し +┌─────────────────────────────────────────────────────────────────┐ +│ ViewModel (ChatScreenModel) │ +│ fun sendMessage(content: String) { │ +│ 1. _state.value にローカルメッセージを追加 │ +│ 2. chatRepository.sendMessage() を呼び出し │ +│ 3. Flow> を収集 │ +│ } │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ ストリーミング +┌─────────────────────────────────────────────────────────────────┐ +│ Repository (ChatRepository) │ +│ suspend fun sendMessage(): Flow> │ +│ • HTTP POST /v1/chat │ +│ • SSE (Server-Sent Events) のパース │ +│ • JSONデコード │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ Flow> +┌─────────────────────────────────────────────────────────────────┐ +│ ViewModel │ +│ chatRepository.sendMessage(request).collect { result -> │ +│ result.onSuccess { chunk -> │ +│ // State更新(UIに自動反映) │ +│ _state.update { /* deltaチャンクを追加 */ } │ +│ } │ +│ result.onFailure { error -> │ +│ // Effect送信(Snackbarなど) │ +│ _effect.send(ChatEffect.ShowMessage(error.message)) │ +│ } │ +│ } │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ StateFlow / Channel +┌─────────────────────────────────────────────────────────────────┐ +│ View (ChatScreen) │ +│ val state by screenModel.state.collectAsState() │ +│ → stateが変更されるたびに再コンポーズ │ +│ │ +│ LaunchedEffect { │ +│ screenModel.effect.collect { effect -> │ +│ when (effect) { │ +│ ShowMessage(msg) -> showSnackbar(msg) │ +│ } │ +│ } │ +│ } │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 9. ベストプラクティス + +### 9.1 状態は不変に + +```kotlin +// ✅ 良い例: copy() で新しい状態を作成 +updateThreadList { current -> + current.copy(threads = newThreads) +} + +// ❌ 悪い例: 可変状態(やるべきでない) +currentThreads.addAll(newThreads) +_state.value = _state.value.copy(threads = currentThreads) +``` + +### 9.2 ロジックは ViewModel に + +```kotlin +// ✅ 良い例: ViewModelでロジックを実装 +@Composable +fun ChatScreen() { + val screenModel = koinScreenModel() + val state by screenModel.state.collectAsState() + + ChatComposer( + onSendMessage = { text -> screenModel.sendMessage(text) } + ) +} + +// ❌ 悪い例: Composableでロジックを実装(避ける) +@Composable +fun ChatScreen() { + val (messages, setMessages) = useState(emptyList()) + + ChatComposer( + onSendMessage = { text -> + // 複雑なロジックがComposableに入り込む + val request = ChatRequest(...) + api.send(request).then(response => { + setMessages(prev => [...prev, response]) + }) + } + ) +} +``` + +### 9.3 Effect は One-shot イベントのみ + +```kotlin +// ✅ 良い例: EffectはSnackbar、ナビゲーションなど +sealed class ChatEffect { + data class ShowMessage(val message: String) : ChatEffect() + data class NavigateToThread(val threadId: String) : ChatEffect() +} + +// ❌ 悪い例: 状態をEffectに入れる(Stateに入れるべき) +sealed class ChatEffect { + data class MessagesUpdated(val messages: List) : ChatEffect() +} +``` + +--- + +## 10. React 開発者のためのチートシート + +| やりたいこと | React | Kotlin (Compose) | +|-------------|-------|------------------| +| **状態宣言** | `const [count, setCount] = useState(0)` | `val count by countState.collectAsState()` | +| **状態更新** | `setCount(prev => prev + 1)` | `_state.update { it.copy(count = it.count + 1) }` | +| **副作用** | `useEffect(() => { ... }, [deps])` | `LaunchedEffect(deps) { ... }` | +| **非同期処理** | `useEffect` + `fetch` / `axios` | `LaunchedEffect` + `Coroutine` | +| **ストリーミング** | `fetchEventSource` / EventSource | `Flow.collect { ... }` | +| **エラーハンドリング** | `try-catch` / `.catch()` | `Result` / `.onFailure { ... }` | +| **条件レンダリング** | `{condition && }` | `if (condition) { Component() }` | +| **リストレンダリング** | `{items.map(item => )}` | `items.forEach { item -> Item(item) }` | +| **DI** | Context / Custom Hooks | Koin (`koinScreenModel()`) | +| **ルーティング** | React Router | Voyager (`Screen`) | + +--- + +## 11. サンプルコード比較 + +### チャット画面の実装比較 + +**React + TypeScript**: +```tsx +import { useState, useEffect } from 'react'; + +interface Message { + id: string; + content: string; + role: 'user' | 'assistant'; +} + +function ChatScreen() { + const [messages, setMessages] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [input, setInput] = useState(''); + + const sendMessage = async (content: string) => { + // ユーザーメッセージを追加 + const userMessage: Message = { + id: `user-${Date.now()}`, + content, + role: 'user' + }; + setMessages(prev => [...prev, userMessage]); + + // ストリーミングでレスポンスを受信 + setIsLoading(true); + const response = await fetch('/api/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ messages: [...messages, userMessage] }) + }); + + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + + let assistantContent = ''; + while (true) { + const { done, value } = await reader!.read(); + if (done) break; + + const chunk = decoder.decode(value); + const lines = chunk.split('\n').filter(line => line.startsWith('data: ')); + + for (const line of lines) { + const data = JSON.parse(line.slice(6)); + if (data.delta) { + assistantContent += data.delta; + setMessages(prev => { + const last = prev[prev.length - 1]; + if (last?.role === 'assistant') { + return [...prev.slice(0, -1), { ...last, content: assistantContent }]; + } + return [...prev, { + id: `assistant-${Date.now()}`, + content: assistantContent, + role: 'assistant' + }]; + }); + } + } + } + setIsLoading(false); + }; + + return ( +
+ {messages.map(msg => ( +
+ {msg.role}: + {msg.content} +
+ ))} + setInput(e.target.value)} + onKeyPress={e => { + if (e.key === 'Enter' && input.trim()) { + sendMessage(input); + setInput(''); + } + }} + /> +
+ ); +} +``` + +**Kotlin + Compose Multiplatform**: +```kotlin +@Composable +fun ChatScreen() { + val screenModel = koinScreenModel() + val state by screenModel.state.collectAsState() + + Scaffold( + bottomBar = { + ChatComposer( + onSendMessage = { text -> screenModel.sendMessage(text) }, + isLoading = state.composer.isSending + ) + } + ) { padding -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(padding) + ) { + items(state.messageList.messages) { msg -> + MessageBubble( + role = msg.role, + content = msg.content + ) + } + } + } +} + +// ViewModel (ロジックはここに実装される) +class ChatScreenModel( + private val chatRepository: ChatRepository, +) : ScreenModel { + private val _state = MutableStateFlow(ChatState()) + val state: StateFlow = _state.asStateFlow() + + fun sendMessage(content: String) { + screenModelScope.launch { + val userMessage = /* ... */ + updateMessageList { + it.copy(messages = it.messages + userMessage) + } + + chatRepository.sendMessage(request).collect { result -> + result.onSuccess { chunk -> + // State更新(自動的にUIに反映) + updateMessageList { current -> + current.copy(messages = /* ... */) + } + } + } + } + } +} +``` + +--- + +## 12. 参考資料 + +### プロジェクト内の関連ファイル + +| ファイル | 説明 | +|---------|------| +| `ChatScreen.kt` | View(UI)の実装 | +| `ChatScreenModel.kt` | ViewModel(ロジック)の実装 | +| `ChatState.kt` | Stateの定義 | +| `ChatEffect.kt` | Effectの定義 | +| `*Repository.kt` | Model(データ層)の実装 | + +### 外部リソース + +- [Jetpack Compose State](https://developer.android.com/jetpack/compose/state) +- [Kotlin Flow](https://kotlinlang.org/docs/flow.html) +- [Voyager](https://voyager.adriel.cafe/) +- [Koin](https://insert-koin.io/) diff --git a/docs/70.knowledge/webhook-guide.md b/docs/70.knowledge/webhook-guide.md new file mode 100644 index 0000000..021f1c9 --- /dev/null +++ b/docs/70.knowledge/webhook-guide.md @@ -0,0 +1,677 @@ +# Webhook & Push Notification ガイド + +このドキュメントは Webhook の基礎概念と EgoGraph でのプッシュ通知仕組み、セキュリティについて説明します。 + +--- + +## パート1: Webhook とは + +Webhook(ウェブフック)は「イベント通知を HTTP 経由で送信する仕組み」です。「逆ポーリング」とも呼ばれ、サーバーからクライアントへの能動的な通知を実現します。 + +### ポーリング vs Webhook + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 【ポーリング方式(従来)】 │ +│ │ +│ クライアント サーバー │ +│ │ │ │ +│ │ 「何か新しいことある?」 │ │ +│ ├────────────────────────────→│ │ +│ │ │ 「ないよ」 │ +│ │←─────────────────────────────│ │ +│ │ (数秒後) │ +│ │ │ │ +│ │ 「何か新しいことある?」 │ │ +│ ├────────────────────────────→│ │ +│ │ │ 「ないよ」 │ +│ │←─────────────────────────────│ │ +│ │ +│ ※ 無駄な通信が多く、リアルタイム性に欠ける │ +├─────────────────────────────────────────────────────────────────────┤ +│ 【Webhook 方式】 │ +│ │ +│ サービスA サービスB │ +│ │ │ │ +│ │ イベント発生! │ │ +│ │ │ │ +│ │ 「通知をおく!」 │ │ +│ ├────────────────────────────→│ POST /webhook │ +│ │ Body: {"event": "..."} │ │ +│ │ │ 処理実行 │ +│ │ │ │ +│ ※ イベント発生時に即座に通知 │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### 一般的な Webhook フロー + +1. **Webhook URL 登録**: 受信側が Webhook URL を通知元に登録 +2. **イベント発生**: 通知元でイベント発生 +3. **HTTP POST 送信**: 通知元が Webhook URL へ POST リクエスト +4. **処理実行**: 受信側がリクエストを処理 +5. **レスポンス返却**: 受信側が `2xx` ステータスを返す + +--- + +## パート2: EgoGraph での使用 + +### 使用目的 + +- **タスク完了通知**: tmux セッション内のエージェント処理完了をプッシュ通知 +- **イベント配信**: 任意のイベントをモバイルアプリに通知 + +### エンドポイント + +``` +POST /v1/push/webhook +``` + +### 認証 + +`X-Webhook-Secret` ヘッダーを使用したシークレット認証: + +```bash +curl -X POST http://gateway:8001/v1/push/webhook \ + -H "X-Webhook-Secret: your-secret-key" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "task_completed", + "session_id": "agent-0001", + "title": "完了", + "body": "タスクが完了しました" + }' +``` + +### リクエスト形式 + +| フィールド | 型 | 必須 | 説明 | +|----------|------|------|------| +| `type` | string | ✓ | イベントタイプ(例: `task_completed`, `chat_completed`) | +| `session_id` | string | ✓ | セッションID(例: `agent-0001`) | +| `title` | string | ✓ | 通知タイトル(1-100文字) | +| `body` | string | ✓ | 通知本文(1-500文字) | + +### レスポンス形式 + +成功時(200 OK): + +```json +{ + "success_count": 1, + "failure_count": 0, + "invalid_tokens": [] +} +``` + +### Webhookペイロードのフィールド + +| フィールド | タイプ | 必須 | 説明 | 例 | +|-----------|--------|------|------|-----| +| `type` | string | ✓ | イベントタイプ | `task_completed`, `error`, `info` | +| `session_id` | string | ✓ | セッションID | `agent-0001`, `backend-worker` | +| `title` | string | ✓ | 通知タイトル(1-100文字) | `タスク完了` | +| `body` | string | ✓ | 通知本文(1-500文字) | `処理が完了しました` | + +--- + +## パート3: 実装ユースケース + +### ケース1: Claude Code セッション完了通知 + +Claude Code の Stop Hook から Webhook を送信: + +```python +# /root/.claude/hooks/send-webhook-on-stop.py +import httpx +import os + +async def send_webhook(): + await httpx.post( + "http://localhost:8001/v1/push/webhook", + headers={"X-Webhook-Secret": os.getenv("GATEWAY_WEBHOOK_SECRET")}, + json={ + "type": "claude_session_stopped", + "session_id": "claude-code", + "title": "Claude Code 完了", + "body": "セッションが終了しました" + } + ) +``` + +`~/.claude/settings.json` に設定: + +```json +{ + "hooks": { + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "python3 /root/.claude/hooks/send-webhook-on-stop.py" + } + ] + } + ] + } +} +``` + +### ケース2: ラッパースクリプト + +エージェント完了後に通知を送るラッパー: + +```bash +#!/bin/bash +# agent-run スクリプト + +SESSION_NAME="agent-$(date +%Y%m%d-%H%M%S)" + +# tmux セッションでエージェント起動 +tmux new-session -d -s "$SESSION_NAME" "$@" + +# セッション終了待機 +while tmux has-session -t "$SESSION_NAME" 2>/dev/null; do + sleep 1 +done + +# 完了通知 +python -m gateway.cli \ + --secret "$GATEWAY_WEBHOOK_SECRET" \ + --session "$SESSION_NAME" \ + --title "エージェント完了" \ + --body "タスクが完了しました" +``` + +### ケース3: 専用通知コマンド + +```python +# gateway/cli.py +import asyncio +import httpx + +async def send_webhook( + webhook_url: str, + webhook_secret: str, + event_type: str, + session_id: str, + title: str, + body: str, +) -> None: + payload = { + "type": event_type, + "session_id": session_id, + "title": title, + "body": body, + } + + async with httpx.AsyncClient() as client: + response = await client.post( + webhook_url, + json=payload, + headers={ + "X-Webhook-Secret": webhook_secret, + "Content-Type": "application/json", + }, + timeout=10.0, + ) + response.raise_for_status() +``` + +使用例: + +```bash +python -m gateway.cli \ + --secret "$GATEWAY_WEBHOOK_SECRET" \ + --session "agent-0001" \ + --title "バッチ完了" \ + --body "データ処理が完了しました" +``` + +--- + +## パート4: セキュリティ + +### 実施済みセキュリティ対策 + +| 対策 | 状態 | 評価 | +|-----|------|------| +| Webhook Secret 認証 | ✅ 実装済み | ⭐⭐⭐⭐⭐ | +| Timing Attack 対策 | ✅ `secrets.compare_digest()` | ⭐⭐⭐⭐⭐ | +| Pydantic 入力検証 | ✅ 実装済み | ⭐⭐⭐⭐⭐ | + +### Webhook セキュリティリスクと対策 + +| リスク | 対策 | 実装状況 | +|--------|------|---------| +| 認証なし | シークレット認証 | ✅ 実装済み | +| 弱いシークレット | 32バイト以上のランダム文字列 | ✅ ドキュメント化済み | +| Timing Attack | `secrets.compare_digest()` | ✅ 実装済み | +| リプレイ攻撃 | タイムスタンプ検証 | ⚠️ 未実装 | +| DoS 攻撃 | レート制限 | ⚠️ 未実装 | +| ヘッダーインジェクション | 入力値検証 | ✅ 実装済み | + +### Timing Attack 対策 + +シークレット比較には必ず `secrets.compare_digest()` を使用します。 + +```python +# gateway/dependencies.py +import secrets + +if not secrets.compare_digest(webhook_secret, expected_secret): + raise HTTPException(status_code=401, detail="Invalid webhook secret") +``` + +**なぜ `==` ではダメなのか?** + +`==` 演算子は先頭から1文字ずつ比較するため、処理時間の差からシークレットが推測される可能性があります(Timing Attack)。`compare_digest()` は常に一定時間で比較します。 + +### 🟡 優先度中: リプレイ攻撃対策 + +**現状**: Webhook でタイムスタンプ検証なし +**リスク**: 同一リクエストの再送により二重処理 + +**推奨対策**: タイムスタンプ検証の追加 + +```python +class WebhookPayload(BaseModel): + timestamp: int # Unix タイムスタンプ追加 + type: str + session_id: str + title: str + body: str + +# 検証(許容差5分) +if abs(time.time() - payload.timestamp) > 300: + raise HTTPException(status_code=400, detail="Expired timestamp") +``` + +### 🟡 優先度中: レート制限 + +**推奨対策**: slowapi 等を使用したレート制限 + +```python +from slowapi import Limiter +from slowapi.util import get_remote_address + +limiter = Limiter(key_func=get_remote_address) + +@app.post("/v1/push/webhook") +@limiter.limit("10/minute") # 1分間に10回 +async def send_webhook(...): + ... +``` + +### セキュリティ評価サマリー + +``` +認証・認可 ████████████████████░░ 90% ⭐⭐⭐⭐⭐ +入力バリデーション ████████████████████░░ 90% ⭐⭐⭐⭐⭐ +リプレイ攻撃対策 ████░░░░░░░░░░░░░░░░░ 20% ⭐⭐ +DoS 対策 ████░░░░░░░░░░░░░░░░░ 20% ⭐⭐ + +Webhook全体 ██████████████████░░░ 80% ⭐⭐⭐⭐ +``` + +### 推奨本番構成 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ インターネット │ +└─────────────────────────────────────────────────────────────┘ + │ + │ HTTPS (443) + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ [リバースプロキシ] │ +│ • Nginx / Caddy / Traefik │ +│ • TLS 終端処理 │ +│ • レート制限 │ +│ • IP フィルタリング │ +└─────────────────────────────────────────────────────────────┘ + │ + │ HTTP (8080) + ↓ (内部ネットワーク - 平文で可) +┌─────────────────────────────────────────────────────────────┐ +│ Gateway (:8001) │ +│ • Webhook 認証 │ +│ • FCM 送信 │ +└─────────────────────────────────────────────────────────────┘ + │ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ Firebase FCM │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## パート5: 環境設定 + +### 環境変数設定 + +`.env` で設定する項目: + +| 環境変数 | 必須 | デフォルト | 説明 | +|---------|------|----------|------| +| `GATEWAY_WEBHOOK_SECRET` | ✓ | - | Webhook シークレット(32バイト以上推奨) | +| `GCM_PROJECT_ID` | - | - | Firebase プロジェクト ID | +| `FCM_CREDENTIALS_PATH` | - | - | Firebase サービスアカウントキーのパス | + +### シークレット生成 + +```bash +# 32バイト以上のランダム文字列を生成 +openssl rand -base64 32 +``` + +--- + +## 前提条件 + +1. **Gateway**: 起動済み(tmuxセッション: `egograph-gateway`) +2. **Androidアプリ**: EgoGraphアプリをインストール済み +3. **FCM設定**: `gateway/.env` に `FCM_PROJECT_ID` が設定済み +4. **Webhook Secret**: `gateway/.env` に `GATEWAY_WEBHOOK_SECRET` が設定済み + +--- + +## 手順1: FCMトークンの取得 + +Androidアプリは起動時に自動的にFCMトークンを取得・登録しますが、**トークンはLogcatに出力されます**。 + +### エミュレータでトークンを取得 + +```bash +# 1. Androidアプリを起動 +# 2. LogcatでFCMトークンを確認 +adb logcat | grep -E "FcmTokenManager" + +# 出力例: +# FcmTokenManager: Registering FCM token: AAAA... +# FcmTokenManager: FCM token registered successfully +``` + +**※ 完全なトークンを表示するには:** + +一時的にデバッグログを追加します: + +```kotlin +// FcmService.kt の onCreate() メソッド内 +FirebaseMessaging.getInstance().token + .addOnSuccessListener { token -> + if (!token.isNullOrBlank()) { + // デバッグ用: トークン全体をLogcatに出力 + Log.d("FcmService", "FCM Token (DEBUG): $token") + + getTokenManager()?.registerToken( + token = token, + deviceName = android.os.Build.MODEL, + ) + } + } +``` + +再ビルド後、Logcatで完全なトークンを確認します: + +```bash +adb logcat | grep "FCM Token (DEBUG)" +``` + +### 実機でトークンを取得 + +```bash +# 1. 実機をUSB接続 +# 2. デバイスIDを確認 +adb devices + +# 3. Logcatを確認 +adb -s <デバイスID> logcat | grep -E "FcmTokenManager" +``` + +--- + +## 手順2: デバイストークンの登録 + +Androidアプリはトークンを自動登録しますが、手動で登録する場合: + +### 方法1: curlで登録(推奨) + +```bash +# Gateway API Keyとデバイストークンを準備 +GATEWAY_API_KEY="your_gateway_api_key_from_.env" +DEVICE_TOKEN="fcm_token_from_logcat" + +# トークンを登録 +curl -X PUT http://localhost:8001/v1/push/token \ + -H "Content-Type: application/json" \ + -H "X-API-Key: $GATEWAY_API_KEY" \ + -d "{ + \"device_token\": \"$DEVICE_TOKEN\", + \"platform\": \"android\", + \"device_name\": \"Pixel 6 Emulator\" + }" +``` + +**レスポンス:** +```json +{ + "id": 1, + "user_id": "default_user", + "device_token": "AAAA...", + "platform": "android", + "device_name": "Pixel 6 Emulator", + "enabled": true, + "last_seen_at": "2026-02-25T14:00:00", + "created_at": "2026-02-25T14:00:00" +} +``` + +### 方法2: Androidアプリで自動登録 + +アプリの設定画面で以下を設定します: +1. **Gateway API URL**: `http://:8001` +2. **Gateway API Key**: `.env` の `GATEWAY_API_KEY` + +設定後、アプリ起動時に自動登録されます。 + +--- + +## 手順3: Webhookで通知を送信 + +### 基本的なWebhook送信 + +```bash +# Webhook Secretを準備 +WEBHOOK_SECRET="your_webhook_secret_from_.env" + +# 通知を送信 +curl -X POST http://localhost:8001/v1/push/webhook \ + -H "Content-Type: application/json" \ + -H "X-Webhook-Secret: $WEBHOOK_SECRET" \ + -d '{ + "type": "task_completed", + "session_id": "agent-0001", + "title": "タスク完了", + "body": "エージェントがタスクを完了しました" + }' +``` + +**レスポンス:** +```json +{ + "success_count": 1, + "failure_count": 0, + "invalid_tokens": [] +} +``` + +### 通知フロー + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ EgoGraph Webhook 通知フロー │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ [外部サービス] [Gateway] [Firebase] │ +│ (Agent等) (Webhook受信+FCM送信) (FCMサービス) │ +│ │ │ │ │ +│ │ ① イベント発生 │ │ │ +│ │ │ │ │ +│ │ ② POST /v1/push/webhook │ │ +│ ├─────────────────────→│ │ │ +│ │ X-Webhook-Secret │ │ │ +│ │ │ │ │ +│ │ ③ 認証検証 │ │ +│ │ ④ ペイロード検証 │ │ +│ │ ⑤ FCMトークン取得 │ │ +│ │ │ │ │ +│ │ ⑥ FCM送信 │ │ +│ │ ├────────────────────────────→│ │ +│ │ │ │ │ +│ │ │ ⑦ プッシュ通知 │ +│ │ │ ├───→ [ユーザー端末] +│ │ │ │ │ +│ │ ⑧ レスポンス │ │ │ +│ │←─────────────────────│ │ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## トラブルシューティング + +### 通知が届かない + +1. **Gatewayのログを確認** + ```bash + tmux capture-pane -p -t egograph-gateway | grep -E "webhook|FCM" + ``` + +2. **デバイスが登録されているか確認** + ```bash + # Gatewayのデータベースを確認 + cd /root/workspace/ego-graph/wt1/gateway + uv run python -c " + import sqlite3 + conn = sqlite3.connect('gateway.db') + result = conn.execute('SELECT * FROM push_devices').fetchall() + for row in result: + print(row) + " + ``` + +3. **FCM初期化を確認** + ```bash + tmux capture-pane -p -t egograph-gateway | grep "Firebase" + ``` + + 正常に初期化されている場合: + ```text + Firebase Admin SDK initialized with project: your-project-id + ``` + + 未設定の場合: + ```text + FCM project ID not configured. Push notifications disabled + ``` + +### success_count: 0, failure_count: 0 + +**原因:** デバイストークンが登録されていません + +**解決策:** 手順2でトークンを登録してください。 + +### 401 Unauthorized + +**原因:** Webhook Secretが間違っています + +**解決策:** `gateway/.env` の `GATEWAY_WEBHOOK_SECRET` を確認してください。 + +### Gatewayが固まる + +**原因:** 古いバージョンのデッドロックバグ(既に修正済み) + +**解決策:** Gatewayを再起動してください +```bash +tmux kill-session -t egograph-gateway +tmux new-session -d -s egograph-gateway 'cd /root/workspace/ego-graph/wt1 && uv run python -m gateway.main' +``` + +--- + +## APIエンドポイント一覧 + +### POST /v1/push/webhook + +Webhookでプッシュ通知を送信します。 + +**Headers:** +- `X-Webhook-Secret`: Webhookシークレット(32バイト以上) + +**Request Body:** +```json +{ + "type": "task_completed", + "session_id": "agent-0001", + "title": "完了", + "body": "タスク完了" +} +``` + +**Response:** `200 OK` +```json +{ + "success_count": 1, + "failure_count": 0, + "invalid_tokens": [] +} +``` + +### PUT /v1/push/token + +FCMデバイストークンを登録します。 + +**Headers:** +- `X-API-Key`: Gateway API Key + +**Request Body:** +```json +{ + "device_token": "AAAA...", + "platform": "android", + "device_name": "My Device" +} +``` + +**Response:** `200 OK` +```json +{ + "id": 1, + "user_id": "default_user", + "device_token": "AAAA...", + "platform": "android", + "device_name": "My Device", + "enabled": true, + "last_seen_at": "2026-02-25T14:00:00", + "created_at": "2026-02-25T14:00:00" +} +``` + +--- + +## 関連ファイル + +- `gateway/api/push.py` - Webhook エンドポイント実装 +- `gateway/services/fcm_service.py` - FCM サービス +- `gateway/domain/models.py` - データモデル定義 +- `gateway/dependencies.py` - 認証処理 + +## 参考資料 + +- [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) +- [OWASP Webhook Security Guidelines](https://github.com/OWASP/GitHub-Security-Whitepaper) +- [Webhook Best Practices](https://sendgrid.com/blog/webhooks-vs-api-the-difference/) From 5d33b309a7d344b176c7b11ffe21290189658f6f Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 15:49:17 +0000 Subject: [PATCH 03/14] docs: refresh plexus repository guidance --- AGENTS.md | 224 +++++++++++++++++---------------------------- README.md | 27 ++---- frontend/README.md | 87 ++++-------------- gateway/README.md | 48 +++------- 4 files changed, 127 insertions(+), 259 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 99346e3..d77941f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,189 +1,137 @@ -# EgoGraph 開発ガイドライン +# Plexus 開発ガイドライン ## 概要 -Personal AI Agent and Personal Data Warehouse and Personal Mobile App(サーバーレス・ローカルファースト) +Plexus は、tmux を中心に AI エージェントや Worker を動かすための runtime repository です。 -構成: Python (uv workspace) + Kotlin Multiplatform / Compose Multiplatform +構成: -## アーキテクチャ - -### ingest (データ収集) - -ETL/ELT Pipeline: Provider → Collector → Transform → Storage → Data Lake +- `gateway/`: Python / Starlette ベースの runtime API +- `frontend/`: Kotlin Multiplatform / Compose Multiplatform ベースの Android terminal client +- `maestro/`: terminal UI の E2E フロー +- `docs/`: terminal / orchestration / FCM / webhook などの設計と運用知識 -- Collector: API から生データ取得 -- Transform: クレンジング & スキーママッピング -- Storage: Parquet (分析用) + JSON (Raw) を R2 に保存 -- Stateful: R2 内のカーソル位置を追跡し、増分取り込みをサポート -- Tech Stack: Python 3.12+, DuckDB, boto3 +## アーキテクチャ -### backend (Agent API) +### gateway -DDD (Domain-Driven Design) - レイヤードアーキテクチャ +Layered Architecture - Starlette ベースの軽量 API -- api/: プレゼンテーション (FastAPI ルート、リクエスト/レスポンス) -- usecases/: アプリケーション (ユースケース、ツールファクトリ、LLM調整) -- infrastructure/: インフラストラクチャ (DuckDB、LLMプロバイダ、Repository実装) -- database/: DuckDB ステートレス設計(`:memory:`)、R2 から直接 Parquet 読み込み -- Tech Stack: FastAPI, Uvicorn, DuckDB, pandas, httpx, Pydantic +- Terminal Surface: tmux session list / snapshot / websocket terminal / push +- Runtime Control: 認証、永続化、push token 管理 +- 将来拡張: orchestration surface(task / attempt / worker / lease / heartbeat) +- Tech Stack: Python 3.12+, Starlette, Uvicorn, SQLite, WebSocket, libtmux -### frontend (Mobile App) +### frontend MVVM (StateFlow + Channel) - Kotlin Multiplatform + Compose Multiplatform -- core/domain/: DTOs, Repository インターフェース -- core/network/: HTTP クライアント (Ktor) -- features/: 機能モジュール (Screen + ScreenModel + State + Effect) -- di/: 依存性注入 (Koin) +- `core/domain/`: terminal model / repository interface +- `core/network/`: HTTP クライアント (Ktor) +- `core/platform/`: WebView, permissions, keyboard, preferences などの platform abstraction +- `features/terminal/`: session list / terminal session / gateway settings +- `di/`: Koin による依存性注入 -| レイヤー | 役割 | 例 | -| ----------- | -------------------------- | ------------------ | -| Screen | Compose UI 表示 | ChatScreen.kt | -| ScreenModel | ビジネスロジック・状態更新 | ChatScreenModel.kt | -| State | UI 状態(データクラス) | ChatState.kt | -| Effect | One-shot イベント | ChatEffect.kt | +| レイヤー | 役割 | 例 | +| --- | --- | --- | +| Screen | Compose UI 表示 | `TerminalScreen.kt` | +| ScreenModel | ビジネスロジック・状態更新 | `AgentListScreenModel.kt` | +| State | UI 状態 | `AgentListState.kt` | +| Effect | One-shot イベント | `AgentListEffect.kt` | -- DI: Koin 4.0.0 -- State Management: StateFlow + Channel (Kotlin Coroutines) -- Tech Stack: Kotlin 2.2.21, Compose Multiplatform 1.9.0, Ktor 3.3.3, Voyager 1.1.0-beta03, Kermit -- Testing: kotlin-test, Turbine, MockK, Ktor MockEngine +### runtime model -- 注意:旧 React + Capacitor フロントエンドは別 repo `endo-ava/egograph-frontend-capacitor-legacy` へ移行済みのため、このモノレポでは無視してください +Plexus は次の3つを runtime の中核として扱います。 -### gateway (Terminal GW) - -Layered Architecture - Starlette ベースの軽量 API - -- tmux Integration: `agent-XXXX` 形式のセッションを列挙・管理 -- WebSocket: 端末入出力の双方向通信 -- Push Notification: FCM 経由の通知送信 -- Tech Stack: Starlette, Uvicorn, WebSocket, libtmux +- `tmux`: 実行 session の中心 +- `git worktree`: 並列作業の物理分離 +- durable state: 実行状態の追跡と将来の orchestration control plane ## 開発コマンド ```bash -# === Python Workspace === -uv sync # 依存関係同期 -uv run pytest # 全テスト -uv run ruff check . # Lint -uv run ruff check . --fix # Lint & Fix -uv run ruff format . # Format - -# === Ingest === -uv run python -m ingest.spotify.main -uv run pytest ingest/tests --cov=ingest - -# === Backend === -tmux new-session -d -s fastapi 'uv run python -m backend.main' -uv run python -m backend.main -uv run pytest backend/tests --cov=backend -uv run python -m backend.dev_tools.chat_cli # デバッグ用CLIツール - # === Gateway === -tmux new-session -d -s gateway 'uv run python -m gateway.main' -uv run pytest gateway/tests --cov=gateway - -# === Frontend (cd frontend) === -cd frontend # PJルートからはgradlewは使えないことに注意 -./gradlew :androidApp:assembleDebug # ビルド -./gradlew :androidApp:installDebug # インストール -./gradlew :shared:testDebugUnitTest # テスト -./gradlew :shared:koverHtmlReportDebug # カバレッジ率 -./gradlew ktlintCheck # Lint -./gradlew ktlintFormat # Format -./gradlew detekt # 静的解析 -# NOTE: ktlintFormat/ktlintCheck は同一コマンドで連続実行せず、先に ktlintFormat 単体で実行する(同一Gradle実行内だと ktlintCheck が先に走って失敗することがあるため) +cd gateway +uv run python -m gateway.main +uv run pytest tests -v +uv run pytest tests/unit -v +uv run ruff check . +uv run ruff check . --fix +uv run ruff format . + +# === Frontend === +cd frontend +./gradlew :androidApp:assembleDebug +./gradlew :androidApp:installDebug +./gradlew :shared:testDebugUnitTest +./gradlew :shared:koverHtmlReportDebug +./gradlew ktlintFormat +./gradlew ktlintCheck +./gradlew detekt # === E2E Test (Maestro) === -maestro test maestro/flows/ # 全テスト一括実行 - -# === Coderabbit review === -coderabbit --prompt-only -t uncommitted # Commit前 -coderabbit --prompt-only -t committed --base main # PR作成前 +maestro test maestro/flows/ ``` ## 規約 -### コーディング - -#### 基本原則 +### 基本原則 -- **「長期的な保守性」「コードの美しさ」「堅牢性」**を担保するようなコーディングを意識 - - SOLID原則 - - KISS (Keep It Simple, Stupid) & YAGNI (You Ain't Gonna Need It): - - DRY (Don't Repeat Yourself) - - 責務の分離 (Separation of Concerns): ビジネスロジック、UI、データアクセスなどが適切に分離されているか? - - 可読性と美しさ +- 長期的な保守性、コードの美しさ、堅牢性を優先する +- KISS / YAGNI / DRY を守る +- terminal surface と orchestration surface の責務を混ぜない +- tmux / worktree / runtime state の役割を曖昧にしない +- 場当たり的なフォールバックで複雑さを増やさない -#### その他のルール +### 実装ルール -| 項目 | ルール | -| --------- | -------------------------------------------------- | -| SQL | プレースホルダ必須: `execute(query, (param,))` | -| Logging | 遅延評価 `logger.info("k=%s", v)`, 機密情報禁止 | -| APIエラー | 統一フォーマット `invalid_: ` | -| Docstring | 日本語 | -| テスト | AAA パターン必須、Python: pytest、Frontend: Kotest | +| 項目 | ルール | +| --- | --- | +| SQL | プレースホルダ必須: `execute(query, (param,))` | +| Logging | 遅延評価 `logger.info("k=%s", v)`、機密情報禁止 | +| APIエラー | 統一フォーマット `invalid_: ` | +| Docstring / KDoc | 日本語 | +| テスト | AAA パターンを基本とする | ### Git / CI -- GitHub Flow: `main` 直接コミット禁止、ブランチ `/` -- コミット: Conventional Commits(英語) -- ワークフロー: `ci-*.yml`(テスト), `job-*.yml`(定期), `deploy-*.yml`, `release-*.yml` +- GitHub Flow を基本とする +- コミットは Conventional Commits(英語) +- ワークフローは Plexus の責務に関係するものだけ残す ## デバッグ ### スキル選択 -| シナリオ | 使用スキル | 説明 | -| -------------------- | -------------------------------------- | ---------------------------------------------- | -| APIのみ | `tmux-api-debug` | Backend APIの動作確認・デバッグ | -| UI + API(E2E) | `android-adb-debug` + `tmux-api-debug` | フロントエンドからバックエンドまでの統合テスト | -| LLM ToolCall検証 | `agent-tool-test` | 各LLMモデルの全ツール使用可否テスト | +| シナリオ | 使用スキル | 説明 | +| --- | --- | --- | +| Gateway API の確認 | `tmux-api-debug` | runtime API の再現・ログ確認 | +| Android 実機 / エミュレータ確認 | `android-adb-debug` | frontend の挙動確認、インストール、UI確認 | +| ADB 接続トラブル | `adb-connection-troubleshoot` | Linux から Android / Windows emulator への接続診断 | -### 環境構成 +### Android / ADB 構成 -``` -Linux ─ Backend (tmux) + ADB Client +```text +Linux ─ Gateway (tmux) + ADB Client ↓ Tailscale:100.x.x.x:5559 Windows ─ netsh (0.0.0.0:5559→127.0.0.1:5555) ─ Android Emulator (:5555) ``` -※ 5559を外部公開する理由: エミュレータの:5555とのポート競合回避 - -### Frontend~Backend間の検証方法 - -1. Windows側でエミュ起動 or デバッグ用実機でadb待ち受け(ユーザー作業) -2. Linux から ADB 接続(`adb connect :PORT`)(エミュ:5559, 実機:5669) -3. Backend を起動(tmux推奨) -4. adb コマンドで現在の挙動を確認しながら実装 -5. ビルド & インストール -6. adb コマンドでビルド内容の確認 +※ 5559 を外部公開する理由: エミュレータの :5555 とのポート競合回避 -# initial plan review request +### Frontend~Gateway 間の検証方法 -## 必ず -m でモデルを指定すること (gpt-5.3-codex が最適) -```bash -codex exec -m gpt-5.3-codex "このプランをレビューして。致命的な点だけ指摘して: {plan_full_path} (ref: {CLAUDE.md full_path})" -``` - -# updated plan review request -```bash -resume --last をつけないと最初のレビューの文脈が失われるから注意 -codex exec resume --last -m gpt-5.3-codex "プランを更新したからレビューして。致命的な点だけ指摘して: {plan_full_path} (ref: {CLAUDE.md full_path})" -``` +1. Windows 側でエミュを起動、またはデバッグ用実機で adb 待ち受け +2. Linux から `adb connect :PORT` で接続 +3. Gateway を起動 +4. adb コマンドで現在挙動を確認しながら実装 +5. frontend をビルドしてインストール +6. adb コマンドでビルド内容を確認 ## その他 -- 質問は `AskUserQuestion` 等を積極的に活用 -- サブエージェント活用でコンテキストをクリーンに(`delegate_task` を使用、`task` は使わない) - コード変更後はテスト確認必須 -- **コードレビューで一つも指摘されないレベル**のコード品質を目指す。不十分なコードの場合、レビュー指摘によりより多くの時間とトークンを消費します -- 目の前の目標を達成するためだけの場当たり的な対応は禁止 - - バグを潰すための場当たり的なフォールバック処理 - - テストやビルドを通すためだけの本質的ではない修正 -- 「後方互換」は負債にしかならないため禁止 -- うまくいかない時にコードを増やし続けない。コードを削除する勇気を持つ。シンプルが最も美しい。 -- ui/uxの調整タスクは言葉での認識合わせが難しいことを考慮し、必要に応じてASCII等を使いながらユーザーに確認する -- `.env`系を読むことは禁止 +- コードレビューで一つも指摘されないレベルの品質を目指す +- 後方互換のためだけの複雑化は禁止 +- `.env` 系を読むことは禁止 +- Firebase / FCM / webhook などの secrets 実体は repo に置かない diff --git a/README.md b/README.md index 4148183..f452736 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Plexus -Plexus is the dedicated runtime repository extracted from EgoGraph. +Plexus is a tmux-centered runtime platform for AI agents and workers. Concept documents: - English: `docs/CONCEPT.md` - Japanese: `docs/CONCEPT.ja.md` -It currently bootstraps the two surfaces needed for parallel run migration: +Plexus is built around two connected surfaces: -- `gateway/`: terminal runtime API for tmux session list, snapshot, websocket attach, and push -- `frontend/`: Android terminal client used to connect to the runtime from mobile +- `gateway/`: runtime API for tmux session list, snapshot, websocket attach, push, and webhook handling +- `frontend/`: Android terminal client used to access the runtime from mobile ## Scope @@ -19,22 +19,16 @@ Plexus owns runtime-oriented capabilities: - terminal access - tmux session lifecycle - runtime-facing push notifications -- future worker orchestration surfaces - -Plexus does not own: - -- personal data ingestion -- chat and reasoning backend -- RAG and memory features -- channel adapters such as Discord +- worker execution control +- future orchestration surfaces ## Repository Layout ```text plexus/ -├── docs/ # runtime and terminal design notes +├── docs/ # runtime, terminal, FCM, webhook, and orchestration notes ├── frontend/ # Android terminal client (KMP + Compose Multiplatform) -├── gateway/ # Starlette-based terminal runtime API +├── gateway/ # Starlette-based runtime API └── maestro/ # E2E flows for terminal UI ``` @@ -63,8 +57,3 @@ uv run pytest tests -v cd /root/workspace/plexus/frontend ./gradlew :shared:testDebugUnitTest ``` - -## Migration Note - -This repository is the Phase A-D bootstrap for parallel run migration from EgoGraph. -The old implementation in EgoGraph is intentionally kept for now and will be removed in a later phase. diff --git a/frontend/README.md b/frontend/README.md index 09018af..ca96bc2 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -5,7 +5,7 @@ ## 概要 Plexus Gateway に接続して tmux terminal へアクセスするための Android アプリです。 -EgoGraph から分離される runtime / terminal surface のモバイル UI を担います。 +Plexus の terminal surface におけるモバイル UI を担います。 - **Native Android**: Compose Multiplatform によるネイティブ UI - **MVVM**: 状態管理 @@ -33,21 +33,13 @@ frontend/ │ │ │ │ ├── model/ # データモデル │ │ │ │ └── repository/ # Repository インターフェース │ │ │ ├── platform/ # プラットフォーム抽象化 -│ │ │ ├── settings/ # テーマ設定 +│ │ │ ├── settings/ # 設定 │ │ │ ├── ui/ # 共通UIコンポーネント │ │ │ └── network/ # HTTPクライアント │ │ ├── features/ # 機能モジュール(MVVM) -│ │ │ ├── chat/ # チャット機能 -│ │ │ │ ├── ChatScreen.kt -│ │ │ │ ├── ChatScreenModel.kt -│ │ │ │ ├── ChatState.kt -│ │ │ │ ├── ChatEffect.kt -│ │ │ │ └── components/ # チャット専用コンポーネント -│ │ │ ├── terminal/ # ターミナル機能 -│ │ │ ├── settings/ # 設定画面 -│ │ │ ├── sidebar/ # サイドバー -│ │ │ ├── systemprompt/ # システムプロンプト編集 -│ │ │ └── navigation/ # ナビゲーション +│ │ │ ├── terminal/ # セッション一覧 / ターミナル接続 / 設定 +│ │ │ ├── navigation/ # ナビゲーション +│ │ │ └── sidebar/ # 画面遷移コンテナ │ │ └── di/ # 依存性注入モジュール │ ├── src/androidMain/ # Android 固有実装 │ └── src/commonTest/ # 共通テスト @@ -55,40 +47,29 @@ frontend/ └── src/main/ # AndroidManifest, MainActivity ``` -### アーキテクチャ +### 画面構成(Screen + ScreenModel + State + Effect) -本プロジェクトは **MVVM (StateFlow + Channel)** アーキテクチャを採用しています。 - -#### 画面構成(Screen + ScreenModel + State + Effect) - -| レイヤー | 役割 | ファイル例 | -| --------------- | -------------------------- | -------------------- | -| **Screen** | Compose UI 表示 | `ChatScreen.kt` | -| **ScreenModel** | ビジネスロジック・状態更新 | `ChatScreenModel.kt` | -| **State** | UI状態(データクラス) | `ChatState.kt` | -| **Effect** | One-shotイベント | `ChatEffect.kt` | - -#### シンプルな画面 - -設定画面など状態遷移が単純な画面はScreenのみとし、State/Effectを省略しています。 -これはIntentionalな設計判断です。 +| レイヤー | 役割 | ファイル例 | +| --------------- | -------------------------- | ---------- | +| **Screen** | Compose UI 表示 | `TerminalScreen.kt` | +| **ScreenModel** | ビジネスロジック・状態更新 | `AgentListScreenModel.kt` | +| **State** | UI状態(データクラス) | `AgentListState.kt` | +| **Effect** | One-shotイベント | `AgentListEffect.kt` | ## ビルド要件 ### 必須ツール - **JDK**: 17 以上(推奨: JDK 21) -- **Android SDK**: API 34(コマンドラインツール) - - Build Tools 34.0.0 - - Platform Tools -- **Gradle**: 8.8+ (Wrapper 同梱) +- **Android SDK**: API 34 以上 +- **Gradle**: Wrapper 同梱 ### テストフレームワーク -- **kotlin-test**: Kotlin 標準テスト -- **Turbine**: Flowのテスト -- **MockK**: モックライブラリ -- **Ktor MockEngine**: HTTPモック +- **kotlin-test** +- **Turbine** +- **MockK** +- **Ktor MockEngine** ## リリース署名 @@ -99,33 +80,11 @@ frontend/ ```bash keytool -genkey -v \ -keystore release.keystore \ - -alias egograph \ + -alias plexus \ -keyalg RSA -keysize 2048 -validity 10000 ``` -### 2. 署名設定 - -`androidApp/build.gradle.kts` に署名設定を追加: - -```kotlin -android { - signingConfigs { - create("release") { - storeFile = file("../release.keystore") - storePassword = System.getenv("KEYSTORE_PASSWORD") - keyAlias = "egograph" - keyPassword = System.getenv("KEY_PASSWORD") - } - } - buildTypes { - release { - signingConfig = signingConfigs.getByName("release") - } - } -} -``` - -### 3. 署名付きリリースビルド +### 2. 署名付きリリースビルド ```bash export KEYSTORE_PASSWORD="your-password" @@ -133,9 +92,3 @@ export KEY_PASSWORD="your-password" ./gradlew :androidApp:assembleRelease ``` - -## 旧バージョン(React + Capacitor) - -React + Capacitor 版はモノレポから分離され、 -[`endo-ava/egograph-frontend-capacitor-legacy`](https://github.com/endo-ava/egograph-frontend-capacitor-legacy) -で保守されています。新規開発はすべて KMP 版で行ってください。 diff --git a/gateway/README.md b/gateway/README.md index b84b9de..e42a914 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -1,12 +1,13 @@ # Plexus Gateway -tmux セッションへの WebSocket 接続とプッシュ通知を提供する独立サービスです。 +tmux session への WebSocket 接続、snapshot 取得、push 通知を提供する runtime API です。 ## 機能 - **tmux セッション管理**: `agent-XXXX` 形式の tmux セッションを列挙・管理 -- **WebSocket 接続**: 端末入出力の双方向通信(予定) -- **プッシュ通知**: FCM 経由の通知送信(予定) +- **WebSocket 接続**: 端末入出力の双方向通信 +- **Snapshot**: セッション内容のプレーンテキスト取得 +- **プッシュ通知**: FCM 経由の通知送信 - **認証**: API Key と Webhook シークレットによる認証 ## 開発 @@ -18,24 +19,16 @@ cd /root/workspace/plexus uv sync ``` -### 環境変数設定 +### 環境変数 -`.env.example` をコピーして `.env` を作成し、必要な環境変数を設定してください。 +必須: -```bash -cp gateway/.env.example gateway/.env -``` - -必須環境変数: - `GATEWAY_API_KEY`: Gateway API Key(32バイト以上推奨) - `GATEWAY_WEBHOOK_SECRET`: Webhook シークレット(32バイト以上推奨) -- `CORS_ORIGINS`: ブラウザアクセスを許可する Origin(本番は `https://.ts.net` を明示) +- `CORS_ORIGINS`: ブラウザアクセスを許可する Origin -`CORS_ORIGINS` の設定指針: -- 本番: `https://.ts.net` を必要数だけカンマ区切りで指定 -- 開発: 一時的に `*` も利用可能(ただし本番非推奨) +プッシュ通知利用時: -プッシュ通知利用時に必要な環境変数: - `FCM_PROJECT_ID`: Firebase プロジェクト ID - `FCM_CREDENTIALS_PATH`: Firebase サービスアカウント JSON パス(省略時 `gateway/firebase-service-account.json`) @@ -48,25 +41,13 @@ uv run python -m gateway.main `uvicorn gateway.main:app` を直接使うと `GATEWAY_HOST` / `GATEWAY_PORT` が反映されないため、 設定値で起動したい場合は `python -m gateway.main` を使用してください。 -`GATEWAY_RELOAD` はデフォルトで `false` です。開発時のみ `true` にしてください。 - -uvicorn を直接使用する場合: - -```bash -uvicorn gateway.main:app --host 127.0.0.1 --port 8001 --reload -``` ### テスト実行 ```bash cd gateway -uv run pytest tests/ -v -``` - -単体テストのみ: - -```bash -uv run pytest tests/unit/ -v +uv run pytest tests -v +uv run pytest tests/unit -v ``` ## API エンドポイント @@ -85,21 +66,18 @@ tmux セッション一覧を取得 - `terminal_ws_token_store` は in-memory 実装です。 - `POST /api/v1/terminal/sessions/{session_id}/ws-token` で発行したトークンは、同じプロセスでの `terminal_ws_token_store.consume` でのみ検証できます。 -- マルチプロセス/マルチPod構成では、sticky-session で同一インスタンスへ到達させるか、共有ストア実装(例: Redis)に置き換えてください。 +- マルチプロセス / マルチPod 構成では、sticky-session で同一インスタンスへ到達させるか、共有ストア実装(例: Redis)に置き換えてください。 ## プロジェクト構成 -``` +```text gateway/ ├── api/ # API ルート ├── domain/ # ドメインモデル ├── infrastructure/ # インフラストラクチャ(DB、tmux) +├── services/ # websocket / push / token store ├── tests/ # テスト ├── config.py # 設定管理 ├── dependencies.py # 依存関数(認証など) └── main.py # アプリケーションエントリーポイント ``` - -## ライセンス - -本プロジェクトの一部として、同じライセンスが適用されます。 From 55e954ddefe856e59352497387598dba2e74a8c3 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:09:24 +0000 Subject: [PATCH 04/14] refactor: rename frontend app identity to plexus --- frontend/.gitignore | 2 +- frontend/androidApp/build.gradle.kts | 6 +- .../androidApp/google-services.json.example | 2 +- .../androidApp/src/main/AndroidManifest.xml | 6 +- .../android/PlexusApplication.kt} | 16 ++--- .../android/fcm/FcmService.kt | 10 ++-- .../android/fcm/FcmTokenManager.kt | 2 +- .../NotificationChannelManager.kt | 8 +-- .../notifications/NotificationDisplayer.kt | 4 +- .../{egograph => plexus}/app/MainActivity.kt | 20 +++---- .../src/main/res/values/strings.xml | 2 +- frontend/shared/build.gradle.kts | 18 +++--- frontend/shared/lint-baseline.xml | 18 +++--- .../{egograph => plexus}/shared/Platform.kt | 2 +- .../shared/cache/DiskCache.kt | 2 +- .../core/network/HttpClientConfigProvider.kt | 4 +- .../shared/core/network/HttpClientProvider.kt | 2 +- .../core/platform/BaseUrlProvider.android.kt | 4 +- .../core/platform/KeyboardState.android.kt | 2 +- .../platform/PlatformPreferences.android.kt | 4 +- .../core/platform/TimeProvider.android.kt | 2 +- .../terminal/PermissionUtil.android.kt | 2 +- .../SpeechRecognizerFactory.android.kt | 2 +- .../terminal/TerminalWebView.android.kt | 2 +- .../ui/components/MermaidDiagram.android.kt | 2 +- .../shared/di/AndroidModule.kt | 6 +- .../components/TerminalView.android.kt | 8 +-- .../core/platform/KeyboardStateAndroidTest.kt | 2 +- .../terminal/AndroidTerminalWebViewTest.kt | 2 +- .../shared/di/TerminalModuleTest.kt | 8 +-- .../terminal/TerminalRepositoryImplTest.kt | 16 ++--- .../dev/{egograph => plexus}/shared/App.kt | 4 +- .../shared/cache/DiskCache.kt | 2 +- .../core/data/repository/BaseRepository.kt | 6 +- .../data/repository/ChatRepositoryImpl.kt | 24 ++++---- .../data/repository/MessageRepositoryImpl.kt | 16 ++--- .../repository/SystemPromptRepositoryImpl.kt | 18 +++--- .../data/repository/TerminalRepositoryImpl.kt | 16 ++--- .../data/repository/ThreadRepositoryImpl.kt | 18 +++--- .../repository/internal/RepositoryClient.kt | 4 +- .../repository/internal/RepositoryUtils.kt | 2 +- .../shared/core/domain/model/ChatRequest.kt | 2 +- .../shared/core/domain/model/ChatResponse.kt | 2 +- .../shared/core/domain/model/LLMModel.kt | 2 +- .../shared/core/domain/model/Message.kt | 2 +- .../core/domain/model/ModelsResponse.kt | 2 +- .../shared/core/domain/model/StreamChunk.kt | 2 +- .../core/domain/model/SystemPromptName.kt | 2 +- .../core/domain/model/SystemPromptResponse.kt | 2 +- .../domain/model/SystemPromptUpdateRequest.kt | 2 +- .../shared/core/domain/model/Thread.kt | 2 +- .../core/domain/model/ThreadListResponse.kt | 2 +- .../shared/core/domain/model/ThreadMessage.kt | 2 +- .../domain/model/ThreadMessagesResponse.kt | 2 +- .../shared/core/domain/model/ToolCall.kt | 2 +- .../shared/core/domain/model/Usage.kt | 2 +- .../core/domain/model/terminal/Session.kt | 2 +- .../domain/model/terminal/TerminalSnapshot.kt | 2 +- .../domain/model/terminal/TerminalWsToken.kt | 2 +- .../shared/core/domain/repository/ApiError.kt | 2 +- .../core/domain/repository/ChatRepository.kt | 10 ++-- .../core/domain/repository/ErrorAction.kt | 2 +- .../domain/repository/MessageRepository.kt | 4 +- .../repository/SystemPromptRepository.kt | 6 +- .../domain/repository/TerminalRepository.kt | 8 +-- .../domain/repository/ThreadRepository.kt | 6 +- .../shared/core/network/HttpClientConfig.kt | 2 +- .../core/network/HttpClientConfigProvider.kt | 2 +- .../shared/core/network/HttpClientProvider.kt | 2 +- .../shared/core/platform/BaseUrlProvider.kt | 2 +- .../shared/core/platform/KeyboardState.kt | 2 +- .../core/platform/PlatformPreferences.kt | 2 +- .../shared/core/platform/TimeProvider.kt | 2 +- .../platform/terminal/ISpeechRecognizer.kt | 2 +- .../core/platform/terminal/PermissionUtil.kt | 2 +- .../terminal/SpeechRecognizerFactory.kt | 2 +- .../terminal/SpeechRecognizerFactoryMock.kt | 2 +- .../core/platform/terminal/TerminalWebView.kt | 2 +- .../shared/core/settings/AppTheme.kt | 2 +- .../shared/core/settings/ThemeRepository.kt | 8 +-- .../core/ui/common/CompactActionButton.kt | 8 +-- .../shared/core/ui/common/DateTimeText.kt | 2 +- .../shared/core/ui/common/ListStateContent.kt | 2 +- .../shared/core/ui/common/SaveNavigation.kt | 2 +- .../core/ui/common/TestTagExtensions.kt | 2 +- .../ui/components/AssistantContentBlock.kt | 2 +- .../shared/core/ui/components/EmptyView.kt | 6 +- .../shared/core/ui/components/ErrorView.kt | 6 +- .../shared/core/ui/components/LoadingView.kt | 6 +- .../core/ui/components/MermaidDiagram.kt | 2 +- .../core/ui/components/SecretTextField.kt | 4 +- .../core/ui/components/SettingsTopBar.kt | 8 +-- .../ui/components/VoiceInputCoordinator.kt | 6 +- .../ui/components/VoiceInputToggleButton.kt | 4 +- .../shared/core/ui/theme/AppTypography.kt | 2 +- .../shared/core/ui/theme/DesignTokens.kt | 28 ++++----- .../shared/core/ui/theme/Theme.kt | 10 ++-- .../shared/di/AppModule.kt | 58 +++++++++---------- .../shared/di/TerminalModule.kt | 18 +++--- .../shared/features/chat/ChatEffect.kt | 2 +- .../shared/features/chat/ChatErrorState.kt | 10 ++-- .../shared/features/chat/ChatScreen.kt | 18 +++--- .../shared/features/chat/ChatScreenModel.kt | 30 +++++----- .../shared/features/chat/ChatState.kt | 8 +-- .../features/chat/components/ChatComposer.kt | 6 +- .../chat/components/ChatComposerComponents.kt | 30 +++++----- .../features/chat/components/ChatMessage.kt | 22 +++---- .../chat/components/ChatModelSelector.kt | 4 +- .../features/chat/components/ErrorBanner.kt | 24 ++++---- .../features/chat/components/MessageList.kt | 14 ++--- .../features/chat/components/ModelSelector.kt | 12 ++-- .../chat/reducer/ChatStreamReduceResult.kt | 8 +-- .../features/chat/threads/ThreadItem.kt | 14 ++--- .../features/chat/threads/ThreadList.kt | 18 +++--- .../features/chat/threads/ThreadListEmpty.kt | 6 +- .../features/chat/threads/ThreadListError.kt | 6 +- .../chat/threads/ThreadListLoading.kt | 6 +- .../features/chat/threads/ThreadListScreen.kt | 6 +- .../chat/threads/ThreadTitleFormatter.kt | 2 +- .../features/navigation/MainNavigationHost.kt | 2 +- .../shared/features/navigation/MainView.kt | 2 +- .../features/navigation/MainViewTransition.kt | 2 +- .../navigation/SwipeNavigationContainer.kt | 2 +- .../features/settings/SettingsEffect.kt | 2 +- .../features/settings/SettingsScreen.kt | 26 ++++----- .../features/settings/SettingsScreenModel.kt | 16 ++--- .../shared/features/settings/SettingsState.kt | 4 +- .../shared/features/sidebar/SidebarFooter.kt | 16 ++--- .../shared/features/sidebar/SidebarHeader.kt | 8 +-- .../shared/features/sidebar/SidebarScreen.kt | 34 +++++------ .../systemprompt/SystemPromptEditor.kt | 4 +- .../systemprompt/SystemPromptEditorEffect.kt | 2 +- .../systemprompt/SystemPromptEditorScreen.kt | 8 +-- .../SystemPromptEditorScreenModel.kt | 6 +- .../systemprompt/SystemPromptEditorState.kt | 4 +- .../features/systemprompt/SystemPromptTabs.kt | 6 +- .../features/terminal/TerminalTestTags.kt | 2 +- .../terminal/agentlist/AgentListEffect.kt | 2 +- .../terminal/agentlist/AgentListScreen.kt | 4 +- .../agentlist/AgentListScreenModel.kt | 8 +-- .../terminal/agentlist/AgentListState.kt | 4 +- .../agentlist/components/SessionList.kt | 18 +++--- .../agentlist/components/SessionListEmpty.kt | 6 +- .../agentlist/components/SessionListError.kt | 6 +- .../agentlist/components/SessionListItem.kt | 22 +++---- .../components/SessionListLoading.kt | 6 +- .../terminal/session/TerminalCopyModeSheet.kt | 16 ++--- .../session/TerminalReconnectBackoff.kt | 2 +- .../terminal/session/TerminalScreen.kt | 24 ++++---- .../terminal/session/TerminalSettings.kt | 12 ++-- .../session/TerminalVoiceInputCoordinator.kt | 4 +- .../DraggableTerminalFloatingControlPill.kt | 2 +- .../session/components/SpecialKeysBar.kt | 12 ++-- .../session/components/TerminalControls.kt | 14 ++--- .../session/components/TerminalHeader.kt | 14 ++--- .../session/components/TerminalView.kt | 4 +- .../settings/GatewaySettingsEffect.kt | 2 +- .../settings/GatewaySettingsScreen.kt | 12 ++-- .../settings/GatewaySettingsScreenModel.kt | 14 ++--- .../terminal/settings/GatewaySettingsState.kt | 2 +- .../core/domain/model/DtoSerializationTest.kt | 2 +- .../core/domain/model/terminal/SessionTest.kt | 2 +- .../core/domain/repository/ApiErrorTest.kt | 2 +- .../core/domain/repository/RepositoryTest.kt | 22 +++---- .../shared/core/network/KtorConfigTest.kt | 2 +- .../core/platform/BaseUrlProviderTest.kt | 6 +- .../shared/core/platform/KeyboardStateTest.kt | 2 +- .../platform/terminal/PermissionUtilTest.kt | 2 +- .../shared/core/settings/AppThemeTest.kt | 2 +- .../core/settings/ThemeRepositoryTest.kt | 4 +- .../shared/core/ui/common/DateTimeTextTest.kt | 2 +- .../core/ui/components/MermaidDiagramTest.kt | 2 +- .../shared/di/KoinDiTest.kt | 16 ++--- .../features/chat/ChatErrorStateTest.kt | 26 ++++----- .../features/chat/ChatRepositoryImplTest.kt | 28 ++++----- .../shared/features/chat/ChatStateTest.kt | 2 +- .../shared/features/chat/ChatUtilsTest.kt | 4 +- .../chat/MessageRepositoryImplTest.kt | 16 ++--- .../features/chat/ThreadRepositoryImplTest.kt | 12 ++-- .../features/settings/SettingsStateTest.kt | 8 +-- .../SystemPromptEditorStateTest.kt | 12 ++-- .../SystemPromptRepositoryImplTest.kt | 12 ++-- .../terminal/agentlist/AgentListStateTest.kt | 2 +- .../components/SessionListItemStateTest.kt | 6 +- .../session/TerminalReconnectBackoffTest.kt | 2 +- ...raggableTerminalFloatingControlPillTest.kt | 2 +- .../settings/GatewaySettingsStateTest.kt | 2 +- 187 files changed, 691 insertions(+), 691 deletions(-) rename frontend/androidApp/src/main/kotlin/dev/{egograph/android/EgoGraphApplication.kt => plexus/android/PlexusApplication.kt} (67%) rename frontend/androidApp/src/main/kotlin/dev/{egograph => plexus}/android/fcm/FcmService.kt (94%) rename frontend/androidApp/src/main/kotlin/dev/{egograph => plexus}/android/fcm/FcmTokenManager.kt (99%) rename frontend/androidApp/src/main/kotlin/dev/{egograph => plexus}/android/notifications/NotificationChannelManager.kt (86%) rename frontend/androidApp/src/main/kotlin/dev/{egograph => plexus}/android/notifications/NotificationDisplayer.kt (96%) rename frontend/androidApp/src/main/kotlin/dev/{egograph => plexus}/app/MainActivity.kt (88%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/Platform.kt (75%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/cache/DiskCache.kt (99%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/network/HttpClientConfigProvider.kt (81%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/network/HttpClientProvider.kt (98%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/platform/BaseUrlProvider.android.kt (94%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/platform/KeyboardState.android.kt (96%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/platform/PlatformPreferences.android.kt (89%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/platform/TimeProvider.android.kt (88%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/PermissionUtil.android.kt (98%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/SpeechRecognizerFactory.android.kt (99%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/TerminalWebView.android.kt (99%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/MermaidDiagram.android.kt (99%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/di/AndroidModule.kt (68%) rename frontend/shared/src/androidMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/components/TerminalView.android.kt (79%) rename frontend/shared/src/androidUnitTest/kotlin/dev/{egograph => plexus}/shared/core/platform/KeyboardStateAndroidTest.kt (98%) rename frontend/shared/src/androidUnitTest/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/AndroidTerminalWebViewTest.kt (84%) rename frontend/shared/src/androidUnitTest/kotlin/dev/{egograph => plexus}/shared/di/TerminalModuleTest.kt (86%) rename frontend/shared/src/androidUnitTest/kotlin/dev/{egograph => plexus}/shared/features/terminal/TerminalRepositoryImplTest.kt (97%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/App.kt (87%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/cache/DiskCache.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/data/repository/BaseRepository.kt (84%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/data/repository/ChatRepositoryImpl.kt (88%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/data/repository/MessageRepositoryImpl.kt (79%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/data/repository/SystemPromptRepositoryImpl.kt (75%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/data/repository/TerminalRepositoryImpl.kt (84%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/data/repository/ThreadRepositoryImpl.kt (86%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/data/repository/internal/RepositoryClient.kt (98%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/data/repository/internal/RepositoryUtils.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/ChatRequest.kt (88%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/ChatResponse.kt (90%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/LLMModel.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/Message.kt (93%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/ModelsResponse.kt (85%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/StreamChunk.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/SystemPromptName.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/SystemPromptResponse.kt (80%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/SystemPromptUpdateRequest.kt (78%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/Thread.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/ThreadListResponse.kt (83%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/ThreadMessage.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/ThreadMessagesResponse.kt (86%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/ToolCall.kt (84%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/Usage.kt (88%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/terminal/Session.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/terminal/TerminalSnapshot.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/model/terminal/TerminalWsToken.kt (90%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/ApiError.kt (99%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/ChatRepository.kt (78%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/ErrorAction.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/MessageRepository.kt (84%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/SystemPromptRepository.kt (60%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/TerminalRepository.kt (84%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/ThreadRepository.kt (85%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/network/HttpClientConfig.kt (97%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/network/HttpClientConfigProvider.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/network/HttpClientProvider.kt (90%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/BaseUrlProvider.kt (96%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/KeyboardState.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/PlatformPreferences.kt (96%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/TimeProvider.kt (69%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/ISpeechRecognizer.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/PermissionUtil.kt (96%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/SpeechRecognizerFactory.kt (79%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/SpeechRecognizerFactoryMock.kt (93%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/TerminalWebView.kt (97%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/settings/AppTheme.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/settings/ThemeRepository.kt (85%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/common/CompactActionButton.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/common/DateTimeText.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/common/ListStateContent.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/common/SaveNavigation.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/common/TestTagExtensions.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/AssistantContentBlock.kt (97%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/EmptyView.kt (86%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/ErrorView.kt (85%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/LoadingView.kt (84%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/MermaidDiagram.kt (86%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/SecretTextField.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/SettingsTopBar.kt (83%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/VoiceInputCoordinator.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/components/VoiceInputToggleButton.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/theme/AppTypography.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/theme/DesignTokens.kt (76%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/core/ui/theme/Theme.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/di/AppModule.kt (67%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/di/TerminalModule.kt (73%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatEffect.kt (96%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatErrorState.kt (93%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatScreen.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatScreenModel.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatState.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/components/ChatComposer.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/components/ChatComposerComponents.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/components/ChatMessage.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/components/ChatModelSelector.kt (82%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/components/ErrorBanner.kt (69%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/components/MessageList.kt (93%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/components/ModelSelector.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/reducer/ChatStreamReduceResult.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/threads/ThreadItem.kt (86%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/threads/ThreadList.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/threads/ThreadListEmpty.kt (83%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/threads/ThreadListError.kt (83%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/threads/ThreadListLoading.kt (84%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/threads/ThreadListScreen.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/chat/threads/ThreadTitleFormatter.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/navigation/MainNavigationHost.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/navigation/MainView.kt (77%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/navigation/MainViewTransition.kt (97%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/navigation/SwipeNavigationContainer.kt (98%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/settings/SettingsEffect.kt (88%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/settings/SettingsScreen.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/settings/SettingsScreenModel.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/settings/SettingsState.kt (82%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/sidebar/SidebarFooter.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/sidebar/SidebarHeader.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/sidebar/SidebarScreen.kt (90%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/systemprompt/SystemPromptEditor.kt (90%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/systemprompt/SystemPromptEditorEffect.kt (85%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/systemprompt/SystemPromptEditorScreen.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/systemprompt/SystemPromptEditorScreenModel.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/systemprompt/SystemPromptEditorState.kt (85%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/systemprompt/SystemPromptTabs.kt (86%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/TerminalTestTags.kt (96%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/AgentListEffect.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/AgentListScreen.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/AgentListScreenModel.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/AgentListState.kt (76%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/components/SessionList.kt (93%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/components/SessionListEmpty.kt (91%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/components/SessionListError.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/components/SessionListItem.kt (94%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/components/SessionListLoading.kt (88%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/TerminalCopyModeSheet.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/TerminalReconnectBackoff.kt (98%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/TerminalScreen.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/TerminalSettings.kt (87%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/TerminalVoiceInputCoordinator.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/components/DraggableTerminalFloatingControlPill.kt (99%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/components/SpecialKeysBar.kt (95%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/components/TerminalControls.kt (89%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/components/TerminalHeader.kt (92%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/components/TerminalView.kt (77%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/settings/GatewaySettingsEffect.kt (86%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/settings/GatewaySettingsScreen.kt (93%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/settings/GatewaySettingsScreenModel.kt (90%) rename frontend/shared/src/commonMain/kotlin/dev/{egograph => plexus}/shared/features/terminal/settings/GatewaySettingsState.kt (89%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/domain/model/DtoSerializationTest.kt (98%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/domain/model/terminal/SessionTest.kt (99%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/ApiErrorTest.kt (99%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/domain/repository/RepositoryTest.kt (93%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/network/KtorConfigTest.kt (97%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/platform/BaseUrlProviderTest.kt (94%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/platform/KeyboardStateTest.kt (97%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/platform/terminal/PermissionUtilTest.kt (99%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/settings/AppThemeTest.kt (98%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/settings/ThemeRepositoryTest.kt (89%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/ui/common/DateTimeTextTest.kt (97%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/core/ui/components/MermaidDiagramTest.kt (94%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/di/KoinDiTest.kt (90%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatErrorStateTest.kt (83%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatRepositoryImplTest.kt (96%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatStateTest.kt (98%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/chat/ChatUtilsTest.kt (98%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/chat/MessageRepositoryImplTest.kt (96%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/chat/ThreadRepositoryImplTest.kt (97%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/settings/SettingsStateTest.kt (77%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/systemprompt/SystemPromptEditorStateTest.kt (87%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/systemprompt/SystemPromptRepositoryImplTest.kt (97%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/AgentListStateTest.kt (90%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/terminal/agentlist/components/SessionListItemStateTest.kt (91%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/TerminalReconnectBackoffTest.kt (99%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/terminal/session/components/DraggableTerminalFloatingControlPillTest.kt (99%) rename frontend/shared/src/commonTest/kotlin/dev/{egograph => plexus}/shared/features/terminal/settings/GatewaySettingsStateTest.kt (98%) diff --git a/frontend/.gitignore b/frontend/.gitignore index 227436f..b74ae08 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -61,7 +61,7 @@ captures/ .cxx/ # Google Services (e.g. APIs or Firebase) -# google-services.json +google-services.json # Freeline freeline.py diff --git a/frontend/androidApp/build.gradle.kts b/frontend/androidApp/build.gradle.kts index 9bc1d1b..74b1fc0 100644 --- a/frontend/androidApp/build.gradle.kts +++ b/frontend/androidApp/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } android { - namespace = "dev.egograph.app" + namespace = "dev.plexus.app" compileSdk = 36 val keystorePath = "debug.keystore" @@ -25,14 +25,14 @@ android { create("release") { storeFile = file(keystorePath) storePassword = keystorePassword - keyAlias = "egograph_debug" + keyAlias = "plexus_debug" keyPassword = keystorePassword } } } defaultConfig { - applicationId = "dev.egograph.app" + applicationId = "dev.plexus.app" minSdk = 24 targetSdk = 35 versionCode = System.getenv("VERSION_CODE")?.toIntOrNull() ?: 1 diff --git a/frontend/androidApp/google-services.json.example b/frontend/androidApp/google-services.json.example index 38efb99..f1f88d7 100644 --- a/frontend/androidApp/google-services.json.example +++ b/frontend/androidApp/google-services.json.example @@ -9,7 +9,7 @@ "client_info": { "mobilesdk_app_id": "YOUR_APP_ID", "android_client_info": { - "package_name": "dev.egograph.app" + "package_name": "dev.plexus.app" } }, "oauth_client": [], diff --git a/frontend/androidApp/src/main/AndroidManifest.xml b/frontend/androidApp/src/main/AndroidManifest.xml index a92a05e..8383cf3 100644 --- a/frontend/androidApp/src/main/AndroidManifest.xml +++ b/frontend/androidApp/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ @@ -26,7 +26,7 @@ diff --git a/frontend/androidApp/src/main/kotlin/dev/egograph/android/EgoGraphApplication.kt b/frontend/androidApp/src/main/kotlin/dev/plexus/android/PlexusApplication.kt similarity index 67% rename from frontend/androidApp/src/main/kotlin/dev/egograph/android/EgoGraphApplication.kt rename to frontend/androidApp/src/main/kotlin/dev/plexus/android/PlexusApplication.kt index 1ed41dd..177ac13 100644 --- a/frontend/androidApp/src/main/kotlin/dev/egograph/android/EgoGraphApplication.kt +++ b/frontend/androidApp/src/main/kotlin/dev/plexus/android/PlexusApplication.kt @@ -1,20 +1,20 @@ -package dev.egograph.android +package dev.plexus.android import android.app.Application -import dev.egograph.android.notifications.NotificationChannelManager -import dev.egograph.shared.di.androidModule -import dev.egograph.shared.di.appModule -import dev.egograph.shared.di.terminalModule +import dev.plexus.android.notifications.NotificationChannelManager +import dev.plexus.shared.di.androidModule +import dev.plexus.shared.di.appModule +import dev.plexus.shared.di.terminalModule import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin /** - * EgoGraph Application class + * Plexus Application class * * Initializes Koin dependency injection container on app startup. * Must be declared in AndroidManifest.xml as the application class. */ -class EgoGraphApplication : Application() { +class PlexusApplication : Application() { /** * Application entry point * @@ -28,7 +28,7 @@ class EgoGraphApplication : Application() { // Initialize Koin DI container startKoin { - androidContext(this@EgoGraphApplication) + androidContext(this@PlexusApplication) modules(appModule, androidModule, terminalModule) } } diff --git a/frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmService.kt b/frontend/androidApp/src/main/kotlin/dev/plexus/android/fcm/FcmService.kt similarity index 94% rename from frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmService.kt rename to frontend/androidApp/src/main/kotlin/dev/plexus/android/fcm/FcmService.kt index 67b3c63..9406d8d 100644 --- a/frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmService.kt +++ b/frontend/androidApp/src/main/kotlin/dev/plexus/android/fcm/FcmService.kt @@ -1,11 +1,11 @@ -package dev.egograph.android.fcm +package dev.plexus.android.fcm import android.util.Log import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import dev.egograph.android.notifications.NotificationChannelManager -import dev.egograph.android.notifications.NotificationDisplayer +import dev.plexus.android.notifications.NotificationChannelManager +import dev.plexus.android.notifications.NotificationDisplayer /** Firebase Cloud Messaging サービス。 @@ -14,7 +14,7 @@ FCMトークンの更新とメッセージ受信を処理します。 class FcmService : FirebaseMessagingService() { companion object { private const val TAG = "FcmService" - private const val PREFS_NAME = "egograph_prefs" + private const val PREFS_NAME = "plexus_prefs" private const val KEY_GATEWAY_API_URL = "gateway_api_url" private const val KEY_GATEWAY_API_KEY = "gateway_api_key" private const val TOKEN_PREVIEW_LENGTH = 10 @@ -110,7 +110,7 @@ class FcmService : FirebaseMessagingService() { // 通知を表示 NotificationDisplayer.showNotification( context = this, - title = notification.title ?: "EgoGraph", + title = notification.title ?: "Plexus", message = notification.body ?: "New notification", ) } diff --git a/frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmTokenManager.kt b/frontend/androidApp/src/main/kotlin/dev/plexus/android/fcm/FcmTokenManager.kt similarity index 99% rename from frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmTokenManager.kt rename to frontend/androidApp/src/main/kotlin/dev/plexus/android/fcm/FcmTokenManager.kt index 4c69e11..0acfbf1 100644 --- a/frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmTokenManager.kt +++ b/frontend/androidApp/src/main/kotlin/dev/plexus/android/fcm/FcmTokenManager.kt @@ -1,4 +1,4 @@ -package dev.egograph.android.fcm +package dev.plexus.android.fcm import android.util.Log import kotlinx.coroutines.CancellationException diff --git a/frontend/androidApp/src/main/kotlin/dev/egograph/android/notifications/NotificationChannelManager.kt b/frontend/androidApp/src/main/kotlin/dev/plexus/android/notifications/NotificationChannelManager.kt similarity index 86% rename from frontend/androidApp/src/main/kotlin/dev/egograph/android/notifications/NotificationChannelManager.kt rename to frontend/androidApp/src/main/kotlin/dev/plexus/android/notifications/NotificationChannelManager.kt index be83c3e..15ce7d0 100644 --- a/frontend/androidApp/src/main/kotlin/dev/egograph/android/notifications/NotificationChannelManager.kt +++ b/frontend/androidApp/src/main/kotlin/dev/plexus/android/notifications/NotificationChannelManager.kt @@ -1,4 +1,4 @@ -package dev.egograph.android.notifications +package dev.plexus.android.notifications import android.app.NotificationChannel import android.app.NotificationManager @@ -13,9 +13,9 @@ import androidx.core.content.getSystemService * Android 13 (API 33) 以降、POST_NOTIFICATIONSパーミッションも必要です。 */ object NotificationChannelManager { - private const val CHANNEL_ID = "egograph_terminal" - private const val CHANNEL_NAME = "Terminal Notifications" - private const val CHANNEL_DESCRIPTION = "Notifications for terminal events" + private const val CHANNEL_ID = "plexus_terminal" + private const val CHANNEL_NAME = "Plexus Terminal" + private const val CHANNEL_DESCRIPTION = "Notifications for Plexus terminal events" /** * 通知チャンネルを作成する diff --git a/frontend/androidApp/src/main/kotlin/dev/egograph/android/notifications/NotificationDisplayer.kt b/frontend/androidApp/src/main/kotlin/dev/plexus/android/notifications/NotificationDisplayer.kt similarity index 96% rename from frontend/androidApp/src/main/kotlin/dev/egograph/android/notifications/NotificationDisplayer.kt rename to frontend/androidApp/src/main/kotlin/dev/plexus/android/notifications/NotificationDisplayer.kt index 9ce9d9c..5aff5be 100644 --- a/frontend/androidApp/src/main/kotlin/dev/egograph/android/notifications/NotificationDisplayer.kt +++ b/frontend/androidApp/src/main/kotlin/dev/plexus/android/notifications/NotificationDisplayer.kt @@ -1,4 +1,4 @@ -package dev.egograph.android.notifications +package dev.plexus.android.notifications import android.app.NotificationManager import android.app.PendingIntent @@ -6,7 +6,7 @@ import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat import androidx.core.content.getSystemService -import dev.egograph.app.MainActivity +import dev.plexus.app.MainActivity /** * 通知表示ユーティリティ diff --git a/frontend/androidApp/src/main/kotlin/dev/egograph/app/MainActivity.kt b/frontend/androidApp/src/main/kotlin/dev/plexus/app/MainActivity.kt similarity index 88% rename from frontend/androidApp/src/main/kotlin/dev/egograph/app/MainActivity.kt rename to frontend/androidApp/src/main/kotlin/dev/plexus/app/MainActivity.kt index 1aeb80d..be2d867 100644 --- a/frontend/androidApp/src/main/kotlin/dev/egograph/app/MainActivity.kt +++ b/frontend/androidApp/src/main/kotlin/dev/plexus/app/MainActivity.kt @@ -1,4 +1,4 @@ -package dev.egograph.app +package dev.plexus.app import android.Manifest import android.os.Build @@ -16,13 +16,13 @@ import androidx.lifecycle.LifecycleEventObserver import cafe.adriel.voyager.navigator.CurrentScreen import cafe.adriel.voyager.navigator.Navigator import com.google.firebase.messaging.FirebaseMessaging -import dev.egograph.android.fcm.FcmTokenManager -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.core.platform.terminal.ActivityRecorder -import dev.egograph.shared.core.settings.AppTheme -import dev.egograph.shared.core.settings.ThemeRepository -import dev.egograph.shared.core.ui.theme.EgoGraphTheme -import dev.egograph.shared.features.sidebar.SidebarScreen +import dev.plexus.android.fcm.FcmTokenManager +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.platform.terminal.ActivityRecorder +import dev.plexus.shared.core.settings.AppTheme +import dev.plexus.shared.core.settings.ThemeRepository +import dev.plexus.shared.core.ui.theme.PlexusTheme +import dev.plexus.shared.features.sidebar.SidebarScreen import org.koin.compose.KoinContext import org.koin.compose.koinInject @@ -96,7 +96,7 @@ class MainActivity : ComponentActivity() { AppTheme.SYSTEM -> isSystemInDarkTheme() } - EgoGraphTheme(darkTheme = darkTheme) { + PlexusTheme(darkTheme = darkTheme) { Navigator(SidebarScreen()) { CurrentScreen() } @@ -109,7 +109,7 @@ class MainActivity : ComponentActivity() { private fun getTokenManager(): FcmTokenManager? { fcmTokenManager?.let { return it } - val prefs = getSharedPreferences("egograph_prefs", MODE_PRIVATE) + val prefs = getSharedPreferences("plexus_prefs", MODE_PRIVATE) val gatewayUrl = prefs.getString(PlatformPrefsKeys.KEY_GATEWAY_API_URL, "")?.trim().orEmpty() val apiKey = prefs.getString(PlatformPrefsKeys.KEY_GATEWAY_API_KEY, "")?.trim().orEmpty() diff --git a/frontend/androidApp/src/main/res/values/strings.xml b/frontend/androidApp/src/main/res/values/strings.xml index 8db0688..9ceec24 100644 --- a/frontend/androidApp/src/main/res/values/strings.xml +++ b/frontend/androidApp/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - EgoGraph + Plexus diff --git a/frontend/shared/build.gradle.kts b/frontend/shared/build.gradle.kts index a136848..c86520d 100644 --- a/frontend/shared/build.gradle.kts +++ b/frontend/shared/build.gradle.kts @@ -41,13 +41,13 @@ fun loadDotenv(file: java.io.File): Map { val dotenv = loadDotenv(rootProject.projectDir.resolve(".env")) val debugBaseUrl = - dotenv["EGOGRAPH_BASE_URL_DEBUG"]?.takeIf { it.isNotBlank() } - ?: project.findProperty("EGOGRAPH_BASE_URL_DEBUG") as? String + dotenv["PLEXUS_BASE_URL_DEBUG"]?.takeIf { it.isNotBlank() } + ?: project.findProperty("PLEXUS_BASE_URL_DEBUG") as? String ?: "http://10.0.2.2:8000" val debugGatewayBaseUrl = - dotenv["EGOGRAPH_GATEWAY_BASE_URL_DEBUG"]?.takeIf { it.isNotBlank() } - ?: project.findProperty("EGOGRAPH_GATEWAY_BASE_URL_DEBUG") as? String + dotenv["PLEXUS_GATEWAY_BASE_URL_DEBUG"]?.takeIf { it.isNotBlank() } + ?: project.findProperty("PLEXUS_GATEWAY_BASE_URL_DEBUG") as? String ?: "http://10.0.2.2:8001" kotlin { @@ -116,7 +116,7 @@ kotlin { } android { - namespace = "dev.egograph.shared" + namespace = "dev.plexus.shared" compileSdk = 36 defaultConfig { @@ -129,12 +129,12 @@ android { buildConfigField( "String", "STAGING_BASE_URL", - "\"${project.findProperty("EGOGRAPH_BASE_URL_STAGING") ?: "http://192.168.0.2:8000"}\"", + "\"${project.findProperty("PLEXUS_BASE_URL_STAGING") ?: "http://192.168.0.2:8000"}\"", ) buildConfigField( "String", "RELEASE_BASE_URL", - "\"${project.findProperty("EGOGRAPH_BASE_URL_RELEASE") ?: "https://api.egograph.dev"}\"", + "\"${project.findProperty("PLEXUS_BASE_URL_RELEASE") ?: "https://api.plexus.dev"}\"", ) buildConfigField( "String", @@ -144,12 +144,12 @@ android { buildConfigField( "String", "STAGING_GATEWAY_BASE_URL", - "\"${project.findProperty("EGOGRAPH_GATEWAY_BASE_URL_STAGING") ?: "http://192.168.0.2:8001"}\"", + "\"${project.findProperty("PLEXUS_GATEWAY_BASE_URL_STAGING") ?: "http://192.168.0.2:8001"}\"", ) buildConfigField( "String", "RELEASE_GATEWAY_BASE_URL", - "\"${project.findProperty("EGOGRAPH_GATEWAY_BASE_URL_RELEASE") ?: "https://gateway.egograph.dev"}\"", + "\"${project.findProperty("PLEXUS_GATEWAY_BASE_URL_RELEASE") ?: "https://gateway.plexus.dev"}\"", ) } diff --git a/frontend/shared/lint-baseline.xml b/frontend/shared/lint-baseline.xml index 30cd847..0eb01ad 100644 --- a/frontend/shared/lint-baseline.xml +++ b/frontend/shared/lint-baseline.xml @@ -7,7 +7,7 @@ errorLine1=" launcher.launch(Manifest.permission.POST_NOTIFICATIONS)" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -18,7 +18,7 @@ errorLine1=" Manifest.permission.POST_NOTIFICATIONS," errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -29,7 +29,7 @@ errorLine1=" javaScriptEnabled = true" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -40,7 +40,7 @@ errorLine1="/**" errorLine2="^"> @@ -51,7 +51,7 @@ errorLine1=" var currentActivity: Context? = null" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -62,7 +62,7 @@ errorLine1=" prefs.edit().putString(key, value).apply()" errorLine2=" ~~~~~~~~~~~~"> @@ -73,7 +73,7 @@ errorLine1=" prefs.edit().putBoolean(key, value).apply()" errorLine2=" ~~~~~~~~~~~~"> @@ -84,7 +84,7 @@ errorLine1=" setBackgroundColor(Color.parseColor("#1e1e1e"))" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -95,7 +95,7 @@ errorLine1=" _webView.setBackgroundColor(Color.parseColor(backgroundColor))" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/Platform.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/Platform.kt similarity index 75% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/Platform.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/Platform.kt index 71c9263..a1f95e2 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/Platform.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/Platform.kt @@ -1,3 +1,3 @@ -package dev.egograph.shared +package dev.plexus.shared actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}" diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/cache/DiskCache.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/cache/DiskCache.kt similarity index 99% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/cache/DiskCache.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/cache/DiskCache.kt index 3f10a13..529d5de 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/cache/DiskCache.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/cache/DiskCache.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.cache +package dev.plexus.shared.cache import co.touchlab.kermit.Logger import kotlinx.coroutines.Dispatchers diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/network/HttpClientConfigProvider.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/network/HttpClientConfigProvider.kt similarity index 81% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/network/HttpClientConfigProvider.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/network/HttpClientConfigProvider.kt index 1ee7302..c59b6d8 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/network/HttpClientConfigProvider.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/network/HttpClientConfigProvider.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.core.network +package dev.plexus.shared.core.network -import dev.egograph.shared.BuildConfig +import dev.plexus.shared.BuildConfig /** * Android用HttpClientConfigProviderの実装 diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/network/HttpClientProvider.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/network/HttpClientProvider.kt similarity index 98% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/network/HttpClientProvider.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/network/HttpClientProvider.kt index 4be8365..33a3aef 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/network/HttpClientProvider.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/network/HttpClientProvider.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.network +package dev.plexus.shared.core.network import io.ktor.client.HttpClient import io.ktor.client.engine.okhttp.OkHttp diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/BaseUrlProvider.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/BaseUrlProvider.android.kt similarity index 94% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/BaseUrlProvider.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/BaseUrlProvider.android.kt index b05d9b7..0f6bdb4 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/BaseUrlProvider.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/BaseUrlProvider.android.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform -import dev.egograph.shared.BuildConfig +import dev.plexus.shared.BuildConfig actual fun getDefaultBaseUrl(): String = when (BuildConfig.BUILD_TYPE) { diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/KeyboardState.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/KeyboardState.android.kt similarity index 96% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/KeyboardState.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/KeyboardState.android.kt index 96a6900..e5e4e7f 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/KeyboardState.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/KeyboardState.android.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform import android.graphics.Rect import android.view.ViewTreeObserver diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/PlatformPreferences.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.android.kt similarity index 89% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/PlatformPreferences.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.android.kt index f2c05ca..24c20bf 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/PlatformPreferences.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.android.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform import android.content.Context import android.content.SharedPreferences @@ -37,6 +37,6 @@ actual class PlatformPreferences( } companion object { - private const val PREFS_NAME = "egograph_prefs" + private const val PREFS_NAME = "plexus_prefs" } } diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/TimeProvider.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/TimeProvider.android.kt similarity index 88% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/TimeProvider.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/TimeProvider.android.kt index b057f8a..0763d26 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/TimeProvider.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/TimeProvider.android.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform import java.text.SimpleDateFormat import java.util.Date diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtil.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtil.android.kt similarity index 98% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtil.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtil.android.kt index ea313ea..c0ba734 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtil.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtil.android.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal import android.Manifest import android.content.pm.PackageManager diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactory.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactory.android.kt similarity index 99% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactory.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactory.android.kt index d94bfbc..b18ce9c 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactory.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactory.android.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal import android.content.Context import android.content.Intent diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/TerminalWebView.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/TerminalWebView.android.kt similarity index 99% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/TerminalWebView.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/TerminalWebView.android.kt index 7d24bc5..f82cd31 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/platform/terminal/TerminalWebView.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/platform/terminal/TerminalWebView.android.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal import android.content.ClipboardManager import android.content.Context diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagram.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagram.android.kt similarity index 99% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagram.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagram.android.kt index 61bea8c..cd2a6f5 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagram.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagram.android.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import android.annotation.SuppressLint import android.os.Handler diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/di/AndroidModule.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/di/AndroidModule.kt similarity index 68% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/di/AndroidModule.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/di/AndroidModule.kt index ca5707d..1865ac1 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/di/AndroidModule.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/di/AndroidModule.kt @@ -1,8 +1,8 @@ -package dev.egograph.shared.di +package dev.plexus.shared.di import android.content.Context -import dev.egograph.shared.cache.DiskCacheContext -import dev.egograph.shared.core.platform.PlatformPreferences +import dev.plexus.shared.cache.DiskCacheContext +import dev.plexus.shared.core.platform.PlatformPreferences import org.koin.dsl.module val androidModule = diff --git a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalView.android.kt b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalView.android.kt similarity index 79% rename from frontend/shared/src/androidMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalView.android.kt rename to frontend/shared/src/androidMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalView.android.kt index b9848ce..ae2fe04 100644 --- a/frontend/shared/src/androidMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalView.android.kt +++ b/frontend/shared/src/androidMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalView.android.kt @@ -1,13 +1,13 @@ -package dev.egograph.shared.features.terminal.session.components +package dev.plexus.shared.features.terminal.session.components import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView -import dev.egograph.shared.core.platform.terminal.AndroidTerminalWebView -import dev.egograph.shared.core.platform.terminal.TerminalWebView -import dev.egograph.shared.core.platform.terminal.createTerminalWebView +import dev.plexus.shared.core.platform.terminal.AndroidTerminalWebView +import dev.plexus.shared.core.platform.terminal.TerminalWebView +import dev.plexus.shared.core.platform.terminal.createTerminalWebView private const val TAG = "TerminalView" diff --git a/frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/core/platform/KeyboardStateAndroidTest.kt b/frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/core/platform/KeyboardStateAndroidTest.kt similarity index 98% rename from frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/core/platform/KeyboardStateAndroidTest.kt rename to frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/core/platform/KeyboardStateAndroidTest.kt index dbeccaa..b099182 100644 --- a/frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/core/platform/KeyboardStateAndroidTest.kt +++ b/frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/core/platform/KeyboardStateAndroidTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform import androidx.compose.ui.unit.dp import kotlin.test.Test diff --git a/frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/core/platform/terminal/AndroidTerminalWebViewTest.kt b/frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/core/platform/terminal/AndroidTerminalWebViewTest.kt similarity index 84% rename from frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/core/platform/terminal/AndroidTerminalWebViewTest.kt rename to frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/core/platform/terminal/AndroidTerminalWebViewTest.kt index 7c36d3b..a8b7b6a 100644 --- a/frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/core/platform/terminal/AndroidTerminalWebViewTest.kt +++ b/frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/core/platform/terminal/AndroidTerminalWebViewTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal import kotlin.test.Test import kotlin.test.assertFailsWith diff --git a/frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/di/TerminalModuleTest.kt b/frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/di/TerminalModuleTest.kt similarity index 86% rename from frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/di/TerminalModuleTest.kt rename to frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/di/TerminalModuleTest.kt index 454ca72..d867d35 100644 --- a/frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/di/TerminalModuleTest.kt +++ b/frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/di/TerminalModuleTest.kt @@ -1,8 +1,8 @@ -package dev.egograph.shared.di +package dev.plexus.shared.di -import dev.egograph.shared.core.domain.repository.TerminalRepository -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.domain.repository.TerminalRepository +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsKeys import io.ktor.client.HttpClient import io.mockk.every import io.mockk.mockk diff --git a/frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/features/terminal/TerminalRepositoryImplTest.kt b/frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/features/terminal/TerminalRepositoryImplTest.kt similarity index 97% rename from frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/features/terminal/TerminalRepositoryImplTest.kt rename to frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/features/terminal/TerminalRepositoryImplTest.kt index 36f2b7a..33b6657 100644 --- a/frontend/shared/src/androidUnitTest/kotlin/dev/egograph/shared/features/terminal/TerminalRepositoryImplTest.kt +++ b/frontend/shared/src/androidUnitTest/kotlin/dev/plexus/shared/features/terminal/TerminalRepositoryImplTest.kt @@ -1,11 +1,11 @@ -package dev.egograph.shared.features.terminal - -import dev.egograph.shared.core.data.repository.TerminalRepositoryImpl -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.terminal.Session -import dev.egograph.shared.core.domain.model.terminal.SessionStatus -import dev.egograph.shared.core.domain.model.terminal.TerminalSnapshot -import dev.egograph.shared.core.domain.repository.ApiError +package dev.plexus.shared.features.terminal + +import dev.plexus.shared.core.data.repository.TerminalRepositoryImpl +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.terminal.Session +import dev.plexus.shared.core.domain.model.terminal.SessionStatus +import dev.plexus.shared.core.domain.model.terminal.TerminalSnapshot +import dev.plexus.shared.core.domain.repository.ApiError import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/App.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/App.kt similarity index 87% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/App.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/App.kt index 49bd116..e221e75 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/App.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/App.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared +package dev.plexus.shared import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -15,7 +15,7 @@ fun App() { Surface(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text( - text = "Hello from EgoGraph KMP!\n${getPlatformName()}", + text = "Hello from Plexus KMP!\n${getPlatformName()}", style = MaterialTheme.typography.headlineMedium, ) } diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/cache/DiskCache.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/cache/DiskCache.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/cache/DiskCache.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/cache/DiskCache.kt index 0591701..c737519 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/cache/DiskCache.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/cache/DiskCache.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.cache +package dev.plexus.shared.cache import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/BaseRepository.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/BaseRepository.kt similarity index 84% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/BaseRepository.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/BaseRepository.kt index 160f8cd..5f505f3 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/BaseRepository.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/BaseRepository.kt @@ -1,7 +1,7 @@ -package dev.egograph.shared.core.data.repository +package dev.plexus.shared.core.data.repository -import dev.egograph.shared.core.domain.repository.ApiError -import dev.egograph.shared.core.domain.repository.RepositoryResult +import dev.plexus.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.domain.repository.RepositoryResult import kotlinx.serialization.SerializationException /** diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/ChatRepositoryImpl.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/ChatRepositoryImpl.kt similarity index 88% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/ChatRepositoryImpl.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/ChatRepositoryImpl.kt index b4cfc35..4add42b 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/ChatRepositoryImpl.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/ChatRepositoryImpl.kt @@ -1,16 +1,16 @@ -package dev.egograph.shared.core.data.repository +package dev.plexus.shared.core.data.repository -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.ChatRequest -import dev.egograph.shared.core.domain.model.ChatResponse -import dev.egograph.shared.core.domain.model.ModelsResponse -import dev.egograph.shared.core.domain.model.StreamChunk -import dev.egograph.shared.core.domain.model.StreamChunkType -import dev.egograph.shared.core.domain.repository.ApiError -import dev.egograph.shared.core.domain.repository.ChatRepository -import dev.egograph.shared.core.domain.repository.RepositoryResult -import dev.egograph.shared.core.domain.repository.TimeoutType -import dev.egograph.shared.core.network.HttpClientConfig +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.ChatRequest +import dev.plexus.shared.core.domain.model.ChatResponse +import dev.plexus.shared.core.domain.model.ModelsResponse +import dev.plexus.shared.core.domain.model.StreamChunk +import dev.plexus.shared.core.domain.model.StreamChunkType +import dev.plexus.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.domain.repository.ChatRepository +import dev.plexus.shared.core.domain.repository.RepositoryResult +import dev.plexus.shared.core.domain.repository.TimeoutType +import dev.plexus.shared.core.network.HttpClientConfig import io.ktor.client.statement.bodyAsChannel import io.ktor.http.ContentType import io.ktor.http.contentType diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/MessageRepositoryImpl.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/MessageRepositoryImpl.kt similarity index 79% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/MessageRepositoryImpl.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/MessageRepositoryImpl.kt index 2e8d320..cb977dd 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/MessageRepositoryImpl.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/MessageRepositoryImpl.kt @@ -1,12 +1,12 @@ -package dev.egograph.shared.core.data.repository +package dev.plexus.shared.core.data.repository -import dev.egograph.shared.cache.DiskCache -import dev.egograph.shared.core.data.repository.internal.InMemoryCache -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.ThreadMessagesResponse -import dev.egograph.shared.core.domain.repository.ApiError -import dev.egograph.shared.core.domain.repository.MessageRepository -import dev.egograph.shared.core.domain.repository.RepositoryResult +import dev.plexus.shared.cache.DiskCache +import dev.plexus.shared.core.data.repository.internal.InMemoryCache +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.ThreadMessagesResponse +import dev.plexus.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.domain.repository.MessageRepository +import dev.plexus.shared.core.domain.repository.RepositoryResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/SystemPromptRepositoryImpl.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/SystemPromptRepositoryImpl.kt similarity index 75% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/SystemPromptRepositoryImpl.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/SystemPromptRepositoryImpl.kt index f57195a..5f1035d 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/SystemPromptRepositoryImpl.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/SystemPromptRepositoryImpl.kt @@ -1,13 +1,13 @@ -package dev.egograph.shared.core.data.repository +package dev.plexus.shared.core.data.repository -import dev.egograph.shared.cache.DiskCache -import dev.egograph.shared.core.data.repository.internal.InMemoryCache -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.SystemPromptName -import dev.egograph.shared.core.domain.model.SystemPromptResponse -import dev.egograph.shared.core.domain.model.SystemPromptUpdateRequest -import dev.egograph.shared.core.domain.repository.RepositoryResult -import dev.egograph.shared.core.domain.repository.SystemPromptRepository +import dev.plexus.shared.cache.DiskCache +import dev.plexus.shared.core.data.repository.internal.InMemoryCache +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.SystemPromptName +import dev.plexus.shared.core.domain.model.SystemPromptResponse +import dev.plexus.shared.core.domain.model.SystemPromptUpdateRequest +import dev.plexus.shared.core.domain.repository.RepositoryResult +import dev.plexus.shared.core.domain.repository.SystemPromptRepository /** * SystemPromptRepositoryの実装 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/TerminalRepositoryImpl.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/TerminalRepositoryImpl.kt similarity index 84% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/TerminalRepositoryImpl.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/TerminalRepositoryImpl.kt index 75516ba..f198be0 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/TerminalRepositoryImpl.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/TerminalRepositoryImpl.kt @@ -1,12 +1,12 @@ -package dev.egograph.shared.core.data.repository +package dev.plexus.shared.core.data.repository -import dev.egograph.shared.core.data.repository.internal.InMemoryCache -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.terminal.Session -import dev.egograph.shared.core.domain.model.terminal.TerminalSnapshot -import dev.egograph.shared.core.domain.model.terminal.TerminalWsToken -import dev.egograph.shared.core.domain.repository.RepositoryResult -import dev.egograph.shared.core.domain.repository.TerminalRepository +import dev.plexus.shared.core.data.repository.internal.InMemoryCache +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.terminal.Session +import dev.plexus.shared.core.domain.model.terminal.TerminalSnapshot +import dev.plexus.shared.core.domain.model.terminal.TerminalWsToken +import dev.plexus.shared.core.domain.repository.RepositoryResult +import dev.plexus.shared.core.domain.repository.TerminalRepository import io.ktor.http.encodeURLPathPart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/ThreadRepositoryImpl.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/ThreadRepositoryImpl.kt similarity index 86% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/ThreadRepositoryImpl.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/ThreadRepositoryImpl.kt index 1cb67cf..4e8f29d 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/ThreadRepositoryImpl.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/ThreadRepositoryImpl.kt @@ -1,13 +1,13 @@ -package dev.egograph.shared.core.data.repository +package dev.plexus.shared.core.data.repository -import dev.egograph.shared.cache.DiskCache -import dev.egograph.shared.core.data.repository.internal.InMemoryCache -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.Thread -import dev.egograph.shared.core.domain.model.ThreadListResponse -import dev.egograph.shared.core.domain.repository.ApiError -import dev.egograph.shared.core.domain.repository.RepositoryResult -import dev.egograph.shared.core.domain.repository.ThreadRepository +import dev.plexus.shared.cache.DiskCache +import dev.plexus.shared.core.data.repository.internal.InMemoryCache +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.Thread +import dev.plexus.shared.core.domain.model.ThreadListResponse +import dev.plexus.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.domain.repository.RepositoryResult +import dev.plexus.shared.core.domain.repository.ThreadRepository import io.ktor.client.request.parameter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/internal/RepositoryClient.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/internal/RepositoryClient.kt similarity index 98% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/internal/RepositoryClient.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/internal/RepositoryClient.kt index 5081854..6d9c2d6 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/internal/RepositoryClient.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/internal/RepositoryClient.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.core.data.repository.internal +package dev.plexus.shared.core.data.repository.internal -import dev.egograph.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.domain.repository.ApiError import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.HttpRequestBuilder diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/internal/RepositoryUtils.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/internal/RepositoryUtils.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/internal/RepositoryUtils.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/internal/RepositoryUtils.kt index a3d8dd6..c769d3c 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/data/repository/internal/RepositoryUtils.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/data/repository/internal/RepositoryUtils.kt @@ -1,6 +1,6 @@ @file:OptIn(kotlin.time.ExperimentalTime::class) -package dev.egograph.shared.core.data.repository.internal +package dev.plexus.shared.core.data.repository.internal import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ChatRequest.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ChatRequest.kt similarity index 88% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ChatRequest.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ChatRequest.kt index 0c3620c..c32abb7 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ChatRequest.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ChatRequest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ChatResponse.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ChatResponse.kt similarity index 90% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ChatResponse.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ChatResponse.kt index f0f79a7..8f85e33 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ChatResponse.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ChatResponse.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/LLMModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/LLMModel.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/LLMModel.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/LLMModel.kt index b9d992c..167953e 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/LLMModel.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/LLMModel.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Message.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Message.kt similarity index 93% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Message.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Message.kt index 5676817..571ad35 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Message.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Message.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ModelsResponse.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ModelsResponse.kt similarity index 85% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ModelsResponse.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ModelsResponse.kt index 1c10669..a49df5d 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ModelsResponse.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ModelsResponse.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/StreamChunk.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/StreamChunk.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/StreamChunk.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/StreamChunk.kt index e96234e..2f84c37 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/StreamChunk.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/StreamChunk.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptName.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptName.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptName.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptName.kt index 48b1eee..56832d2 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptName.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptName.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptResponse.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptResponse.kt similarity index 80% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptResponse.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptResponse.kt index b166b5b..84dc7f2 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptResponse.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptResponse.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptUpdateRequest.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptUpdateRequest.kt similarity index 78% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptUpdateRequest.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptUpdateRequest.kt index 5d1e606..afc21d1 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/SystemPromptUpdateRequest.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/SystemPromptUpdateRequest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Thread.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Thread.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Thread.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Thread.kt index 85dfdc1..f394a2a 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Thread.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Thread.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadListResponse.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadListResponse.kt similarity index 83% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadListResponse.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadListResponse.kt index 5125d7f..a26a527 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadListResponse.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadListResponse.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadMessage.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadMessage.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadMessage.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadMessage.kt index 3e3302a..e5c2de2 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadMessage.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadMessage.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadMessagesResponse.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadMessagesResponse.kt similarity index 86% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadMessagesResponse.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadMessagesResponse.kt index 6d1ef62..f578e23 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ThreadMessagesResponse.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ThreadMessagesResponse.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ToolCall.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ToolCall.kt similarity index 84% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ToolCall.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ToolCall.kt index dc6e06d..fe41e18 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/ToolCall.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/ToolCall.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Usage.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Usage.kt similarity index 88% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Usage.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Usage.kt index 3b44e13..64ed3f9 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/Usage.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/Usage.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/Session.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/Session.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/Session.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/Session.kt index d71cba2..4ff2df3 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/Session.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/Session.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model.terminal +package dev.plexus.shared.core.domain.model.terminal import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/TerminalSnapshot.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/TerminalSnapshot.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/TerminalSnapshot.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/TerminalSnapshot.kt index b201f30..e4b36dd 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/TerminalSnapshot.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/TerminalSnapshot.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model.terminal +package dev.plexus.shared.core.domain.model.terminal import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/TerminalWsToken.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/TerminalWsToken.kt similarity index 90% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/TerminalWsToken.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/TerminalWsToken.kt index 4348ff6..1380d42 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/model/terminal/TerminalWsToken.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/model/terminal/TerminalWsToken.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model.terminal +package dev.plexus.shared.core.domain.model.terminal import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ApiError.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ApiError.kt similarity index 99% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ApiError.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ApiError.kt index b15c135..81ce6d6 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ApiError.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ApiError.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.repository +package dev.plexus.shared.core.domain.repository sealed class ApiError protected constructor( cause: Throwable? = null, diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ChatRepository.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ChatRepository.kt similarity index 78% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ChatRepository.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ChatRepository.kt index 954ed05..bc7590c 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ChatRepository.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ChatRepository.kt @@ -1,9 +1,9 @@ -package dev.egograph.shared.core.domain.repository +package dev.plexus.shared.core.domain.repository -import dev.egograph.shared.core.domain.model.ChatRequest -import dev.egograph.shared.core.domain.model.ChatResponse -import dev.egograph.shared.core.domain.model.ModelsResponse -import dev.egograph.shared.core.domain.model.StreamChunk +import dev.plexus.shared.core.domain.model.ChatRequest +import dev.plexus.shared.core.domain.model.ChatResponse +import dev.plexus.shared.core.domain.model.ModelsResponse +import dev.plexus.shared.core.domain.model.StreamChunk import kotlinx.coroutines.flow.Flow /** diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ErrorAction.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ErrorAction.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ErrorAction.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ErrorAction.kt index c0f653c..d0588b1 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ErrorAction.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ErrorAction.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.repository +package dev.plexus.shared.core.domain.repository /** * ユーザーに提示するエラーアクションの種類 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/MessageRepository.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/MessageRepository.kt similarity index 84% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/MessageRepository.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/MessageRepository.kt index 3ab4723..3ec6731 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/MessageRepository.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/MessageRepository.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.core.domain.repository +package dev.plexus.shared.core.domain.repository -import dev.egograph.shared.core.domain.model.ThreadMessagesResponse +import dev.plexus.shared.core.domain.model.ThreadMessagesResponse import kotlinx.coroutines.flow.Flow /** diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/SystemPromptRepository.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/SystemPromptRepository.kt similarity index 60% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/SystemPromptRepository.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/SystemPromptRepository.kt index 793bf4e..9f80352 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/SystemPromptRepository.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/SystemPromptRepository.kt @@ -1,7 +1,7 @@ -package dev.egograph.shared.core.domain.repository +package dev.plexus.shared.core.domain.repository -import dev.egograph.shared.core.domain.model.SystemPromptName -import dev.egograph.shared.core.domain.model.SystemPromptResponse +import dev.plexus.shared.core.domain.model.SystemPromptName +import dev.plexus.shared.core.domain.model.SystemPromptResponse interface SystemPromptRepository { suspend fun getSystemPrompt(name: SystemPromptName): RepositoryResult diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/TerminalRepository.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/TerminalRepository.kt similarity index 84% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/TerminalRepository.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/TerminalRepository.kt index 2af8e43..30f80b7 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/TerminalRepository.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/TerminalRepository.kt @@ -1,8 +1,8 @@ -package dev.egograph.shared.core.domain.repository +package dev.plexus.shared.core.domain.repository -import dev.egograph.shared.core.domain.model.terminal.Session -import dev.egograph.shared.core.domain.model.terminal.TerminalSnapshot -import dev.egograph.shared.core.domain.model.terminal.TerminalWsToken +import dev.plexus.shared.core.domain.model.terminal.Session +import dev.plexus.shared.core.domain.model.terminal.TerminalSnapshot +import dev.plexus.shared.core.domain.model.terminal.TerminalWsToken import kotlinx.coroutines.flow.Flow /** diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ThreadRepository.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ThreadRepository.kt similarity index 85% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ThreadRepository.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ThreadRepository.kt index 0b036d7..41f0e76 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/domain/repository/ThreadRepository.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/domain/repository/ThreadRepository.kt @@ -1,7 +1,7 @@ -package dev.egograph.shared.core.domain.repository +package dev.plexus.shared.core.domain.repository -import dev.egograph.shared.core.domain.model.Thread -import dev.egograph.shared.core.domain.model.ThreadListResponse +import dev.plexus.shared.core.domain.model.Thread +import dev.plexus.shared.core.domain.model.ThreadListResponse import kotlinx.coroutines.flow.Flow /** diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientConfig.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientConfig.kt similarity index 97% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientConfig.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientConfig.kt index 62a425b..d321820 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientConfig.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientConfig.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.network +package dev.plexus.shared.core.network /** * HTTPクライアントの設定 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientConfigProvider.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientConfigProvider.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientConfigProvider.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientConfigProvider.kt index f67bb03..06460ad 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientConfigProvider.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientConfigProvider.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.network +package dev.plexus.shared.core.network /** * プラットフォーム固有のHTTPクライアント設定を提供する diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientProvider.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientProvider.kt similarity index 90% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientProvider.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientProvider.kt index 5deab73..7d2f07f 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/network/HttpClientProvider.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/network/HttpClientProvider.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.network +package dev.plexus.shared.core.network import io.ktor.client.HttpClient diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/BaseUrlProvider.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/BaseUrlProvider.kt similarity index 96% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/BaseUrlProvider.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/BaseUrlProvider.kt index d103f2e..a9e250c 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/BaseUrlProvider.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/BaseUrlProvider.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform expect fun getDefaultBaseUrl(): String diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/KeyboardState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/KeyboardState.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/KeyboardState.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/KeyboardState.kt index 1c3cdc3..1e88fdb 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/KeyboardState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/KeyboardState.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/PlatformPreferences.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.kt similarity index 96% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/PlatformPreferences.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.kt index 318d8c5..1aeabbc 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/PlatformPreferences.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform /** * Platform preferences (expect class) diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/TimeProvider.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/TimeProvider.kt similarity index 69% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/TimeProvider.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/TimeProvider.kt index eebdc9b..81d9f7d 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/TimeProvider.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/TimeProvider.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform /** * 現在時刻のISO-8601文字列を返す。 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/ISpeechRecognizer.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/ISpeechRecognizer.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/ISpeechRecognizer.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/ISpeechRecognizer.kt index cdc391c..8f8c917 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/ISpeechRecognizer.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/ISpeechRecognizer.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal import kotlinx.coroutines.flow.Flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtil.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtil.kt similarity index 96% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtil.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtil.kt index b3c54af..6655862 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtil.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtil.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal /** * パーミッションリクエストの結果 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactory.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactory.kt similarity index 79% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactory.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactory.kt index 62be653..2f671ba 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactory.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactory.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal /** * プラットフォーム固有のSpeechRecognizerを作成する diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactoryMock.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactoryMock.kt similarity index 93% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactoryMock.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactoryMock.kt index 28bab39..03cc971 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/SpeechRecognizerFactoryMock.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/SpeechRecognizerFactoryMock.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/TerminalWebView.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/TerminalWebView.kt similarity index 97% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/TerminalWebView.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/TerminalWebView.kt index 7fb7c1c..9287fb7 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/platform/terminal/TerminalWebView.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/terminal/TerminalWebView.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal import kotlinx.coroutines.flow.Flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/settings/AppTheme.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/AppTheme.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/settings/AppTheme.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/AppTheme.kt index fe4810e..0480af8 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/settings/AppTheme.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/AppTheme.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.settings +package dev.plexus.shared.core.settings /** * アプリテーマ diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/settings/ThemeRepository.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/ThemeRepository.kt similarity index 85% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/settings/ThemeRepository.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/ThemeRepository.kt index c428ff5..23bc251 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/settings/ThemeRepository.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/ThemeRepository.kt @@ -1,8 +1,8 @@ -package dev.egograph.shared.core.settings +package dev.plexus.shared.core.settings -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsDefaults -import dev.egograph.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsDefaults +import dev.plexus.shared.core.platform.PlatformPrefsKeys import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/CompactActionButton.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/CompactActionButton.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/CompactActionButton.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/CompactActionButton.kt index 0e3ac93..b7f4c48 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/CompactActionButton.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/CompactActionButton.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.common +package dev.plexus.shared.core.ui.common import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer @@ -12,7 +12,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * コンパクト表示用の共通アクションボタン。 @@ -25,8 +25,8 @@ internal fun CompactActionButton( testTag: String, text: String? = null, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes val buttonHeight = dimens.space28 val buttonPadding = PaddingValues(horizontal = dimens.space8) val iconSize = dimens.iconSizeSmall diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/DateTimeText.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/DateTimeText.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/DateTimeText.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/DateTimeText.kt index 10444b0..19b11b9 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/DateTimeText.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/DateTimeText.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.common +package dev.plexus.shared.core.ui.common import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/ListStateContent.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/ListStateContent.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/ListStateContent.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/ListStateContent.kt index dd29dbe..b42c121 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/ListStateContent.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/ListStateContent.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.common +package dev.plexus.shared.core.ui.common import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/SaveNavigation.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/SaveNavigation.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/SaveNavigation.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/SaveNavigation.kt index 9c74513..2c5b16d 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/SaveNavigation.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/SaveNavigation.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.common +package dev.plexus.shared.core.ui.common import androidx.compose.material3.SnackbarHostState import kotlinx.coroutines.CoroutineScope diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/TestTagExtensions.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/TestTagExtensions.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/TestTagExtensions.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/TestTagExtensions.kt index 5dc4634..9e30edb 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/common/TestTagExtensions.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/common/TestTagExtensions.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.common +package dev.plexus.shared.core.ui.common import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/AssistantContentBlock.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/AssistantContentBlock.kt similarity index 97% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/AssistantContentBlock.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/AssistantContentBlock.kt index 42c8b6c..2a9906d 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/AssistantContentBlock.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/AssistantContentBlock.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components private val mermaidFenceRegex = Regex( diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/EmptyView.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/EmptyView.kt similarity index 86% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/EmptyView.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/EmptyView.kt index 7db9354..5589fac 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/EmptyView.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/EmptyView.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -8,7 +8,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * 空状態表示コンポーネント @@ -21,7 +21,7 @@ fun EmptyView( modifier: Modifier = Modifier, message: String = "No content", ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Box( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/ErrorView.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/ErrorView.kt similarity index 85% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/ErrorView.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/ErrorView.kt index 5983603..d9e31b4 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/ErrorView.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/ErrorView.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -8,7 +8,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * エラー表示コンポーネント @@ -21,7 +21,7 @@ fun ErrorView( message: String, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Box( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/LoadingView.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/LoadingView.kt similarity index 84% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/LoadingView.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/LoadingView.kt index 20f2c30..694f02b 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/LoadingView.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/LoadingView.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -9,7 +9,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * ローディング表示コンポーネント @@ -24,7 +24,7 @@ fun LoadingView( message: String = "Loading...", height: Dp? = null, ) { - val resolvedHeight = height ?: EgoGraphThemeTokens.dimens.listLoadingHeight + val resolvedHeight = height ?: PlexusThemeTokens.dimens.listLoadingHeight Box( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagram.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagram.kt similarity index 86% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagram.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagram.kt index afdcf1c..55076f0 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagram.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagram.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/SecretTextField.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/SecretTextField.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/SecretTextField.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/SecretTextField.kt index c24f2e2..ff5f31a 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/SecretTextField.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/SecretTextField.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Lock @@ -15,7 +15,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation -import dev.egograph.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.common.testTagResourceId @Composable internal fun SecretTextField( diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/SettingsTopBar.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/SettingsTopBar.kt similarity index 83% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/SettingsTopBar.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/SettingsTopBar.kt index 676b067..c3e6446 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/SettingsTopBar.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/SettingsTopBar.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.height @@ -9,7 +9,7 @@ import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -17,8 +17,8 @@ internal fun SettingsTopBar( title: String, onBack: () -> Unit, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes CenterAlignedTopAppBar( title = { Text(title) }, diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/VoiceInputCoordinator.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/VoiceInputCoordinator.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/VoiceInputCoordinator.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/VoiceInputCoordinator.kt index 0092161..9ca56e3 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/VoiceInputCoordinator.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/VoiceInputCoordinator.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -7,8 +7,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import dev.egograph.shared.core.platform.terminal.createPermissionUtil -import dev.egograph.shared.core.platform.terminal.createSpeechRecognizer +import dev.plexus.shared.core.platform.terminal.createPermissionUtil +import dev.plexus.shared.core.platform.terminal.createSpeechRecognizer import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectLatest diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/VoiceInputToggleButton.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/VoiceInputToggleButton.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/VoiceInputToggleButton.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/VoiceInputToggleButton.kt index 2495929..4ac4287 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/components/VoiceInputToggleButton.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/components/VoiceInputToggleButton.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Mic @@ -9,7 +9,7 @@ import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.common.testTagResourceId /** * 音声入力の開始/停止を切り替えるアイコンボタン。 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/AppTypography.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/AppTypography.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/AppTypography.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/AppTypography.kt index 4c3524c..43c41d1 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/AppTypography.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/AppTypography.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.theme +package dev.plexus.shared.core.ui.theme import androidx.compose.material3.Typography import androidx.compose.runtime.Composable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/DesignTokens.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/DesignTokens.kt similarity index 76% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/DesignTokens.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/DesignTokens.kt index 4ea63ba..e0e84a6 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/DesignTokens.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/DesignTokens.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.theme +package dev.plexus.shared.core.ui.theme import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -14,7 +14,7 @@ import androidx.compose.ui.unit.dp private fun Color.luminance(): Float = (0.299f * red + 0.587f * green + 0.114f * blue) @Immutable -data class EgoGraphDimens( +data class PlexusDimens( val zero: Dp = 0.dp, val space2: Dp = 2.dp, val space4: Dp = 4.dp, @@ -48,7 +48,7 @@ data class EgoGraphDimens( ) @Immutable -data class EgoGraphShapes( +data class PlexusShapes( val radiusXs: Shape = RoundedCornerShape(6.dp), val radiusSm: Shape = RoundedCornerShape(8.dp), val radiusMd: Shape = RoundedCornerShape(12.dp), @@ -58,27 +58,27 @@ data class EgoGraphShapes( ) @Immutable -data class EgoGraphExtendedColors( +data class PlexusExtendedColors( val success: Color = Color(0xFF4CAF50), // 接続成功、正常状態(緑) ) -internal val LocalEgoGraphDimens = staticCompositionLocalOf { EgoGraphDimens() } -internal val LocalEgoGraphShapes = staticCompositionLocalOf { EgoGraphShapes() } -internal val LocalEgoGraphExtendedColors = staticCompositionLocalOf { EgoGraphExtendedColors() } +internal val LocalPlexusDimens = staticCompositionLocalOf { PlexusDimens() } +internal val LocalPlexusShapes = staticCompositionLocalOf { PlexusShapes() } +internal val LocalPlexusExtendedColors = staticCompositionLocalOf { PlexusExtendedColors() } -object EgoGraphThemeTokens { - val dimens: EgoGraphDimens +object PlexusThemeTokens { + val dimens: PlexusDimens @Composable @ReadOnlyComposable - get() = LocalEgoGraphDimens.current + get() = LocalPlexusDimens.current - val shapes: EgoGraphShapes + val shapes: PlexusShapes @Composable @ReadOnlyComposable - get() = LocalEgoGraphShapes.current + get() = LocalPlexusShapes.current - val extendedColors: EgoGraphExtendedColors + val extendedColors: PlexusExtendedColors @Composable @ReadOnlyComposable - get() = LocalEgoGraphExtendedColors.current + get() = LocalPlexusExtendedColors.current } diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/Theme.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/Theme.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/Theme.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/Theme.kt index b0a7ead..4ea7658 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/core/ui/theme/Theme.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/ui/theme/Theme.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.theme +package dev.plexus.shared.core.ui.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme @@ -128,16 +128,16 @@ private val DarkColorScheme = ) @Composable -fun EgoGraphTheme( +fun PlexusTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit, ) { val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme CompositionLocalProvider( - LocalEgoGraphDimens provides EgoGraphDimens(), - LocalEgoGraphShapes provides EgoGraphShapes(), - LocalEgoGraphExtendedColors provides EgoGraphExtendedColors(), + LocalPlexusDimens provides PlexusDimens(), + LocalPlexusShapes provides PlexusShapes(), + LocalPlexusExtendedColors provides PlexusExtendedColors(), ) { MaterialTheme( colorScheme = colorScheme, diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/di/AppModule.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt similarity index 67% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/di/AppModule.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt index 869d611..6ebc3ba 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/di/AppModule.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt @@ -1,32 +1,32 @@ -package dev.egograph.shared.di - -import dev.egograph.shared.cache.DiskCache -import dev.egograph.shared.cache.DiskCacheContext -import dev.egograph.shared.core.data.repository.ChatRepositoryImpl -import dev.egograph.shared.core.data.repository.MessageRepositoryImpl -import dev.egograph.shared.core.data.repository.SystemPromptRepositoryImpl -import dev.egograph.shared.core.data.repository.ThreadRepositoryImpl -import dev.egograph.shared.core.data.repository.internal.InMemoryCache -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.repository.ChatRepository -import dev.egograph.shared.core.domain.repository.MessageRepository -import dev.egograph.shared.core.domain.repository.SystemPromptRepository -import dev.egograph.shared.core.domain.repository.ThreadRepository -import dev.egograph.shared.core.network.HttpClientConfig -import dev.egograph.shared.core.network.HttpClientConfigProvider -import dev.egograph.shared.core.network.provideHttpClient -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsDefaults -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.core.platform.getDefaultBaseUrl -import dev.egograph.shared.core.platform.normalizeBaseUrl -import dev.egograph.shared.core.settings.ThemeRepository -import dev.egograph.shared.core.settings.ThemeRepositoryImpl -import dev.egograph.shared.features.chat.ChatScreenModel -import dev.egograph.shared.features.settings.SettingsScreenModel -import dev.egograph.shared.features.systemprompt.SystemPromptEditorScreenModel -import dev.egograph.shared.features.terminal.agentlist.AgentListScreenModel -import dev.egograph.shared.features.terminal.settings.GatewaySettingsScreenModel +package dev.plexus.shared.di + +import dev.plexus.shared.cache.DiskCache +import dev.plexus.shared.cache.DiskCacheContext +import dev.plexus.shared.core.data.repository.ChatRepositoryImpl +import dev.plexus.shared.core.data.repository.MessageRepositoryImpl +import dev.plexus.shared.core.data.repository.SystemPromptRepositoryImpl +import dev.plexus.shared.core.data.repository.ThreadRepositoryImpl +import dev.plexus.shared.core.data.repository.internal.InMemoryCache +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.repository.ChatRepository +import dev.plexus.shared.core.domain.repository.MessageRepository +import dev.plexus.shared.core.domain.repository.SystemPromptRepository +import dev.plexus.shared.core.domain.repository.ThreadRepository +import dev.plexus.shared.core.network.HttpClientConfig +import dev.plexus.shared.core.network.HttpClientConfigProvider +import dev.plexus.shared.core.network.provideHttpClient +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsDefaults +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.platform.getDefaultBaseUrl +import dev.plexus.shared.core.platform.normalizeBaseUrl +import dev.plexus.shared.core.settings.ThemeRepository +import dev.plexus.shared.core.settings.ThemeRepositoryImpl +import dev.plexus.shared.features.chat.ChatScreenModel +import dev.plexus.shared.features.settings.SettingsScreenModel +import dev.plexus.shared.features.systemprompt.SystemPromptEditorScreenModel +import dev.plexus.shared.features.terminal.agentlist.AgentListScreenModel +import dev.plexus.shared.features.terminal.settings.GatewaySettingsScreenModel import io.ktor.client.HttpClient import org.koin.core.qualifier.named import org.koin.dsl.module diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/di/TerminalModule.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/TerminalModule.kt similarity index 73% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/di/TerminalModule.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/TerminalModule.kt index 5fb4356..cdd9be5 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/di/TerminalModule.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/TerminalModule.kt @@ -1,13 +1,13 @@ -package dev.egograph.shared.di +package dev.plexus.shared.di -import dev.egograph.shared.core.data.repository.TerminalRepositoryImpl -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.repository.TerminalRepository -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsDefaults -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.core.platform.getDefaultGatewayBaseUrl -import dev.egograph.shared.core.platform.normalizeBaseUrl +import dev.plexus.shared.core.data.repository.TerminalRepositoryImpl +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.repository.TerminalRepository +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsDefaults +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.platform.getDefaultGatewayBaseUrl +import dev.plexus.shared.core.platform.normalizeBaseUrl import org.koin.core.qualifier.named import org.koin.dsl.module diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatEffect.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatEffect.kt similarity index 96% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatEffect.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatEffect.kt index 29a1977..cf0d9cf 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatEffect.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatEffect.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat /** * チャット画面のOne-shotイベント (Effect) diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatErrorState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatErrorState.kt similarity index 93% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatErrorState.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatErrorState.kt index f78beae..60df962 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatErrorState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatErrorState.kt @@ -1,9 +1,9 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat -import dev.egograph.shared.core.domain.repository.ApiError -import dev.egograph.shared.core.domain.repository.ErrorAction -import dev.egograph.shared.core.domain.repository.ErrorSeverity -import dev.egograph.shared.core.domain.repository.TimeoutType +import dev.plexus.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.domain.repository.ErrorAction +import dev.plexus.shared.core.domain.repository.ErrorSeverity +import dev.plexus.shared.core.domain.repository.TimeoutType /** * チャット機能のエラー状態 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreen.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatScreen.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreen.kt index 99d05ee..5edf4ce 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreen.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -30,12 +30,12 @@ import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.koinScreenModel import co.touchlab.kermit.Logger -import dev.egograph.shared.core.ui.common.CompactActionButton -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens -import dev.egograph.shared.features.chat.components.ChatComposer -import dev.egograph.shared.features.chat.components.ErrorBanner -import dev.egograph.shared.features.chat.components.MessageList +import dev.plexus.shared.core.ui.common.CompactActionButton +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens +import dev.plexus.shared.features.chat.components.ChatComposer +import dev.plexus.shared.features.chat.components.ErrorBanner +import dev.plexus.shared.features.chat.components.MessageList import kotlinx.serialization.Transient /** @@ -50,7 +50,7 @@ class ChatScreen( ) : Screen { @Composable override fun Content() { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens val screenModel = koinScreenModel() val state by screenModel.state.collectAsState() val snackbarHostState = remember { SnackbarHostState() } @@ -145,7 +145,7 @@ private fun ChatTopActions( onNewChat: () -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Row( modifier = modifier.fillMaxWidth(), diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatScreenModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreenModel.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatScreenModel.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreenModel.kt index d8534f7..4b2c688 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatScreenModel.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreenModel.kt @@ -1,19 +1,19 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope -import dev.egograph.shared.core.domain.model.ChatRequest -import dev.egograph.shared.core.domain.model.Message -import dev.egograph.shared.core.domain.model.MessageRole -import dev.egograph.shared.core.domain.model.StreamChunk -import dev.egograph.shared.core.domain.model.ThreadMessage -import dev.egograph.shared.core.domain.repository.ApiError -import dev.egograph.shared.core.domain.repository.ChatRepository -import dev.egograph.shared.core.domain.repository.MessageRepository -import dev.egograph.shared.core.domain.repository.ThreadRepository -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.features.chat.reducer.reduceChatStreamChunk +import dev.plexus.shared.core.domain.model.ChatRequest +import dev.plexus.shared.core.domain.model.Message +import dev.plexus.shared.core.domain.model.MessageRole +import dev.plexus.shared.core.domain.model.StreamChunk +import dev.plexus.shared.core.domain.model.ThreadMessage +import dev.plexus.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.domain.repository.ChatRepository +import dev.plexus.shared.core.domain.repository.MessageRepository +import dev.plexus.shared.core.domain.repository.ThreadRepository +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.features.chat.reducer.reduceChatStreamChunk import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -445,9 +445,9 @@ class ChatScreenModel( is ApiError -> error.toChatErrorState() else -> ChatErrorState( - type = dev.egograph.shared.features.chat.ErrorType.UNKNOWN, + type = dev.plexus.shared.features.chat.ErrorType.UNKNOWN, message = "メッセージ送信に失敗: ${error.message}", - action = dev.egograph.shared.core.domain.repository.ErrorAction.DISMISS, + action = dev.plexus.shared.core.domain.repository.ErrorAction.DISMISS, ) } diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatState.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatState.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatState.kt index 096d186..83c4861 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/ChatState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatState.kt @@ -1,8 +1,8 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat -import dev.egograph.shared.core.domain.model.LLMModel -import dev.egograph.shared.core.domain.model.Thread -import dev.egograph.shared.core.domain.model.ThreadMessage +import dev.plexus.shared.core.domain.model.LLMModel +import dev.plexus.shared.core.domain.model.Thread +import dev.plexus.shared.core.domain.model.ThreadMessage /** * チャット画面の状態 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatComposer.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposer.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatComposer.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposer.kt index bff5484..5688268 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatComposer.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposer.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.components +package dev.plexus.shared.features.chat.components import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -8,8 +8,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import dev.egograph.shared.core.domain.model.LLMModel -import dev.egograph.shared.core.ui.components.rememberVoiceInputCoordinator +import dev.plexus.shared.core.domain.model.LLMModel +import dev.plexus.shared.core.ui.components.rememberVoiceInputCoordinator /** * チャット入力欄と送信/音声入力操作をまとめたコンポーザー。 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatComposerComponents.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposerComponents.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatComposerComponents.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposerComponents.kt index e4e10b1..cb7a7f5 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatComposerComponents.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposerComponents.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.components +package dev.plexus.shared.features.chat.components import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource @@ -36,38 +36,38 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.unit.dp -import dev.egograph.shared.core.domain.model.LLMModel -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.components.VoiceInputToggleButton -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.domain.model.LLMModel +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.components.VoiceInputToggleButton +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens internal object ChatComposerMetrics { val outerHorizontalPadding - @Composable get() = EgoGraphThemeTokens.dimens.space12 + @Composable get() = PlexusThemeTokens.dimens.space12 val outerVerticalPadding - @Composable get() = EgoGraphThemeTokens.dimens.space12 + @Composable get() = PlexusThemeTokens.dimens.space12 val actionButtonsSpacing - @Composable get() = EgoGraphThemeTokens.dimens.space8 + @Composable get() = PlexusThemeTokens.dimens.space8 val containerMinHeight - @Composable get() = EgoGraphThemeTokens.dimens.chatComposerMinHeight + @Composable get() = PlexusThemeTokens.dimens.chatComposerMinHeight const val INPUT_MIN_LINES = 1 const val INPUT_MAX_LINES = 3 val contentHorizontalPadding - @Composable get() = EgoGraphThemeTokens.dimens.space16 + @Composable get() = PlexusThemeTokens.dimens.space16 val contentTopPadding - @Composable get() = EgoGraphThemeTokens.dimens.space12 + @Composable get() = PlexusThemeTokens.dimens.space12 val contentBottomPadding - @Composable get() = EgoGraphThemeTokens.dimens.space8 + @Composable get() = PlexusThemeTokens.dimens.space8 val modelSelectorSpacing - @Composable get() = EgoGraphThemeTokens.dimens.space8 + @Composable get() = PlexusThemeTokens.dimens.space8 } @OptIn(ExperimentalMaterial3Api::class) @@ -94,8 +94,8 @@ internal fun ChatComposerField( modifier: Modifier = Modifier, ) { val interactionSource = remember { MutableInteractionSource() } - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes val colors = OutlinedTextFieldDefaults.colors() val shape: Shape = shapes.radiusXl diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatMessage.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatMessage.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatMessage.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatMessage.kt index c4981d4..59efedb 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatMessage.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatMessage.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.components +package dev.plexus.shared.features.chat.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -34,13 +34,13 @@ import com.mikepenz.markdown.m3.Markdown import com.mikepenz.markdown.m3.markdownColor import com.mikepenz.markdown.m3.markdownTypography import com.mikepenz.markdown.model.markdownDimens -import dev.egograph.shared.core.domain.model.MessageRole -import dev.egograph.shared.core.domain.model.ThreadMessage -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.components.AssistantContentBlock -import dev.egograph.shared.core.ui.components.MermaidDiagram -import dev.egograph.shared.core.ui.components.splitAssistantContent -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.domain.model.MessageRole +import dev.plexus.shared.core.domain.model.ThreadMessage +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.components.AssistantContentBlock +import dev.plexus.shared.core.ui.components.MermaidDiagram +import dev.plexus.shared.core.ui.components.splitAssistantContent +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens import org.intellij.markdown.ast.ASTNode import org.intellij.markdown.ast.getTextInNode import org.intellij.markdown.flavours.gfm.GFMElementTypes.HEADER @@ -78,7 +78,7 @@ private fun UserMessage( message: ThreadMessage, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Row( modifier = @@ -110,7 +110,7 @@ private fun AssistantMessage( isStreaming: Boolean = false, activeAssistantTask: String? = null, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens val contentBlocks = remember(message.content) { splitAssistantContent(message.content) } val textColor = MaterialTheme.colorScheme.onSurfaceVariant val assistantBodyStyle = @@ -321,7 +321,7 @@ private fun MessageBubble( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { - val shapes = EgoGraphThemeTokens.shapes + val shapes = PlexusThemeTokens.shapes Surface( shape = shapes.radiusMd, diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatModelSelector.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatModelSelector.kt similarity index 82% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatModelSelector.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatModelSelector.kt index 37f7306..e8454fc 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ChatModelSelector.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatModelSelector.kt @@ -1,8 +1,8 @@ -package dev.egograph.shared.features.chat.components +package dev.plexus.shared.features.chat.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.egograph.shared.core.domain.model.LLMModel +import dev.plexus.shared.core.domain.model.LLMModel @Composable fun ChatModelSelector( diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ErrorBanner.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ErrorBanner.kt similarity index 69% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ErrorBanner.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ErrorBanner.kt index cb3049e..a6c2d96 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ErrorBanner.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ErrorBanner.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.components +package dev.plexus.shared.features.chat.components import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -17,8 +17,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens -import dev.egograph.shared.features.chat.ChatErrorState +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens +import dev.plexus.shared.features.chat.ChatErrorState /** * エラーバナー コンポーネント @@ -37,21 +37,21 @@ fun ErrorBanner( onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens val backgroundColor = when (errorState.severity) { - dev.egograph.shared.core.domain.repository.ErrorSeverity.INFO -> MaterialTheme.colorScheme.primaryContainer - dev.egograph.shared.core.domain.repository.ErrorSeverity.WARNING -> MaterialTheme.colorScheme.tertiaryContainer - dev.egograph.shared.core.domain.repository.ErrorSeverity.ERROR -> MaterialTheme.colorScheme.errorContainer - dev.egograph.shared.core.domain.repository.ErrorSeverity.CRITICAL -> MaterialTheme.colorScheme.error + dev.plexus.shared.core.domain.repository.ErrorSeverity.INFO -> MaterialTheme.colorScheme.primaryContainer + dev.plexus.shared.core.domain.repository.ErrorSeverity.WARNING -> MaterialTheme.colorScheme.tertiaryContainer + dev.plexus.shared.core.domain.repository.ErrorSeverity.ERROR -> MaterialTheme.colorScheme.errorContainer + dev.plexus.shared.core.domain.repository.ErrorSeverity.CRITICAL -> MaterialTheme.colorScheme.error } val contentColor = when (errorState.severity) { - dev.egograph.shared.core.domain.repository.ErrorSeverity.CRITICAL -> MaterialTheme.colorScheme.onError - dev.egograph.shared.core.domain.repository.ErrorSeverity.ERROR -> MaterialTheme.colorScheme.onErrorContainer - dev.egograph.shared.core.domain.repository.ErrorSeverity.WARNING -> MaterialTheme.colorScheme.onTertiaryContainer - dev.egograph.shared.core.domain.repository.ErrorSeverity.INFO -> MaterialTheme.colorScheme.onPrimaryContainer + dev.plexus.shared.core.domain.repository.ErrorSeverity.CRITICAL -> MaterialTheme.colorScheme.onError + dev.plexus.shared.core.domain.repository.ErrorSeverity.ERROR -> MaterialTheme.colorScheme.onErrorContainer + dev.plexus.shared.core.domain.repository.ErrorSeverity.WARNING -> MaterialTheme.colorScheme.onTertiaryContainer + dev.plexus.shared.core.domain.repository.ErrorSeverity.INFO -> MaterialTheme.colorScheme.onPrimaryContainer } Surface( diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/MessageList.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/MessageList.kt similarity index 93% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/MessageList.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/MessageList.kt index 80c0d61..71a59cb 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/MessageList.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/MessageList.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.components +package dev.plexus.shared.features.chat.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -19,10 +19,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import dev.egograph.shared.core.domain.model.ThreadMessage -import dev.egograph.shared.core.ui.common.ListStateContent -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.domain.model.ThreadMessage +import dev.plexus.shared.core.ui.common.ListStateContent +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter @@ -47,7 +47,7 @@ fun MessageList( streamingMessageId: String? = null, activeAssistantTask: String? = null, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens val listState = rememberLazyListState() val reversedMessages = remember(messages) { messages.asReversed() } val keyboardController = LocalSoftwareKeyboardController.current @@ -134,7 +134,7 @@ fun MessageList( */ @Composable fun MessageListEmpty(modifier: Modifier = Modifier) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Box( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ModelSelector.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ModelSelector.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ModelSelector.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ModelSelector.kt index 923e625..3fe376c 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/components/ModelSelector.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ModelSelector.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.components +package dev.plexus.shared.features.chat.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -22,9 +22,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow -import dev.egograph.shared.core.domain.model.LLMModel -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.domain.model.LLMModel +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * モデル選択コンポーネント @@ -47,8 +47,8 @@ fun ModelSelector( onModelSelected: (String) -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes var expanded by remember { mutableStateOf(false) } val selectedModel = models.find { it.id == selectedModelId } diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/reducer/ChatStreamReduceResult.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/reducer/ChatStreamReduceResult.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/reducer/ChatStreamReduceResult.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/reducer/ChatStreamReduceResult.kt index fb47060..eb5d5d5 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/reducer/ChatStreamReduceResult.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/reducer/ChatStreamReduceResult.kt @@ -1,8 +1,8 @@ -package dev.egograph.shared.features.chat.reducer +package dev.plexus.shared.features.chat.reducer -import dev.egograph.shared.core.domain.model.StreamChunk -import dev.egograph.shared.core.domain.model.StreamChunkType -import dev.egograph.shared.features.chat.MessageListState +import dev.plexus.shared.core.domain.model.StreamChunk +import dev.plexus.shared.core.domain.model.StreamChunkType +import dev.plexus.shared.features.chat.MessageListState data class ChatStreamReduceResult( val state: MessageListState, diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadItem.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadItem.kt similarity index 86% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadItem.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadItem.kt index 9545fcf..05504a1 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadItem.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadItem.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.threads +package dev.plexus.shared.features.chat.threads import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -12,10 +12,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextOverflow -import dev.egograph.shared.core.domain.model.Thread -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.common.toCompactIsoDateTime -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.domain.model.Thread +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.common.toCompactIsoDateTime +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * スレッドリストアイテムコンポーネント @@ -32,8 +32,8 @@ fun ThreadItem( onClick: () -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes val backgroundColor = if (isActive) { diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadList.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadList.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadList.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadList.kt index 6336136..e5591bc 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadList.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadList.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.threads +package dev.plexus.shared.features.chat.threads import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -25,12 +25,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import dev.egograph.shared.core.domain.model.Thread -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.components.EmptyView -import dev.egograph.shared.core.ui.components.ErrorView -import dev.egograph.shared.core.ui.components.LoadingView -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.domain.model.Thread +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.components.EmptyView +import dev.plexus.shared.core.ui.components.ErrorView +import dev.plexus.shared.core.ui.components.LoadingView +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * スレッド一覧コンポーネント @@ -107,7 +107,7 @@ private fun ThreadListContent( listState: LazyListState, onThreadClick: (String) -> Unit, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens if (error != null && threads.isEmpty()) { Box( @@ -159,7 +159,7 @@ private fun ThreadListContent( @Composable private fun LoadingMoreIndicator() { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Box( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListEmpty.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListEmpty.kt similarity index 83% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListEmpty.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListEmpty.kt index dd693a3..408a5d9 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListEmpty.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListEmpty.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.threads +package dev.plexus.shared.features.chat.threads import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -8,11 +8,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @Composable fun ThreadListEmpty(modifier: Modifier = Modifier) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Box( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListError.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListError.kt similarity index 83% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListError.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListError.kt index 18294ee..118014f 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListError.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListError.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.threads +package dev.plexus.shared.features.chat.threads import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -8,14 +8,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @Composable fun ThreadListError( message: String, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Box( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListLoading.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListLoading.kt similarity index 84% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListLoading.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListLoading.kt index 5d07467..d235476 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListLoading.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListLoading.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.threads +package dev.plexus.shared.features.chat.threads import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -8,14 +8,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @Composable fun ThreadListLoading( modifier: Modifier = Modifier, message: String = "Loading...", ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Box( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListScreen.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListScreen.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListScreen.kt index f465641..fd01788 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadListScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListScreen.kt @@ -1,12 +1,12 @@ -package dev.egograph.shared.features.chat.threads +package dev.plexus.shared.features.chat.threads import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.koinScreenModel -import dev.egograph.shared.features.chat.ChatScreenModel -import dev.egograph.shared.features.chat.ChatState +import dev.plexus.shared.features.chat.ChatScreenModel +import dev.plexus.shared.features.chat.ChatState /** * スレッド一覧画面 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadTitleFormatter.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadTitleFormatter.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadTitleFormatter.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadTitleFormatter.kt index ae87d1a..6f805a7 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/chat/threads/ThreadTitleFormatter.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadTitleFormatter.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat.threads +package dev.plexus.shared.features.chat.threads /** * スレッドタイトルをフォーマットする diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainNavigationHost.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainNavigationHost.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainNavigationHost.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainNavigationHost.kt index 726f5b7..b371f05 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainNavigationHost.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainNavigationHost.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.navigation +package dev.plexus.shared.features.navigation import androidx.compose.runtime.Composable diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainView.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt similarity index 77% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainView.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt index 8777ad1..d4373a4 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainView.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.navigation +package dev.plexus.shared.features.navigation /** * メインのViewを表す列挙型 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainViewTransition.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainViewTransition.kt similarity index 97% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainViewTransition.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainViewTransition.kt index 5a7d235..e89c110 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/MainViewTransition.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainViewTransition.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.navigation +package dev.plexus.shared.features.navigation import androidx.compose.animation.AnimatedContent import androidx.compose.animation.EnterTransition diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/SwipeNavigationContainer.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/SwipeNavigationContainer.kt similarity index 98% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/SwipeNavigationContainer.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/SwipeNavigationContainer.kt index 461013e..e3b77fc 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/navigation/SwipeNavigationContainer.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/SwipeNavigationContainer.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.navigation +package dev.plexus.shared.features.navigation import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Box diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsEffect.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsEffect.kt similarity index 88% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsEffect.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsEffect.kt index 02c6280..f97bc69 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsEffect.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsEffect.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.settings +package dev.plexus.shared.features.settings /** * アプリケーション設定画面のOne-shotイベント diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreen.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsScreen.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreen.kt index f1e43fb..e8a85c8 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreen.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.settings +package dev.plexus.shared.features.settings import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -27,12 +27,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.koinScreenModel -import dev.egograph.shared.core.platform.isValidUrl -import dev.egograph.shared.core.settings.AppTheme -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.components.SecretTextField -import dev.egograph.shared.core.ui.components.SettingsTopBar -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.platform.isValidUrl +import dev.plexus.shared.core.settings.AppTheme +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.components.SecretTextField +import dev.plexus.shared.core.ui.components.SettingsTopBar +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens import kotlinx.coroutines.launch /** @@ -50,7 +50,7 @@ class SettingsScreen( val screenModel = koinScreenModel() val state by screenModel.state.collectAsState() val snackbarHostState = remember { SnackbarHostState() } - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens LaunchedEffect(Unit) { screenModel.effect.collect { effect -> @@ -111,7 +111,7 @@ private fun AppearanceSection( selectedTheme: AppTheme, onThemeSelected: (AppTheme) -> Unit, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Text( text = "Appearance", @@ -137,7 +137,7 @@ private fun ApiConfigurationSection( inputKey: String, onKeyChange: (String) -> Unit, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Text( text = "API Configuration", @@ -149,7 +149,7 @@ private fun ApiConfigurationSection( value = inputUrl, onValueChange = onUrlChange, label = { Text("API URL") }, - placeholder = { Text("https://api.egograph.dev") }, + placeholder = { Text("https://api.example.com") }, modifier = Modifier .testTagResourceId("api_url_input") @@ -158,7 +158,7 @@ private fun ApiConfigurationSection( isError = inputUrl.isNotBlank() && !isValidUrl(inputUrl), supportingText = { Text( - text = "Production: https://api.egograph.dev | Tailscale: http://100.x.x.x:8000", + text = "Production: your hosted API | Tailscale: http://100.x.x.x:8000", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -205,7 +205,7 @@ private fun ThemeOption( selected: Boolean, onClick: () -> Unit, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Row( verticalAlignment = Alignment.CenterVertically, diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsScreenModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreenModel.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsScreenModel.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreenModel.kt index 95022da..96dfb48 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsScreenModel.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreenModel.kt @@ -1,14 +1,14 @@ -package dev.egograph.shared.features.settings +package dev.plexus.shared.features.settings import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsDefaults -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.core.platform.isValidUrl -import dev.egograph.shared.core.platform.normalizeBaseUrl -import dev.egograph.shared.core.settings.AppTheme -import dev.egograph.shared.core.settings.ThemeRepository +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsDefaults +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.platform.isValidUrl +import dev.plexus.shared.core.platform.normalizeBaseUrl +import dev.plexus.shared.core.settings.AppTheme +import dev.plexus.shared.core.settings.ThemeRepository import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt similarity index 82% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsState.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt index fcd902f..a86aeae 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/settings/SettingsState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.features.settings +package dev.plexus.shared.features.settings -import dev.egograph.shared.core.settings.AppTheme +import dev.plexus.shared.core.settings.AppTheme /** * アプリケーション設定画面のUI状態 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarFooter.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarFooter.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt index 7be17e7..3f83086 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarFooter.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.sidebar +package dev.plexus.shared.features.sidebar import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement @@ -23,8 +23,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @Composable fun SidebarFooter( @@ -33,7 +33,7 @@ fun SidebarFooter( onTerminalClick: () -> Unit, onSystemPromptClick: () -> Unit, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Row( modifier = @@ -84,8 +84,8 @@ private fun FooterIconButton( testTag: String, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes Surface( onClick = onClick, @@ -121,8 +121,8 @@ private fun FooterIconWithLabelButton( testTag: String, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes Surface( onClick = onClick, diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarHeader.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarHeader.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarHeader.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarHeader.kt index afe13eb..7316234 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarHeader.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarHeader.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.sidebar +package dev.plexus.shared.features.sidebar import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -14,8 +14,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.common.CompactActionButton -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.common.CompactActionButton +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * サイドバーのヘッダーコンポーネント @@ -32,7 +32,7 @@ fun SidebarHeader( onSettingsClick: () -> Unit = {}, onTerminalClick: () -> Unit = {}, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Row( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt similarity index 90% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarScreen.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt index da69ceb..c18c2b4 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/sidebar/SidebarScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.sidebar +package dev.plexus.shared.features.sidebar import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -28,21 +28,21 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.koinScreenModel import cafe.adriel.voyager.navigator.LocalNavigator -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsDefaults -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens -import dev.egograph.shared.features.chat.ChatScreen -import dev.egograph.shared.features.chat.ChatScreenModel -import dev.egograph.shared.features.chat.ChatState -import dev.egograph.shared.features.chat.threads.ThreadList -import dev.egograph.shared.features.navigation.MainNavigationHost -import dev.egograph.shared.features.navigation.MainView -import dev.egograph.shared.features.settings.SettingsScreen -import dev.egograph.shared.features.systemprompt.SystemPromptEditorScreen -import dev.egograph.shared.features.terminal.agentlist.AgentListScreen -import dev.egograph.shared.features.terminal.session.TerminalScreen -import dev.egograph.shared.features.terminal.settings.GatewaySettingsScreen +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsDefaults +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens +import dev.plexus.shared.features.chat.ChatScreen +import dev.plexus.shared.features.chat.ChatScreenModel +import dev.plexus.shared.features.chat.ChatState +import dev.plexus.shared.features.chat.threads.ThreadList +import dev.plexus.shared.features.navigation.MainNavigationHost +import dev.plexus.shared.features.navigation.MainView +import dev.plexus.shared.features.settings.SettingsScreen +import dev.plexus.shared.features.systemprompt.SystemPromptEditorScreen +import dev.plexus.shared.features.terminal.agentlist.AgentListScreen +import dev.plexus.shared.features.terminal.session.TerminalScreen +import dev.plexus.shared.features.terminal.settings.GatewaySettingsScreen import kotlinx.coroutines.launch import org.koin.compose.koinInject @@ -54,7 +54,7 @@ import org.koin.compose.koinInject class SidebarScreen : Screen { @Composable override fun Content() { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens val navigator = requireNotNull(LocalNavigator.current) val screenModel = koinScreenModel() val state: ChatState by screenModel.state.collectAsState() diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditor.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditor.kt similarity index 90% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditor.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditor.kt index 4e8b8b1..feb2a83 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditor.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditor.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.systemprompt +package dev.plexus.shared.features.systemprompt import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth @@ -6,7 +6,7 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.egograph.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.common.testTagResourceId /** * システムプロンプトエディタコンポーネント diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorEffect.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorEffect.kt similarity index 85% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorEffect.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorEffect.kt index f29151e..84ad933 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorEffect.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorEffect.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.systemprompt +package dev.plexus.shared.features.systemprompt /** * システムプロンプト編集画面のOne-shotイベント diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorScreen.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorScreen.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorScreen.kt index d37bd92..5b600f5 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorScreen.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.systemprompt +package dev.plexus.shared.features.systemprompt import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -22,8 +22,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.koinScreenModel -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens /** * システムプロンプトエディタ画面 @@ -37,7 +37,7 @@ class SystemPromptEditorScreen( ) : Screen { @Composable override fun Content() { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens val screenModel = koinScreenModel() val state by screenModel.state.collectAsState() val snackbarHostState = remember { SnackbarHostState() } diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorScreenModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorScreenModel.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorScreenModel.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorScreenModel.kt index 1e8d29d..8a1fd06 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorScreenModel.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorScreenModel.kt @@ -1,9 +1,9 @@ -package dev.egograph.shared.features.systemprompt +package dev.plexus.shared.features.systemprompt import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope -import dev.egograph.shared.core.domain.model.SystemPromptName -import dev.egograph.shared.core.domain.repository.SystemPromptRepository +import dev.plexus.shared.core.domain.model.SystemPromptName +import dev.plexus.shared.core.domain.repository.SystemPromptRepository import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorState.kt similarity index 85% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorState.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorState.kt index ef37519..d1b231a 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorState.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.features.systemprompt +package dev.plexus.shared.features.systemprompt -import dev.egograph.shared.core.domain.model.SystemPromptName +import dev.plexus.shared.core.domain.model.SystemPromptName /** * システムプロンプト編集画面のUI状態 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptTabs.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptTabs.kt similarity index 86% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptTabs.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptTabs.kt index 77d0488..5487b11 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptTabs.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptTabs.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.systemprompt +package dev.plexus.shared.features.systemprompt import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Tab @@ -6,8 +6,8 @@ import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.egograph.shared.core.domain.model.SystemPromptName -import dev.egograph.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.domain.model.SystemPromptName +import dev.plexus.shared.core.ui.common.testTagResourceId /** * システムプロンプト選択タブ diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/TerminalTestTags.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/TerminalTestTags.kt similarity index 96% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/TerminalTestTags.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/TerminalTestTags.kt index 5c7010a..70e1887 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/TerminalTestTags.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/TerminalTestTags.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal +package dev.plexus.shared.features.terminal /** * Terminal機能用のテストタグ定数 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListEffect.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListEffect.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListEffect.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListEffect.kt index 61c927f..c09eba8 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListEffect.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListEffect.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.agentlist +package dev.plexus.shared.features.terminal.agentlist /** * ターミナル画面のOne-shotイベント (Effect) diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListScreen.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListScreen.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListScreen.kt index e2a5a6b..cfa8600 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListScreen.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.agentlist +package dev.plexus.shared.features.terminal.agentlist import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -9,7 +9,7 @@ import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.koin.koinScreenModel -import dev.egograph.shared.features.terminal.agentlist.components.SessionList +import dev.plexus.shared.features.terminal.agentlist.components.SessionList import kotlinx.serialization.Transient /** diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListScreenModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListScreenModel.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListScreenModel.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListScreenModel.kt index a3f82e5..75ec017 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListScreenModel.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListScreenModel.kt @@ -1,10 +1,10 @@ -package dev.egograph.shared.features.terminal.agentlist +package dev.plexus.shared.features.terminal.agentlist import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope -import dev.egograph.shared.core.domain.repository.TerminalRepository -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.domain.repository.TerminalRepository +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsKeys import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListState.kt similarity index 76% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListState.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListState.kt index 8145aed..571b996 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListState.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.features.terminal.agentlist +package dev.plexus.shared.features.terminal.agentlist -import dev.egograph.shared.core.domain.model.terminal.Session +import dev.plexus.shared.core.domain.model.terminal.Session /** * ターミナル画面の状態 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionList.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionList.kt similarity index 93% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionList.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionList.kt index f9f1973..f7e45f4 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionList.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionList.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.agentlist.components +package dev.plexus.shared.features.terminal.agentlist.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -30,10 +30,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow -import dev.egograph.shared.core.domain.model.terminal.Session -import dev.egograph.shared.core.ui.common.ListStateContent -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens -import dev.egograph.shared.core.ui.theme.monospaceLabelSmall +import dev.plexus.shared.core.domain.model.terminal.Session +import dev.plexus.shared.core.ui.common.ListStateContent +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens +import dev.plexus.shared.core.ui.theme.monospaceLabelSmall /** * ターミナルセッション一覧コンポーネント @@ -56,9 +56,9 @@ fun SessionList( onOpenGatewaySettings: () -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes - val extendedColors = EgoGraphThemeTokens.extendedColors + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes + val extendedColors = PlexusThemeTokens.extendedColors val sessionCount = sessions.size Column( @@ -183,7 +183,7 @@ private fun SessionListContent( onSessionClick: (String) -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens val listState = rememberLazyListState() LazyColumn( modifier = modifier.padding(vertical = dimens.space8), diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListEmpty.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListEmpty.kt similarity index 91% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListEmpty.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListEmpty.kt index 2404d21..2a01eab 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListEmpty.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListEmpty.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.agentlist.components +package dev.plexus.shared.features.terminal.agentlist.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -16,11 +16,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @Composable fun SessionListEmpty(modifier: Modifier = Modifier) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Column( modifier = modifier.fillMaxSize().padding(dimens.space16), diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListError.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListError.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListError.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListError.kt index 98bdb55..50478a0 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListError.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListError.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.agentlist.components +package dev.plexus.shared.features.terminal.agentlist.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -17,7 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @Composable fun SessionListError( @@ -25,7 +25,7 @@ fun SessionListError( onRefresh: () -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Column( modifier = modifier.fillMaxSize().padding(dimens.space16), diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListItem.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListItem.kt similarity index 94% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListItem.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListItem.kt index 79d33d6..78fb934 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListItem.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListItem.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.agentlist.components +package dev.plexus.shared.features.terminal.agentlist.components import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat @@ -34,13 +34,13 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.sp -import dev.egograph.shared.core.domain.model.terminal.Session -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.common.toCompactIsoDateTime -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens -import dev.egograph.shared.core.ui.theme.monospaceBody -import dev.egograph.shared.core.ui.theme.monospaceLabelSmall -import dev.egograph.shared.features.terminal.TerminalTestTags +import dev.plexus.shared.core.domain.model.terminal.Session +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.common.toCompactIsoDateTime +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens +import dev.plexus.shared.core.ui.theme.monospaceBody +import dev.plexus.shared.core.ui.theme.monospaceLabelSmall +import dev.plexus.shared.features.terminal.TerminalTestTags internal fun previewDisplayLines(session: Session): List = if (session.previewLines.isNotEmpty()) { @@ -68,9 +68,9 @@ fun SessionListItem( onClick: () -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes - val extendedColors = EgoGraphThemeTokens.extendedColors + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes + val extendedColors = PlexusThemeTokens.extendedColors val previewLines = previewDisplayLines(session) val subtitle = sessionSubtitle(session) val previewScrollState = rememberScrollState() diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListLoading.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListLoading.kt similarity index 88% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListLoading.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListLoading.kt index a6d3e1e..cba4923 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListLoading.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListLoading.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.agentlist.components +package dev.plexus.shared.features.terminal.agentlist.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -14,11 +14,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @Composable fun SessionListLoading(modifier: Modifier = Modifier) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Column( modifier = modifier.fillMaxSize().padding(dimens.space16), diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalCopyModeSheet.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalCopyModeSheet.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalCopyModeSheet.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalCopyModeSheet.kt index 225da15..9be0d13 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalCopyModeSheet.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalCopyModeSheet.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session +package dev.plexus.shared.features.terminal.session import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll @@ -27,12 +27,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Modifier -import dev.egograph.shared.core.domain.model.terminal.TerminalSnapshot -import dev.egograph.shared.core.domain.repository.TerminalRepository -import dev.egograph.shared.core.ui.components.ErrorView -import dev.egograph.shared.core.ui.components.LoadingView -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens -import dev.egograph.shared.core.ui.theme.monospaceBody +import dev.plexus.shared.core.domain.model.terminal.TerminalSnapshot +import dev.plexus.shared.core.domain.repository.TerminalRepository +import dev.plexus.shared.core.ui.components.ErrorView +import dev.plexus.shared.core.ui.components.LoadingView +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens +import dev.plexus.shared.core.ui.theme.monospaceBody import org.koin.compose.koinInject @OptIn(ExperimentalMaterial3Api::class) @@ -42,7 +42,7 @@ fun TerminalCopyModeSheet( onDismiss: () -> Unit, ) { val repository = koinInject() - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) var reloadToken by remember(agentId) { mutableStateOf(0) } var activeRequestId by remember(agentId) { mutableStateOf(0) } diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalReconnectBackoff.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalReconnectBackoff.kt similarity index 98% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalReconnectBackoff.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalReconnectBackoff.kt index b99e7ab..75524be 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalReconnectBackoff.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalReconnectBackoff.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session +package dev.plexus.shared.features.terminal.session import kotlin.random.Random diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalScreen.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalScreen.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalScreen.kt index f204c52..7f66674 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalScreen.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session +package dev.plexus.shared.features.terminal.session import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme @@ -30,17 +30,17 @@ import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.navigator.LocalNavigator -import dev.egograph.shared.core.domain.repository.TerminalRepository -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.core.platform.rememberKeyboardState -import dev.egograph.shared.core.settings.AppTheme -import dev.egograph.shared.core.settings.ThemeRepository -import dev.egograph.shared.features.terminal.session.components.DraggableTerminalFloatingControlPill -import dev.egograph.shared.features.terminal.session.components.SpecialKeysBar -import dev.egograph.shared.features.terminal.session.components.TerminalFloatingControlPosition -import dev.egograph.shared.features.terminal.session.components.TerminalView -import dev.egograph.shared.features.terminal.session.components.rememberTerminalWebView +import dev.plexus.shared.core.domain.repository.TerminalRepository +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.platform.rememberKeyboardState +import dev.plexus.shared.core.settings.AppTheme +import dev.plexus.shared.core.settings.ThemeRepository +import dev.plexus.shared.features.terminal.session.components.DraggableTerminalFloatingControlPill +import dev.plexus.shared.features.terminal.session.components.SpecialKeysBar +import dev.plexus.shared.features.terminal.session.components.TerminalFloatingControlPosition +import dev.plexus.shared.features.terminal.session.components.TerminalView +import dev.plexus.shared.features.terminal.session.components.rememberTerminalWebView import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalSettings.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalSettings.kt similarity index 87% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalSettings.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalSettings.kt index 60cfb42..997de17 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalSettings.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalSettings.kt @@ -1,12 +1,12 @@ -package dev.egograph.shared.features.terminal.session +package dev.plexus.shared.features.terminal.session import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsDefaults -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.core.platform.getDefaultGatewayBaseUrl -import dev.egograph.shared.core.platform.normalizeBaseUrl +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsDefaults +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.platform.getDefaultGatewayBaseUrl +import dev.plexus.shared.core.platform.normalizeBaseUrl import io.ktor.http.encodeURLParameter /** diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalVoiceInputCoordinator.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalVoiceInputCoordinator.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalVoiceInputCoordinator.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalVoiceInputCoordinator.kt index 5a622a9..ed7848a 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/TerminalVoiceInputCoordinator.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalVoiceInputCoordinator.kt @@ -1,7 +1,7 @@ -package dev.egograph.shared.features.terminal.session +package dev.plexus.shared.features.terminal.session import androidx.compose.runtime.Composable -import dev.egograph.shared.core.ui.components.rememberVoiceInputCoordinator +import dev.plexus.shared.core.ui.components.rememberVoiceInputCoordinator /** * ターミナル画面向け音声入力コーディネーター。 diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/DraggableTerminalFloatingControlPill.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/DraggableTerminalFloatingControlPill.kt similarity index 99% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/DraggableTerminalFloatingControlPill.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/DraggableTerminalFloatingControlPill.kt index ba6b599..31ff8d1 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/DraggableTerminalFloatingControlPill.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/DraggableTerminalFloatingControlPill.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session.components +package dev.plexus.shared.features.terminal.session.components import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.BoxWithConstraints diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/SpecialKeysBar.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/SpecialKeysBar.kt similarity index 95% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/SpecialKeysBar.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/SpecialKeysBar.kt index cb71414..2b3e033 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/SpecialKeysBar.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/SpecialKeysBar.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session.components +package dev.plexus.shared.features.terminal.session.components import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement @@ -25,8 +25,8 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens private const val SPECIAL_KEY_COLUMNS = 6 @@ -62,8 +62,8 @@ fun SpecialKeysBar( isVoiceInputActive: Boolean, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes val actions = listOf( TerminalPanelAction( @@ -134,7 +134,7 @@ private fun SpecialPanelButton( onKeyPress: (String) -> Unit, modifier: Modifier = Modifier, ) { - val shapes = EgoGraphThemeTokens.shapes + val shapes = PlexusThemeTokens.shapes val containerColor = if (action.isAccent) { MaterialTheme.colorScheme.errorContainer diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalControls.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalControls.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalControls.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalControls.kt index 191b6c9..354b3e3 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalControls.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalControls.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session.components +package dev.plexus.shared.features.terminal.session.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -24,9 +24,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import dev.egograph.shared.core.ui.common.testTagResourceId -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens -import dev.egograph.shared.features.terminal.TerminalTestTags +import dev.plexus.shared.core.ui.common.testTagResourceId +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens +import dev.plexus.shared.features.terminal.TerminalTestTags @Composable fun TerminalBackButton( @@ -76,9 +76,9 @@ fun TerminalFloatingControlPill( onCopy: () -> Unit, modifier: Modifier = Modifier, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes - val indicatorColor = if (isConnected) EgoGraphThemeTokens.extendedColors.success else MaterialTheme.colorScheme.error + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes + val indicatorColor = if (isConnected) PlexusThemeTokens.extendedColors.success else MaterialTheme.colorScheme.error Surface( modifier = modifier.testTagResourceId(TerminalTestTags.TERMINAL_STATUS_PILL), diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalHeader.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalHeader.kt similarity index 92% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalHeader.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalHeader.kt index aca78a3..284a305 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalHeader.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalHeader.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session.components +package dev.plexus.shared.features.terminal.session.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -23,9 +23,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens -import dev.egograph.shared.core.ui.theme.monospaceBody -import dev.egograph.shared.core.ui.theme.monospaceBodyMedium +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens +import dev.plexus.shared.core.ui.theme.monospaceBody +import dev.plexus.shared.core.ui.theme.monospaceBodyMedium /** * ターミナル画面のヘッダー @@ -47,9 +47,9 @@ fun TerminalHeader( onBack: () -> Unit, onOpenCopyMode: () -> Unit, ) { - val dimens = EgoGraphThemeTokens.dimens - val shapes = EgoGraphThemeTokens.shapes - val extendedColors = EgoGraphThemeTokens.extendedColors + val dimens = PlexusThemeTokens.dimens + val shapes = PlexusThemeTokens.shapes + val extendedColors = PlexusThemeTokens.extendedColors CenterAlignedTopAppBar( title = { diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalView.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalView.kt similarity index 77% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalView.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalView.kt index 13f5521..1247ecd 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/session/components/TerminalView.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/components/TerminalView.kt @@ -1,8 +1,8 @@ -package dev.egograph.shared.features.terminal.session.components +package dev.plexus.shared.features.terminal.session.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.egograph.shared.core.platform.terminal.TerminalWebView +import dev.plexus.shared.core.platform.terminal.TerminalWebView /** * ターミナルを表示するView diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsEffect.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsEffect.kt similarity index 86% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsEffect.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsEffect.kt index 5a8b3ce..d5e8236 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsEffect.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsEffect.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.settings +package dev.plexus.shared.features.terminal.settings /** * Gateway設定画面のOne-shotイベント diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt similarity index 93% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsScreen.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt index 50bd68a..1b1dab1 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.settings +package dev.plexus.shared.features.terminal.settings import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -22,10 +22,10 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.koinScreenModel -import dev.egograph.shared.core.platform.isValidUrl -import dev.egograph.shared.core.ui.components.SecretTextField -import dev.egograph.shared.core.ui.components.SettingsTopBar -import dev.egograph.shared.core.ui.theme.EgoGraphThemeTokens +import dev.plexus.shared.core.platform.isValidUrl +import dev.plexus.shared.core.ui.components.SecretTextField +import dev.plexus.shared.core.ui.components.SettingsTopBar +import dev.plexus.shared.core.ui.theme.PlexusThemeTokens import kotlinx.coroutines.launch /** @@ -87,7 +87,7 @@ private fun GatewaySettingsContent( onSave: () -> Unit, isSaving: Boolean, ) { - val dimens = EgoGraphThemeTokens.dimens + val dimens = PlexusThemeTokens.dimens Column( modifier = diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsScreenModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreenModel.kt similarity index 90% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsScreenModel.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreenModel.kt index 2fc0b63..0c5ab21 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsScreenModel.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreenModel.kt @@ -1,13 +1,13 @@ -package dev.egograph.shared.features.terminal.settings +package dev.plexus.shared.features.terminal.settings import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope -import dev.egograph.shared.core.platform.PlatformPreferences -import dev.egograph.shared.core.platform.PlatformPrefsDefaults -import dev.egograph.shared.core.platform.PlatformPrefsKeys -import dev.egograph.shared.core.platform.getDefaultGatewayBaseUrl -import dev.egograph.shared.core.platform.isValidUrl -import dev.egograph.shared.core.platform.normalizeBaseUrl +import dev.plexus.shared.core.platform.PlatformPreferences +import dev.plexus.shared.core.platform.PlatformPrefsDefaults +import dev.plexus.shared.core.platform.PlatformPrefsKeys +import dev.plexus.shared.core.platform.getDefaultGatewayBaseUrl +import dev.plexus.shared.core.platform.isValidUrl +import dev.plexus.shared.core.platform.normalizeBaseUrl import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow diff --git a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsState.kt similarity index 89% rename from frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsState.kt rename to frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsState.kt index 46cb96c..9bc4ac6 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsState.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.settings +package dev.plexus.shared.features.terminal.settings /** * Gateway設定画面のUI状態 diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/model/DtoSerializationTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/model/DtoSerializationTest.kt similarity index 98% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/model/DtoSerializationTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/model/DtoSerializationTest.kt index 681c708..b7edfe7 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/model/DtoSerializationTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/model/DtoSerializationTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model +package dev.plexus.shared.core.domain.model import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/model/terminal/SessionTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/model/terminal/SessionTest.kt similarity index 99% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/model/terminal/SessionTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/model/terminal/SessionTest.kt index be0b8c0..978056e 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/model/terminal/SessionTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/model/terminal/SessionTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.model.terminal +package dev.plexus.shared.core.domain.model.terminal import kotlinx.serialization.SerializationException import kotlinx.serialization.encodeToString diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/repository/ApiErrorTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/repository/ApiErrorTest.kt similarity index 99% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/repository/ApiErrorTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/repository/ApiErrorTest.kt index 7a1be3f..c5dce69 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/repository/ApiErrorTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/repository/ApiErrorTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.domain.repository +package dev.plexus.shared.core.domain.repository import kotlin.test.Test import kotlin.test.assertEquals diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/repository/RepositoryTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/repository/RepositoryTest.kt similarity index 93% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/repository/RepositoryTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/repository/RepositoryTest.kt index 2eee509..deaf221 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/domain/repository/RepositoryTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/domain/repository/RepositoryTest.kt @@ -1,14 +1,14 @@ -package dev.egograph.shared.core.domain.repository - -import dev.egograph.shared.core.domain.model.ChatRequest -import dev.egograph.shared.core.domain.model.ChatResponse -import dev.egograph.shared.core.domain.model.Message -import dev.egograph.shared.core.domain.model.MessageRole -import dev.egograph.shared.core.domain.model.ModelsResponse -import dev.egograph.shared.core.domain.model.StreamChunk -import dev.egograph.shared.core.domain.model.Thread -import dev.egograph.shared.core.domain.model.ThreadListResponse -import dev.egograph.shared.core.domain.model.ThreadMessagesResponse +package dev.plexus.shared.core.domain.repository + +import dev.plexus.shared.core.domain.model.ChatRequest +import dev.plexus.shared.core.domain.model.ChatResponse +import dev.plexus.shared.core.domain.model.Message +import dev.plexus.shared.core.domain.model.MessageRole +import dev.plexus.shared.core.domain.model.ModelsResponse +import dev.plexus.shared.core.domain.model.StreamChunk +import dev.plexus.shared.core.domain.model.Thread +import dev.plexus.shared.core.domain.model.ThreadListResponse +import dev.plexus.shared.core.domain.model.ThreadMessagesResponse import kotlinx.coroutines.flow.flow import kotlin.test.Test import kotlin.test.assertEquals diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/network/KtorConfigTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/network/KtorConfigTest.kt similarity index 97% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/network/KtorConfigTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/network/KtorConfigTest.kt index 2abe00e..a0e6635 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/network/KtorConfigTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/network/KtorConfigTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.network +package dev.plexus.shared.core.network import kotlin.test.Test import kotlin.test.assertNotNull diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/BaseUrlProviderTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/BaseUrlProviderTest.kt similarity index 94% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/BaseUrlProviderTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/BaseUrlProviderTest.kt index 7557c79..b5825d8 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/BaseUrlProviderTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/BaseUrlProviderTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform import kotlin.test.Test import kotlin.test.assertEquals @@ -41,8 +41,8 @@ class BaseUrlProviderTest { @Test fun `normalizeBaseUrl - HTTPSスキームを保持する`() { // Arrange - val input = "https://api.egograph.dev/" - val expected = "https://api.egograph.dev" + val input = "https://api.plexus.dev/" + val expected = "https://api.plexus.dev" // Act val result = normalizeBaseUrl(input) diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/KeyboardStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/KeyboardStateTest.kt similarity index 97% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/KeyboardStateTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/KeyboardStateTest.kt index e12b3c3..2909305 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/KeyboardStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/KeyboardStateTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform +package dev.plexus.shared.core.platform import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtilTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtilTest.kt similarity index 99% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtilTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtilTest.kt index 9144e3d..7f763ff 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/platform/terminal/PermissionUtilTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/platform/terminal/PermissionUtilTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.platform.terminal +package dev.plexus.shared.core.platform.terminal import kotlinx.coroutines.test.runTest import kotlin.test.Test diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/settings/AppThemeTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/AppThemeTest.kt similarity index 98% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/settings/AppThemeTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/AppThemeTest.kt index 4569df6..b1298df 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/settings/AppThemeTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/AppThemeTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.settings +package dev.plexus.shared.core.settings import kotlin.test.Test import kotlin.test.assertEquals diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/settings/ThemeRepositoryTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/ThemeRepositoryTest.kt similarity index 89% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/settings/ThemeRepositoryTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/ThemeRepositoryTest.kt index 0f4b86e..aed0810 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/settings/ThemeRepositoryTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/ThemeRepositoryTest.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.core.settings +package dev.plexus.shared.core.settings -import dev.egograph.shared.core.platform.PlatformPrefsDefaults +import dev.plexus.shared.core.platform.PlatformPrefsDefaults import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/ui/common/DateTimeTextTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/ui/common/DateTimeTextTest.kt similarity index 97% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/ui/common/DateTimeTextTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/ui/common/DateTimeTextTest.kt index e954770..cf5dde6 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/ui/common/DateTimeTextTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/ui/common/DateTimeTextTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.common +package dev.plexus.shared.core.ui.common import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagramTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagramTest.kt similarity index 94% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagramTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagramTest.kt index 7c7c3d1..2664640 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/core/ui/components/MermaidDiagramTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/ui/components/MermaidDiagramTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.core.ui.components +package dev.plexus.shared.core.ui.components import kotlin.test.Test import kotlin.test.assertEquals diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/di/KoinDiTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/di/KoinDiTest.kt similarity index 90% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/di/KoinDiTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/di/KoinDiTest.kt index 34efa65..3db423c 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/di/KoinDiTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/di/KoinDiTest.kt @@ -1,11 +1,11 @@ -package dev.egograph.shared.di - -import dev.egograph.shared.core.data.repository.MessageRepositoryImpl -import dev.egograph.shared.core.data.repository.ThreadRepositoryImpl -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.repository.ChatRepository -import dev.egograph.shared.core.domain.repository.MessageRepository -import dev.egograph.shared.core.domain.repository.ThreadRepository +package dev.plexus.shared.di + +import dev.plexus.shared.core.data.repository.MessageRepositoryImpl +import dev.plexus.shared.core.data.repository.ThreadRepositoryImpl +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.repository.ChatRepository +import dev.plexus.shared.core.domain.repository.MessageRepository +import dev.plexus.shared.core.domain.repository.ThreadRepository import io.ktor.client.HttpClient import org.koin.core.context.startKoin import org.koin.core.context.stopKoin diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatErrorStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatErrorStateTest.kt similarity index 83% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatErrorStateTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatErrorStateTest.kt index 0602ac1..490c147 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatErrorStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatErrorStateTest.kt @@ -1,9 +1,9 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat -import dev.egograph.shared.core.domain.repository.ApiError -import dev.egograph.shared.core.domain.repository.ErrorAction -import dev.egograph.shared.core.domain.repository.ErrorSeverity -import dev.egograph.shared.core.domain.repository.TimeoutType +import dev.plexus.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.domain.repository.ErrorAction +import dev.plexus.shared.core.domain.repository.ErrorSeverity +import dev.plexus.shared.core.domain.repository.TimeoutType import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -27,7 +27,7 @@ class ChatErrorStateTest { val errorState = networkError.toChatErrorState() - assertEquals(dev.egograph.shared.features.chat.ErrorType.NETWORK, errorState.type) + assertEquals(dev.plexus.shared.features.chat.ErrorType.NETWORK, errorState.type) assertEquals("ネットワークエラーが発生しました", errorState.message) assertEquals("Connection failed", errorState.detail) assertEquals(ErrorAction.RETRY, errorState.action) @@ -48,7 +48,7 @@ class ChatErrorStateTest { val errorState = timeoutError.toChatErrorState() - assertEquals(dev.egograph.shared.features.chat.ErrorType.TIMEOUT, errorState.type) + assertEquals(dev.plexus.shared.features.chat.ErrorType.TIMEOUT, errorState.type) assertEquals("応答がタイムアウトしました", errorState.message) assertEquals(ErrorAction.RETRY, errorState.action) assertTrue(errorState.canRetry) @@ -68,7 +68,7 @@ class ChatErrorStateTest { val errorState = authError.toChatErrorState() - assertEquals(dev.egograph.shared.features.chat.ErrorType.AUTHENTICATION, errorState.type) + assertEquals(dev.plexus.shared.features.chat.ErrorType.AUTHENTICATION, errorState.type) assertEquals("認証に失敗しました", errorState.message) assertEquals("APIキーが無効です", errorState.detail) assertEquals(ErrorAction.REAUTHENTICATE, errorState.action) @@ -88,7 +88,7 @@ class ChatErrorStateTest { val errorState = httpError.toChatErrorState() - assertEquals(dev.egograph.shared.features.chat.ErrorType.AUTHENTICATION, errorState.type) + assertEquals(dev.plexus.shared.features.chat.ErrorType.AUTHENTICATION, errorState.type) assertEquals("APIキーが無効です", errorState.message) assertEquals(ErrorAction.REAUTHENTICATE, errorState.action) assertFalse(errorState.canRetry) @@ -106,7 +106,7 @@ class ChatErrorStateTest { val errorState = httpError.toChatErrorState() - assertEquals(dev.egograph.shared.features.chat.ErrorType.SERVER, errorState.type) + assertEquals(dev.plexus.shared.features.chat.ErrorType.SERVER, errorState.type) assertEquals("サーバーエラーが発生しました", errorState.message) assertEquals(ErrorAction.RETRY, errorState.action) assertTrue(errorState.canRetry) @@ -124,7 +124,7 @@ class ChatErrorStateTest { val errorState = httpError.toChatErrorState() - assertEquals(dev.egograph.shared.features.chat.ErrorType.UNKNOWN, errorState.type) + assertEquals(dev.plexus.shared.features.chat.ErrorType.UNKNOWN, errorState.type) assertTrue(errorState.message.startsWith("HTTP 404")) assertEquals(ErrorAction.DISMISS, errorState.action) assertFalse(errorState.canRetry) @@ -140,7 +140,7 @@ class ChatErrorStateTest { val errorState = unknownError.toChatErrorState() - assertEquals(dev.egograph.shared.features.chat.ErrorType.UNKNOWN, errorState.type) + assertEquals(dev.plexus.shared.features.chat.ErrorType.UNKNOWN, errorState.type) assertTrue(errorState.message.contains("Unknown error")) assertEquals(ErrorAction.DISMISS, errorState.action) assertFalse(errorState.canRetry) @@ -155,7 +155,7 @@ class ChatErrorStateTest { val errorState = serializationError.toChatErrorState() - assertEquals(dev.egograph.shared.features.chat.ErrorType.UNKNOWN, errorState.type) + assertEquals(dev.plexus.shared.features.chat.ErrorType.UNKNOWN, errorState.type) assertEquals("データの解析に失敗しました", errorState.message) assertEquals(ErrorAction.DISMISS, errorState.action) assertFalse(errorState.canRetry) diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatRepositoryImplTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatRepositoryImplTest.kt similarity index 96% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatRepositoryImplTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatRepositoryImplTest.kt index d837b12..75cd4c3 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatRepositoryImplTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatRepositoryImplTest.kt @@ -1,17 +1,17 @@ -package dev.egograph.shared.features.chat - -import dev.egograph.shared.core.data.repository.ChatRepositoryImpl -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.ChatRequest -import dev.egograph.shared.core.domain.model.ChatResponse -import dev.egograph.shared.core.domain.model.LLMModel -import dev.egograph.shared.core.domain.model.Message -import dev.egograph.shared.core.domain.model.MessageRole -import dev.egograph.shared.core.domain.model.ModelsResponse -import dev.egograph.shared.core.domain.model.StreamChunk -import dev.egograph.shared.core.domain.model.StreamChunkType -import dev.egograph.shared.core.domain.repository.ApiError -import dev.egograph.shared.core.network.HttpClientConfig +package dev.plexus.shared.features.chat + +import dev.plexus.shared.core.data.repository.ChatRepositoryImpl +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.ChatRequest +import dev.plexus.shared.core.domain.model.ChatResponse +import dev.plexus.shared.core.domain.model.LLMModel +import dev.plexus.shared.core.domain.model.Message +import dev.plexus.shared.core.domain.model.MessageRole +import dev.plexus.shared.core.domain.model.ModelsResponse +import dev.plexus.shared.core.domain.model.StreamChunk +import dev.plexus.shared.core.domain.model.StreamChunkType +import dev.plexus.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.network.HttpClientConfig import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatStateTest.kt similarity index 98% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatStateTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatStateTest.kt index 3c8a27d..509f20a 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatStateTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat import kotlin.test.Test import kotlin.test.assertEquals diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatUtilsTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatUtilsTest.kt similarity index 98% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatUtilsTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatUtilsTest.kt index 094d758..0656ad2 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ChatUtilsTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatUtilsTest.kt @@ -1,6 +1,6 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat -import dev.egograph.shared.features.chat.threads.toThreadTitle +import dev.plexus.shared.features.chat.threads.toThreadTitle import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/MessageRepositoryImplTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/MessageRepositoryImplTest.kt similarity index 96% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/MessageRepositoryImplTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/MessageRepositoryImplTest.kt index d3b3e6a..85ccfbb 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/MessageRepositoryImplTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/MessageRepositoryImplTest.kt @@ -1,11 +1,11 @@ -package dev.egograph.shared.features.chat - -import dev.egograph.shared.core.data.repository.MessageRepositoryImpl -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.MessageRole -import dev.egograph.shared.core.domain.model.ThreadMessage -import dev.egograph.shared.core.domain.model.ThreadMessagesResponse -import dev.egograph.shared.core.domain.repository.ApiError +package dev.plexus.shared.features.chat + +import dev.plexus.shared.core.data.repository.MessageRepositoryImpl +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.MessageRole +import dev.plexus.shared.core.domain.model.ThreadMessage +import dev.plexus.shared.core.domain.model.ThreadMessagesResponse +import dev.plexus.shared.core.domain.repository.ApiError import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ThreadRepositoryImplTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ThreadRepositoryImplTest.kt similarity index 97% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ThreadRepositoryImplTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ThreadRepositoryImplTest.kt index a41c84f..1749296 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/chat/ThreadRepositoryImplTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ThreadRepositoryImplTest.kt @@ -1,10 +1,10 @@ -package dev.egograph.shared.features.chat +package dev.plexus.shared.features.chat -import dev.egograph.shared.core.data.repository.ThreadRepositoryImpl -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.Thread -import dev.egograph.shared.core.domain.model.ThreadListResponse -import dev.egograph.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.data.repository.ThreadRepositoryImpl +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.Thread +import dev.plexus.shared.core.domain.model.ThreadListResponse +import dev.plexus.shared.core.domain.repository.ApiError import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/settings/SettingsStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt similarity index 77% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/settings/SettingsStateTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt index 88e67fd..effad3b 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/settings/SettingsStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.settings +package dev.plexus.shared.features.settings import kotlin.test.Test import kotlin.test.assertEquals @@ -14,7 +14,7 @@ class SettingsStateTest { fun `SettingsState starts with SYSTEM theme`() { val state = SettingsState() - assertEquals(dev.egograph.shared.core.settings.AppTheme.SYSTEM, state.selectedTheme) + assertEquals(dev.plexus.shared.core.settings.AppTheme.SYSTEM, state.selectedTheme) } @Test @@ -36,13 +36,13 @@ class SettingsStateTest { fun `SettingsState with custom values preserves values`() { val state = SettingsState( - selectedTheme = dev.egograph.shared.core.settings.AppTheme.DARK, + selectedTheme = dev.plexus.shared.core.settings.AppTheme.DARK, inputUrl = "https://api.example.com", inputKey = "test-key", isSaving = true, ) - assertEquals(dev.egograph.shared.core.settings.AppTheme.DARK, state.selectedTheme) + assertEquals(dev.plexus.shared.core.settings.AppTheme.DARK, state.selectedTheme) assertEquals("https://api.example.com", state.inputUrl) assertEquals("test-key", state.inputKey) assertEquals(true, state.isSaving) diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorStateTest.kt similarity index 87% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorStateTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorStateTest.kt index 22a64c4..55acd8f 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptEditorStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptEditorStateTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.systemprompt +package dev.plexus.shared.features.systemprompt import kotlin.test.Test import kotlin.test.assertEquals @@ -15,7 +15,7 @@ class SystemPromptEditorStateTest { fun `SystemPromptEditorState starts with USER tab`() { val state = SystemPromptEditorState() - assertEquals(dev.egograph.shared.core.domain.model.SystemPromptName.USER, state.selectedTab) + assertEquals(dev.plexus.shared.core.domain.model.SystemPromptName.USER, state.selectedTab) } @Test @@ -126,23 +126,23 @@ class SystemPromptEditorStateTest { fun `SystemPromptEditorState with custom tab preserves tab`() { val state = SystemPromptEditorState( - selectedTab = dev.egograph.shared.core.domain.model.SystemPromptName.IDENTITY, + selectedTab = dev.plexus.shared.core.domain.model.SystemPromptName.IDENTITY, ) - assertEquals(dev.egograph.shared.core.domain.model.SystemPromptName.IDENTITY, state.selectedTab) + assertEquals(dev.plexus.shared.core.domain.model.SystemPromptName.IDENTITY, state.selectedTab) } @Test fun `SystemPromptEditorState with custom values preserves values`() { val state = SystemPromptEditorState( - selectedTab = dev.egograph.shared.core.domain.model.SystemPromptName.IDENTITY, + selectedTab = dev.plexus.shared.core.domain.model.SystemPromptName.IDENTITY, originalContent = "Original identity prompt", draftContent = "Modified identity prompt", isLoading = true, ) - assertEquals(dev.egograph.shared.core.domain.model.SystemPromptName.IDENTITY, state.selectedTab) + assertEquals(dev.plexus.shared.core.domain.model.SystemPromptName.IDENTITY, state.selectedTab) assertEquals("Original identity prompt", state.originalContent) assertEquals("Modified identity prompt", state.draftContent) assertEquals(true, state.isLoading) diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptRepositoryImplTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptRepositoryImplTest.kt similarity index 97% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptRepositoryImplTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptRepositoryImplTest.kt index d301037..d082204 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/systemprompt/SystemPromptRepositoryImplTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/systemprompt/SystemPromptRepositoryImplTest.kt @@ -1,10 +1,10 @@ -package dev.egograph.shared.features.systemprompt +package dev.plexus.shared.features.systemprompt -import dev.egograph.shared.core.data.repository.SystemPromptRepositoryImpl -import dev.egograph.shared.core.data.repository.internal.RepositoryClient -import dev.egograph.shared.core.domain.model.SystemPromptName -import dev.egograph.shared.core.domain.model.SystemPromptResponse -import dev.egograph.shared.core.domain.repository.ApiError +import dev.plexus.shared.core.data.repository.SystemPromptRepositoryImpl +import dev.plexus.shared.core.data.repository.internal.RepositoryClient +import dev.plexus.shared.core.domain.model.SystemPromptName +import dev.plexus.shared.core.domain.model.SystemPromptResponse +import dev.plexus.shared.core.domain.repository.ApiError import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListStateTest.kt similarity index 90% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListStateTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListStateTest.kt index 58fc2ee..1d04d08 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/agentlist/AgentListStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/agentlist/AgentListStateTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.agentlist +package dev.plexus.shared.features.terminal.agentlist import kotlin.test.Test import kotlin.test.assertEquals diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListItemStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListItemStateTest.kt similarity index 91% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListItemStateTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListItemStateTest.kt index 972922e..1bbb368 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/agentlist/components/SessionListItemStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/agentlist/components/SessionListItemStateTest.kt @@ -1,7 +1,7 @@ -package dev.egograph.shared.features.terminal.agentlist.components +package dev.plexus.shared.features.terminal.agentlist.components -import dev.egograph.shared.core.domain.model.terminal.Session -import dev.egograph.shared.core.domain.model.terminal.SessionStatus +import dev.plexus.shared.core.domain.model.terminal.Session +import dev.plexus.shared.core.domain.model.terminal.SessionStatus import kotlin.test.Test import kotlin.test.assertEquals diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/session/TerminalReconnectBackoffTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/session/TerminalReconnectBackoffTest.kt similarity index 99% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/session/TerminalReconnectBackoffTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/session/TerminalReconnectBackoffTest.kt index e223d39..bad0e90 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/session/TerminalReconnectBackoffTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/session/TerminalReconnectBackoffTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session +package dev.plexus.shared.features.terminal.session import kotlin.random.Random import kotlin.test.Test diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/session/components/DraggableTerminalFloatingControlPillTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/session/components/DraggableTerminalFloatingControlPillTest.kt similarity index 99% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/session/components/DraggableTerminalFloatingControlPillTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/session/components/DraggableTerminalFloatingControlPillTest.kt index 569e3bc..1e79290 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/session/components/DraggableTerminalFloatingControlPillTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/session/components/DraggableTerminalFloatingControlPillTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.session.components +package dev.plexus.shared.features.terminal.session.components import kotlin.test.Test import kotlin.test.assertEquals diff --git a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsStateTest.kt similarity index 98% rename from frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsStateTest.kt rename to frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsStateTest.kt index 38840c5..5960ab0 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/egograph/shared/features/terminal/settings/GatewaySettingsStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsStateTest.kt @@ -1,4 +1,4 @@ -package dev.egograph.shared.features.terminal.settings +package dev.plexus.shared.features.terminal.settings import kotlin.test.Test import kotlin.test.assertEquals From 02523e75e03058e715c59f4505a73087f3fb3728 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:15:10 +0000 Subject: [PATCH 05/14] chore: clarify internal debug apk delivery --- .github/workflows/ci-frontend.yml | 6 ++--- .github/workflows/release-frontend-kmp.yml | 18 +++++++++------ docs/40.deploy/frontend-android.md | 26 +++++++++------------- docs/70.knowledge/webhook-guide.md | 10 ++++----- frontend/README.md | 16 +++++++------ 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci-frontend.yml b/.github/workflows/ci-frontend.yml index 4cb67be..52f2a94 100644 --- a/.github/workflows/ci-frontend.yml +++ b/.github/workflows/ci-frontend.yml @@ -1,4 +1,4 @@ -name: Test Frontend +name: Test Frontend Terminal Client on: workflow_dispatch: @@ -105,9 +105,9 @@ jobs: run: ./gradlew build working-directory: frontend - - name: Upload APK artifact + - name: Upload debug APK artifact uses: actions/upload-artifact@v4 with: - name: app-debug + name: plexus-debug-apk path: frontend/androidApp/build/outputs/apk/debug/*.apk if-no-files-found: warn diff --git a/.github/workflows/release-frontend-kmp.yml b/.github/workflows/release-frontend-kmp.yml index 363ae8b..fef87df 100644 --- a/.github/workflows/release-frontend-kmp.yml +++ b/.github/workflows/release-frontend-kmp.yml @@ -1,4 +1,4 @@ -name: Release Frontend (KMP Android APK) +name: Publish Internal Android Debug APK on: workflow_dispatch: @@ -12,7 +12,7 @@ permissions: contents: write jobs: - build-and-release: + build-and-publish-debug-apk: runs-on: ubuntu-latest steps: @@ -56,7 +56,7 @@ jobs: run: | echo "${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}" | base64 -d > frontend/androidApp/google-services.json - - name: Build Debug APK + - name: Build internal debug APK run: ./gradlew :androidApp:assembleDebug working-directory: frontend env: @@ -67,14 +67,18 @@ jobs: - name: Rename APK with version run: | mv frontend/androidApp/build/outputs/apk/debug/androidApp-debug.apk \ - frontend/androidApp/build/outputs/apk/debug/egograph-${{ env.VERSION_NAME }}.apk + frontend/androidApp/build/outputs/apk/debug/plexus-debug-${{ env.VERSION_NAME }}.apk - - name: Create GitHub Release + - name: Publish pre-release with debug APK uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.version.outputs.version }} - name: KMP Android APK (${{ steps.version.outputs.version }}) - files: frontend/androidApp/build/outputs/apk/debug/egograph-*.apk + name: Plexus Internal Debug APK (${{ steps.version.outputs.version }}) + body: | + Internal debug APK for device testing. + This artifact is not intended for production distribution. + files: frontend/androidApp/build/outputs/apk/debug/plexus-debug-*.apk generate_release_notes: true + prerelease: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/40.deploy/frontend-android.md b/docs/40.deploy/frontend-android.md index 3e9cc94..521088c 100644 --- a/docs/40.deploy/frontend-android.md +++ b/docs/40.deploy/frontend-android.md @@ -1,7 +1,7 @@ # Frontend Deploy (Android) -本番フロントエンドを Android アプリ(KMP)としてビルド・デプロイする手順。 -Kotlin Multiplatform + Compose Multiplatform を使用し、Android ネイティブアプリとして配布する。 +Plexus の Android アプリをビルドし、内部配布用 debug APK を作成する手順。 +Kotlin Multiplatform + Compose Multiplatform を使用し、Android ネイティブアプリとして実機確認に使う。 ## 1. 前提条件 @@ -35,16 +35,16 @@ cd frontend # 成果物: androidApp/build/outputs/apk/debug/androidApp-debug.apk ``` -### 2.2 リリースビルド +### 2.2 内部配布向け署名付き debug ビルド -署名付きリリースビルドを作成するには、キーストアが必要です。 +内部配布で使う debug APK に独自キーストアを使う場合の手順。 #### A. キーストア作成(初回のみ) ```bash keytool -genkey -v \ - -keystore release.keystore \ - -alias egograph \ + -keystore debug.keystore \ + -alias plexus \ -keyalg RSA -keysize 2048 -validity 10000 ``` @@ -56,8 +56,8 @@ keytool -genkey -v \ export KEYSTORE_PASSWORD="your-password" export KEY_PASSWORD="your-password" -./gradlew :androidApp:assembleRelease -# 成果物: androidApp/build/outputs/apk/release/androidApp-release.apk +./gradlew :androidApp:assembleDebug +# 成果物: androidApp/build/outputs/apk/debug/androidApp-debug.apk ``` ## 3. インストール @@ -70,12 +70,6 @@ export KEY_PASSWORD="your-password" ## 4. CI/CD -`ci-frontend.yml` ワークフローにより、GitHub Actions 上で自動テストとビルドが行われます。 - -## 5. 旧手順(Capacitor) - -React + Capacitor 時代のデプロイ手順は、分離済み legacy repo を前提に以下を参照してください: +`ci-frontend.yml` で自動テストと debug ビルドを行い、internal debug APK publish workflow で実機確認用 APK を配布できます。 -- [Legacy deploy doc](https://github.com/endo-ava/egograph-frontend-capacitor-legacy/blob/main/docs/40.deploy/frontend-android-capacitor.md) -- [Legacy Capacitor architecture doc](https://github.com/endo-ava/egograph-frontend-capacitor-legacy/blob/main/docs/40.deploy/capacitor.md) -- External repo: +内部配布用 artifact は production release ではありません。用途を明示したうえで GitHub pre-release へ配置してください。 diff --git a/docs/70.knowledge/webhook-guide.md b/docs/70.knowledge/webhook-guide.md index 021f1c9..f754ab9 100644 --- a/docs/70.knowledge/webhook-guide.md +++ b/docs/70.knowledge/webhook-guide.md @@ -371,7 +371,7 @@ openssl rand -base64 32 ## 前提条件 -1. **Gateway**: 起動済み(tmuxセッション: `egograph-gateway`) +1. **Gateway**: 起動済み(tmuxセッション: `plexus-gateway`) 2. **Androidアプリ**: EgoGraphアプリをインストール済み 3. **FCM設定**: `gateway/.env` に `FCM_PROJECT_ID` が設定済み 4. **Webhook Secret**: `gateway/.env` に `GATEWAY_WEBHOOK_SECRET` が設定済み @@ -548,7 +548,7 @@ curl -X POST http://localhost:8001/v1/push/webhook \ 1. **Gatewayのログを確認** ```bash - tmux capture-pane -p -t egograph-gateway | grep -E "webhook|FCM" + tmux capture-pane -p -t plexus-gateway | grep -E "webhook|FCM" ``` 2. **デバイスが登録されているか確認** @@ -566,7 +566,7 @@ curl -X POST http://localhost:8001/v1/push/webhook \ 3. **FCM初期化を確認** ```bash - tmux capture-pane -p -t egograph-gateway | grep "Firebase" + tmux capture-pane -p -t plexus-gateway | grep "Firebase" ``` 正常に初期化されている場合: @@ -597,8 +597,8 @@ curl -X POST http://localhost:8001/v1/push/webhook \ **解決策:** Gatewayを再起動してください ```bash -tmux kill-session -t egograph-gateway -tmux new-session -d -s egograph-gateway 'cd /root/workspace/ego-graph/wt1 && uv run python -m gateway.main' +tmux kill-session -t plexus-gateway +tmux new-session -d -s plexus-gateway 'cd /root/workspace/plexus && uv run python -m gateway.main' ``` --- diff --git a/frontend/README.md b/frontend/README.md index ca96bc2..87391dc 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -71,24 +71,26 @@ frontend/ - **MockK** - **Ktor MockEngine** -## リリース署名 +## 内部配布用 Debug APK -本番リリースには独自の署名キーが必要です。 +実機確認や内部テスト用には debug APK をビルドします。 +GitHub Actions の internal debug APK workflow も同じ `assembleDebug` を使います。 -### 1. リリースキーストアの作成 +### 1. デバッグキーストアの用意 ```bash keytool -genkey -v \ - -keystore release.keystore \ + -keystore debug.keystore \ -alias plexus \ -keyalg RSA -keysize 2048 -validity 10000 ``` -### 2. 署名付きリリースビルド +### 2. 署名付き debug APK ビルド ```bash export KEYSTORE_PASSWORD="your-password" -export KEY_PASSWORD="your-password" -./gradlew :androidApp:assembleRelease +./gradlew :androidApp:assembleDebug ``` + +この APK は内部確認用です。本番配布物としては扱いません。 From af3a0c70cf021cdd35e346d58d5dbef87da55359 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:17:01 +0000 Subject: [PATCH 06/14] chore: rename internal apk publish workflow --- .../{release-frontend-kmp.yml => publish-internal-debug-apk.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{release-frontend-kmp.yml => publish-internal-debug-apk.yml} (100%) diff --git a/.github/workflows/release-frontend-kmp.yml b/.github/workflows/publish-internal-debug-apk.yml similarity index 100% rename from .github/workflows/release-frontend-kmp.yml rename to .github/workflows/publish-internal-debug-apk.yml From 373bd9910b927e5c2fa05415ef6cb815cfae17ff Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:38:06 +0000 Subject: [PATCH 07/14] chore: ignore local runtime artifacts --- .gitignore | 18 + .../plexus/shared/features/chat/ChatEffect.kt | 42 -- .../shared/features/chat/ChatErrorState.kt | 137 ----- .../plexus/shared/features/chat/ChatScreen.kt | 182 ------ .../shared/features/chat/ChatScreenModel.kt | 528 ------------------ .../plexus/shared/features/chat/ChatState.kt | 78 --- .../features/chat/components/ChatComposer.kt | 87 --- .../chat/components/ChatComposerComponents.kt | 258 --------- .../features/chat/components/ChatMessage.kt | 340 ----------- .../chat/components/ChatModelSelector.kt | 24 - .../features/chat/components/ErrorBanner.kt | 94 ---- .../features/chat/components/MessageList.kt | 152 ----- .../features/chat/components/ModelSelector.kt | 151 ----- .../chat/reducer/ChatStreamReduceResult.kt | 75 --- .../features/chat/threads/ThreadItem.kt | 87 --- .../features/chat/threads/ThreadList.kt | 215 ------- .../features/chat/threads/ThreadListEmpty.kt | 30 - .../features/chat/threads/ThreadListError.kt | 33 -- .../chat/threads/ThreadListLoading.kt | 33 -- .../features/chat/threads/ThreadListScreen.kt | 51 -- .../chat/threads/ThreadTitleFormatter.kt | 34 -- .../features/chat/ChatErrorStateTest.kt | 163 ------ .../features/chat/ChatRepositoryImplTest.kt | 524 ----------------- .../shared/features/chat/ChatStateTest.kt | 124 ---- .../shared/features/chat/ChatUtilsTest.kt | 281 ---------- .../chat/MessageRepositoryImplTest.kt | 292 ---------- .../features/chat/ThreadRepositoryImplTest.kt | 417 -------------- 27 files changed, 18 insertions(+), 4432 deletions(-) create mode 100644 .gitignore delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatEffect.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatErrorState.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreen.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreenModel.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatState.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposer.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposerComponents.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatMessage.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatModelSelector.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ErrorBanner.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/MessageList.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ModelSelector.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/reducer/ChatStreamReduceResult.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadItem.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadList.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListEmpty.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListError.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListLoading.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListScreen.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadTitleFormatter.kt delete mode 100644 frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatErrorStateTest.kt delete mode 100644 frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatRepositoryImplTest.kt delete mode 100644 frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatStateTest.kt delete mode 100644 frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatUtilsTest.kt delete mode 100644 frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/MessageRepositoryImplTest.kt delete mode 100644 frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ThreadRepositoryImplTest.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bad7dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Python caches and virtualenvs +__pycache__/ +*.py[cod] +.pytest_cache/ +.venv/ + +# Local runtime artifacts +dist/ +gateway/gateway.db +gateway/uv.lock + +# Local secrets and environment files +gateway/.env +gateway/firebase-service-account.json + +# Editor and OS files +.DS_Store +.idea/ diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatEffect.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatEffect.kt deleted file mode 100644 index cf0d9cf..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatEffect.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.plexus.shared.features.chat - -/** - * チャット画面のOne-shotイベント (Effect) - * - * 画面遷移やSnackbar表示など、状態として保持しない単発のイベントを定義します。 - */ -sealed class ChatEffect { - data class ShowMessage( - val message: String, - ) : ChatEffect() - - /** - * エラーを表示する - * - * @property errorState エラー状態 - * - * Note: リトライはState(chatError)を通じて処理されるため、 - * Effectにはlambdaを含めない - */ - class ShowError( - val errorState: ChatErrorState, - ) : ChatEffect() { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - other as ShowError - return errorState == other.errorState - } - - override fun hashCode(): Int = errorState.hashCode() - } - - /** - * 特定のスレッドに遷移する - * - * @property threadId 遷移先のスレッドID - */ - data class NavigateToThread( - val threadId: String, - ) : ChatEffect() -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatErrorState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatErrorState.kt deleted file mode 100644 index 60df962..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatErrorState.kt +++ /dev/null @@ -1,137 +0,0 @@ -package dev.plexus.shared.features.chat - -import dev.plexus.shared.core.domain.repository.ApiError -import dev.plexus.shared.core.domain.repository.ErrorAction -import dev.plexus.shared.core.domain.repository.ErrorSeverity -import dev.plexus.shared.core.domain.repository.TimeoutType - -/** - * チャット機能のエラー状態 - * - * @property type エラーの種類 - * @property message ユーザーに表示するメッセージ - * @property detail エラー詳細(オプション) - * @property action 推奨されるアクション - * @property severity エラーの重要度 - */ -data class ChatErrorState( - val type: ErrorType, - val message: String, - val detail: String? = null, - val action: ErrorAction, - val severity: ErrorSeverity = ErrorSeverity.ERROR, -) { - /** 再試行可能かどうか(actionから導出) */ - val canRetry: Boolean - get() = action == ErrorAction.RETRY - - /** 再試行可能かどうかのショートカット(canRetryのエイリアス) */ - val isRetryable: Boolean - get() = canRetry -} - -/** - * エラーの種類 - */ -enum class ErrorType { - /** ネットワークエラー */ - NETWORK, - - /** タイムアウト */ - TIMEOUT, - - /** 認証エラー */ - AUTHENTICATION, - - /** サーバーエラー */ - SERVER, - - /** 不明なエラー */ - UNKNOWN, -} - -/** - * [ApiError]から[ChatErrorState]への変換拡張関数 - * - * APIレイヤーのエラーをUIレイヤーで扱いやすい形式に変換します。 - */ -fun ApiError.toChatErrorState(): ChatErrorState = - when (this) { - is ApiError.NetworkError -> - ChatErrorState( - type = ErrorType.NETWORK, - message = "ネットワークエラーが発生しました", - detail = exception.message, - action = suggestedAction, - severity = severity, - ) - - is ApiError.TimeoutError -> - ChatErrorState( - type = ErrorType.TIMEOUT, - message = - when (timeoutType) { - TimeoutType.STREAMING -> "応答がタイムアウトしました" - else -> "リクエストがタイムアウトしました" - }, - detail = "サーバーからの応答に時間がかかりすぎています。通信環境を確認の上、再試行してください。", - action = suggestedAction, - severity = severity, - ) - - is ApiError.AuthenticationError -> - ChatErrorState( - type = ErrorType.AUTHENTICATION, - message = errorMessage, - detail = detail, - action = suggestedAction, - severity = severity, - ) - - is ApiError.HttpError -> - ChatErrorState( - type = - when (code) { - in 500..599 -> ErrorType.SERVER - 401, 403 -> ErrorType.AUTHENTICATION - else -> ErrorType.UNKNOWN - }, - message = - when (code) { - 401 -> "APIキーが無効です" - 403 -> "アクセス権限がありません" - in 500..599 -> "サーバーエラーが発生しました" - else -> "HTTP $code: $errorMessage" - }, - detail = detail, - action = suggestedAction, - severity = severity, - ) - - is ApiError.SerializationError -> - ChatErrorState( - type = ErrorType.UNKNOWN, - message = "データの解析に失敗しました", - detail = exception.message, - action = suggestedAction, - severity = severity, - ) - - is ApiError.UnknownError -> - ChatErrorState( - type = ErrorType.UNKNOWN, - message = message ?: "不明なエラーが発生しました", - detail = exception.message, - action = suggestedAction, - severity = severity, - ) - - is ApiError.ValidationError -> - ChatErrorState( - type = ErrorType.UNKNOWN, - message = errorMessage, - detail = null, - action = suggestedAction, - severity = severity, - ) - } diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreen.kt deleted file mode 100644 index 5edf4ce..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreen.kt +++ /dev/null @@ -1,182 +0,0 @@ -package dev.plexus.shared.features.chat - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Menu -import androidx.compose.material.icons.outlined.Computer -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.koin.koinScreenModel -import co.touchlab.kermit.Logger -import dev.plexus.shared.core.ui.common.CompactActionButton -import dev.plexus.shared.core.ui.common.testTagResourceId -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens -import dev.plexus.shared.features.chat.components.ChatComposer -import dev.plexus.shared.features.chat.components.ErrorBanner -import dev.plexus.shared.features.chat.components.MessageList -import kotlinx.serialization.Transient - -/** - * チャット画面 - * - * メッセージ一覧表示、入力、ストリーミング応答を処理するメイン画面。 - */ -class ChatScreen( - @Transient private val onOpenSidebar: () -> Unit = {}, - @Transient private val onOpenTerminal: () -> Unit = {}, - @Transient private val onNewChat: () -> Unit = {}, -) : Screen { - @Composable - override fun Content() { - val dimens = PlexusThemeTokens.dimens - val screenModel = koinScreenModel() - val state by screenModel.state.collectAsState() - val snackbarHostState = remember { SnackbarHostState() } - - // Effect の収集 - LaunchedEffect(Unit) { - screenModel.effect.collect { effect -> - when (effect) { - is ChatEffect.ShowMessage -> { - snackbarHostState.showSnackbar(effect.message) - } - is ChatEffect.ShowError -> { - // エラー状態は既にStateに設定されているので、 - // ここではログ記録のみ(EffectはOne-shotイベントの通知) - Logger.w { "Error occurred: ${effect.errorState.message}" } - } - is ChatEffect.NavigateToThread -> { - Logger.w { "NavigateToThread effect received but navigation not implemented yet" } - } - } - } - } - - Scaffold( - modifier = Modifier.fillMaxSize(), - contentWindowInsets = WindowInsets(0, 0, 0, 0), - snackbarHost = { SnackbarHost(snackbarHostState) }, - bottomBar = { - ChatComposer( - models = state.composer.models, - selectedModelId = state.composer.selectedModelId, - isLoadingModels = state.composer.isLoadingModels, - modelsError = state.composer.modelsError, - onModelSelected = screenModel::selectModel, - onSendMessage = { text -> - screenModel.sendMessage(text) - }, - isLoading = state.composer.isSending, - modifier = - Modifier - .navigationBarsPadding() - .imePadding(), - ) - }, - ) { paddingValues -> - Column( - modifier = - Modifier - .fillMaxSize() - .padding(paddingValues), - ) { - ChatTopActions( - onOpenSidebar = onOpenSidebar, - onOpenTerminal = onOpenTerminal, - onNewChat = onNewChat, - modifier = - Modifier - .fillMaxWidth() - .statusBarsPadding() - .padding(horizontal = dimens.space8, vertical = dimens.space2), - ) - - // エラーバナー(エラーがある場合のみ表示) - state.chatError?.let { errorState -> - ErrorBanner( - errorState = errorState, - onRetry = { screenModel.retryLastMessage() }, - onDismiss = { screenModel.clearChatError() }, - ) - } - - MessageList( - messages = state.messageList.messages, - modifier = - Modifier - .fillMaxWidth() - .weight(1f), - isLoading = state.messageList.isLoading, - errorMessage = state.messageList.error, - streamingMessageId = state.messageList.streamingMessageId, - activeAssistantTask = state.messageList.activeAssistantTask, - ) - } - } - } -} - -@Composable -private fun ChatTopActions( - onOpenSidebar: () -> Unit, - onOpenTerminal: () -> Unit, - onNewChat: () -> Unit, - modifier: Modifier = Modifier, -) { - val dimens = PlexusThemeTokens.dimens - - Row( - modifier = modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - IconButton( - onClick = onOpenSidebar, - modifier = Modifier.testTagResourceId("chat_sidebar_button"), - ) { - Icon( - imageVector = Icons.Default.Menu, - contentDescription = "Open sidebar", - ) - } - - Spacer(modifier = Modifier.weight(1f)) - - CompactActionButton( - onClick = onOpenTerminal, - icon = Icons.Outlined.Computer, - contentDescription = "Terminal", - testTag = "chat_terminal_button", - ) - Spacer(modifier = Modifier.width(dimens.space8)) - CompactActionButton( - onClick = onNewChat, - icon = Icons.Default.Add, - contentDescription = null, - text = "New", - testTag = "chat_new_chat_button", - ) - Spacer(modifier = Modifier.width(dimens.space8)) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreenModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreenModel.kt deleted file mode 100644 index 4b2c688..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatScreenModel.kt +++ /dev/null @@ -1,528 +0,0 @@ -package dev.plexus.shared.features.chat - -import cafe.adriel.voyager.core.model.ScreenModel -import cafe.adriel.voyager.core.model.screenModelScope -import dev.plexus.shared.core.domain.model.ChatRequest -import dev.plexus.shared.core.domain.model.Message -import dev.plexus.shared.core.domain.model.MessageRole -import dev.plexus.shared.core.domain.model.StreamChunk -import dev.plexus.shared.core.domain.model.ThreadMessage -import dev.plexus.shared.core.domain.repository.ApiError -import dev.plexus.shared.core.domain.repository.ChatRepository -import dev.plexus.shared.core.domain.repository.MessageRepository -import dev.plexus.shared.core.domain.repository.ThreadRepository -import dev.plexus.shared.core.platform.PlatformPreferences -import dev.plexus.shared.core.platform.PlatformPrefsKeys -import dev.plexus.shared.features.chat.reducer.reduceChatStreamChunk -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import java.util.UUID -import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.atomic.AtomicReference - -/** - * チャット画面のViewModel - * - * メッセージ送受信、スレッド管理、モデル選択などのビジネスロジックを担当する。 - */ -class ChatScreenModel( - private val threadRepository: ThreadRepository, - private val messageRepository: MessageRepository, - private val chatRepository: ChatRepository, - private val preferences: PlatformPreferences, -) : ScreenModel { - private val _state = MutableStateFlow(ChatState()) - val state: StateFlow = _state.asStateFlow() - - private val _effect = Channel(Channel.BUFFERED) - val effect: Flow = _effect.receiveAsFlow() - - private val pageLimit = 50 - - /** メッセージID生成用カウンター */ - private val messageIdCounter = AtomicLong(0) - - /** 現在進行中の送信ジョブ(キャンセル用) */ - private var currentSendingJob: kotlinx.coroutines.Job? = null - - /** リトライ用コンテキスト(スレッドセーフ) */ - private data class RetryContext( - val originalRequest: ChatRequest, - val userMessage: ThreadMessage, - val assistantMessage: ThreadMessage, - val selectedThreadId: String, - val streamingMessageId: String, - ) - - /** 保留中のリトライコンテキスト(スレッドセーフ) */ - private val pendingRetryContext = AtomicReference(null) - - private data class LocalSendContext( - val selectedThreadId: String, - val streamingMessageId: String, - val userMessage: ThreadMessage, - val assistantMessage: ThreadMessage, - ) - - init { - loadThreads() - loadModels() - } - - fun loadThreads() { - screenModelScope.launch { - updateThreadList { it.copy(isLoading = true, error = null) } - - threadRepository - .getThreads(limit = pageLimit, offset = 0) - .collect { result -> - result - .onSuccess { response -> - updateThreadList { current -> - // selectedThreadを維持: 新しいthreadsリストから同じIDのスレッドを探す - // ページ外スレッドを選択中の場合は現在のselectedThreadを維持 - val newSelectedThread = - current.selectedThread?.let { selected -> - response.threads.find { it.threadId == selected.threadId } - } ?: current.selectedThread - current.copy( - threads = response.threads, - selectedThread = newSelectedThread, - isLoading = false, - hasMore = response.threads.size < response.total, - ) - } - }.onFailure { error -> - val message = "スレッドの読み込みに失敗: ${error.message}" - updateThreadList { it.copy(error = message, isLoading = false) } - emitMessage(message) - } - } - } - } - - fun loadMoreThreads() { - val currentState = _state.value - if (currentState.threadList.isLoadingMore || !currentState.threadList.hasMore) return - - screenModelScope.launch { - updateThreadList { it.copy(isLoadingMore = true) } - - threadRepository - .getThreads(limit = pageLimit, offset = currentState.threadList.threads.size) - .collect { result -> - result - .onSuccess { response -> - updateThreadList { current -> - val mergedThreads = current.threads + response.threads - // selectedThreadを維持: マージ後のthreadsリストから同じIDのスレッドを探す - val newSelectedThread = - current.selectedThread?.let { selected -> - mergedThreads.find { it.threadId == selected.threadId } - } ?: current.selectedThread - current.copy( - threads = mergedThreads, - selectedThread = newSelectedThread, - isLoadingMore = false, - hasMore = mergedThreads.size < response.total, - ) - } - }.onFailure { error -> - val message = "追加スレッドの読み込みに失敗: ${error.message}" - updateThreadList { it.copy(error = message, isLoadingMore = false) } - emitMessage(message) - } - } - } - } - - fun selectThread(threadId: String) { - updateThreadList { threadList -> - threadList.copy(selectedThread = threadList.threads.find { it.threadId == threadId }) - } - loadMessages(threadId) - } - - fun clearThreadSelection() { - updateThreadList { it.copy(selectedThread = null) } - updateMessageList { it.copy(messages = emptyList()) } - } - - fun loadMessages(threadId: String) { - screenModelScope.launch { - updateMessageList { it.copy(isLoading = true, error = null) } - - messageRepository - .getMessages(threadId) - .collect { result -> - result - .onSuccess { response -> - updateMessageList { - it.copy( - messages = response.messages, - isLoading = false, - ) - } - }.onFailure { error -> - val message = "メッセージの読み込みに失敗: ${error.message}" - updateMessageList { it.copy(error = message, isLoading = false) } - emitMessage(message) - } - } - } - } - - fun loadModels() { - screenModelScope.launch { - updateComposer { it.copy(isLoadingModels = true, modelsError = null) } - - chatRepository - .getModels() - .onSuccess { response -> - updateComposer { - // Preferencesから保存済みモデルIDを読み込む - val savedModelId = preferences.getString(PlatformPrefsKeys.KEY_SELECTED_MODEL, "") - - // 選択するモデルIDを決定(優先順位: 保存済み > APIのデフォルト > 最初のモデル) - val selectedModelId = - when { - savedModelId.isNotBlank() && response.models.any { it.id == savedModelId } -> savedModelId - response.defaultModel.isNotBlank() -> response.defaultModel - else -> response.models.firstOrNull()?.id - } - - it.copy( - models = response.models, - selectedModelId = selectedModelId, - isLoadingModels = false, - ) - } - }.onFailure { error -> - val message = "モデル一覧の取得に失敗: ${error.message}" - updateComposer { it.copy(modelsError = message, isLoadingModels = false) } - emitMessage(message) - } - } - } - - fun selectModel(modelId: String) { - updateComposer { it.copy(selectedModelId = modelId) } - preferences.putString(PlatformPrefsKeys.KEY_SELECTED_MODEL, modelId) - } - - fun sendMessage(content: String) { - if (content.isBlank()) return - - val currentState = _state.value - if (currentState.composer.isSending) return - - updateComposer { it.copy(isSending = true) } - val sendContext = createLocalSendContext(currentState, content) - appendPendingMessages(sendContext) - - // 進行中のジョブがあればキャンセル - currentSendingJob?.cancel() - - val launchingJob = currentSendingJob - currentSendingJob = - screenModelScope.launch { - val request = buildChatRequest(currentState, content) - - // リトライ用にコンテキストを保存(スレッドセーフ) - val retryContext = - RetryContext( - originalRequest = request, - userMessage = sendContext.userMessage, - assistantMessage = sendContext.assistantMessage, - selectedThreadId = sendContext.selectedThreadId, - streamingMessageId = sendContext.streamingMessageId, - ) - pendingRetryContext.set(retryContext) - - var errorCanRetry: Boolean? = null - try { - chatRepository - .sendMessage(request) - .collect { result -> - result - .onSuccess { chunk -> - applyStreamChunk(chunk, sendContext) - }.onFailure { error -> - errorCanRetry = handleSendFailure(error, sendContext) - } - } - } finally { - finalizeSending(currentState) - // ローカルな実行結果に基づいてコンテキストを制御 - when { - // エラーがない場合:成功したのでコンテキストをクリア - errorCanRetry == null -> pendingRetryContext.set(null) - // リトライ可能なエラー:コンテキストを保持 - errorCanRetry == true -> Unit // 何もしない(コンテキストは既にセットされている) - // リトライ不可なエラー:コンテキストをクリア - else -> pendingRetryContext.set(null) - } - if (currentSendingJob === launchingJob) currentSendingJob = null - } - } - } - - fun clearErrors() { - updateThreadList { it.copy(error = null) } - updateMessageList { it.copy(error = null) } - updateComposer { it.copy(modelsError = null) } - } - - /** チャットエラーをクリアする */ - fun clearChatError() { - _state.update { it.copy(chatError = null) } - } - - /** 最後のメッセージ送信をリトライする */ - fun retryLastMessage() { - // コンテキストを取得してクリア(スレッドセーフ) - val retryContext = pendingRetryContext.getAndSet(null) ?: return - - // 進行中のジョブがあればキャンセル - currentSendingJob?.cancel() - - clearChatError() - - updateComposer { it.copy(isSending = true) } - appendPendingMessages( - LocalSendContext( - selectedThreadId = retryContext.selectedThreadId, - streamingMessageId = retryContext.streamingMessageId, - userMessage = retryContext.userMessage, - assistantMessage = retryContext.assistantMessage, - ), - ) - - val launchingJob = currentSendingJob - currentSendingJob = - screenModelScope.launch { - var errorCanRetry: Boolean? = null - try { - val sendContext = - LocalSendContext( - selectedThreadId = retryContext.selectedThreadId, - streamingMessageId = retryContext.streamingMessageId, - userMessage = retryContext.userMessage, - assistantMessage = retryContext.assistantMessage, - ) - chatRepository - .sendMessage(retryContext.originalRequest) - .collect { result -> - result - .onSuccess { chunk -> - applyStreamChunk(chunk, sendContext) - }.onFailure { error -> - errorCanRetry = - handleSendFailure(error, sendContext) - } - } - } finally { - finalizeSending(_state.value) - when { - // エラーがない場合:成功したのでコンテキストをクリア - errorCanRetry == null -> pendingRetryContext.set(null) - // リトライ可能なエラー:コンテキストを再セット(再リトライ可能) - errorCanRetry == true -> pendingRetryContext.set(retryContext) - // リトライ不可なエラー:コンテキストをクリア - else -> pendingRetryContext.set(null) - } - if (currentSendingJob === launchingJob) currentSendingJob = null - } - } - } - - private fun updateThreadList(transform: (ThreadListState) -> ThreadListState) { - _state.update { state -> state.copy(threadList = transform(state.threadList)) } - } - - private fun updateMessageList(transform: (MessageListState) -> MessageListState) { - _state.update { state -> state.copy(messageList = transform(state.messageList)) } - } - - private fun updateComposer(transform: (ComposerState) -> ComposerState) { - _state.update { state -> state.copy(composer = transform(state.composer)) } - } - - private suspend fun emitMessage(message: String) { - _effect.send(ChatEffect.ShowMessage(message)) - } - - private fun createLocalMessageId(prefix: String): String = - "$prefix-${messageIdCounter.incrementAndGet()}-${UUID.randomUUID().toString().take(8)}" - - private fun createLocalSendContext( - currentState: ChatState, - content: String, - ): LocalSendContext { - val selectedThreadId = currentState.threadList.selectedThread?.threadId ?: "local-thread" - val userMessage = - createLocalMessage( - messageId = createLocalMessageId(prefix = "user"), - threadId = selectedThreadId, - role = MessageRole.USER, - content = content, - modelName = null, - ) - val streamingMessageId = createLocalMessageId(prefix = "assistant") - val assistantMessage = - createLocalMessage( - messageId = streamingMessageId, - threadId = selectedThreadId, - role = MessageRole.ASSISTANT, - content = "", - modelName = currentState.composer.selectedModelId, - ) - - return LocalSendContext( - selectedThreadId = selectedThreadId, - streamingMessageId = streamingMessageId, - userMessage = userMessage, - assistantMessage = assistantMessage, - ) - } - - private fun appendPendingMessages(sendContext: LocalSendContext) { - updateMessageList { - it.copy( - messages = it.messages + sendContext.userMessage + sendContext.assistantMessage, - streamingMessageId = sendContext.streamingMessageId, - activeAssistantTask = null, - ) - } - } - - private fun buildChatRequest( - currentState: ChatState, - content: String, - ): ChatRequest = - ChatRequest( - threadId = currentState.threadList.selectedThread?.threadId, - messages = - currentState.messageList.messages.map { - Message(role = it.role, content = it.content) - } + Message(role = MessageRole.USER, content = content), - modelName = currentState.composer.selectedModelId, - ) - - private suspend fun applyStreamChunk( - chunk: StreamChunk, - sendContext: LocalSendContext, - ) { - var uiMessage: String? = null - var newThreadId: String? = null - updateMessageList { currentState -> - val reduced = reduceChatStreamChunk(currentState, chunk, sendContext.streamingMessageId) - uiMessage = reduced.uiMessage - newThreadId = reduced.newThreadId - reduced.state - } - uiMessage?.let { message -> - emitMessage(message) - } - newThreadId?.let { threadId -> - handleNewThreadCreated(threadId, sendContext.selectedThreadId) - } - } - - private suspend fun handleSendFailure( - error: Throwable, - sendContext: LocalSendContext, - ): Boolean { - // エラー状態を変換 - val errorState = - when (error) { - is ApiError -> error.toChatErrorState() - else -> - ChatErrorState( - type = dev.plexus.shared.features.chat.ErrorType.UNKNOWN, - message = "メッセージ送信に失敗: ${error.message}", - action = dev.plexus.shared.core.domain.repository.ErrorAction.DISMISS, - ) - } - - // エラー状態をStateに設定(ErrorBannerが表示される) - _state.update { it.copy(chatError = errorState) } - - // エラー発生をEffectで通知(ログ等の用途) - _effect.send(ChatEffect.ShowError(errorState = errorState)) - - // メッセージリストから一時メッセージを削除 - updateMessageList { currentState -> - val filteredMessages = - currentState.messages.filter { messageItem -> - messageItem.messageId != sendContext.userMessage.messageId && - messageItem.messageId != sendContext.streamingMessageId - } - currentState.copy( - messages = filteredMessages, - streamingMessageId = null, - activeAssistantTask = null, - ) - } - - // リトライ可否を返す - return errorState.canRetry - } - - private suspend fun finalizeSending(currentState: ChatState) { - updateComposer { it.copy(isSending = false) } - currentState.threadList.selectedThread?.threadId?.let { selectedThreadId -> - messageRepository.invalidateCache(selectedThreadId) - } - } - - private fun handleNewThreadCreated( - newThreadId: String, - oldThreadId: String, - ) { - screenModelScope.launch { - messageRepository.invalidateCache(oldThreadId) - threadRepository.getThread(newThreadId).collect { result -> - result.onSuccess { thread -> - updateThreadList { state -> - val existingIndex = state.threads.indexOfFirst { it.threadId == newThreadId } - val updatedThreads = - if (existingIndex >= 0) { - state.threads - } else { - listOf(thread) + state.threads - } - state.copy( - threads = updatedThreads, - selectedThread = thread, - ) - } - loadMessages(newThreadId) - } - } - } - } - - private fun createLocalMessage( - messageId: String, - threadId: String, - role: MessageRole, - content: String, - modelName: String?, - ): ThreadMessage = - ThreadMessage( - messageId = messageId, - threadId = threadId, - userId = "local", - role = role, - content = content, - createdAt = "", - modelName = modelName, - ) -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatState.kt deleted file mode 100644 index 83c4861..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/ChatState.kt +++ /dev/null @@ -1,78 +0,0 @@ -package dev.plexus.shared.features.chat - -import dev.plexus.shared.core.domain.model.LLMModel -import dev.plexus.shared.core.domain.model.Thread -import dev.plexus.shared.core.domain.model.ThreadMessage - -/** - * チャット画面の状態 - * - * @property threads スレッド一覧 - * @property selectedThread 選択中のスレッド - * @property messages 選択中のスレッドのメッセージ一覧 - * @property models 利用可能なモデル一覧 - * @property selectedModel 選択中のモデルID - * @property isLoadingThreads スレッド一覧読み込み中 - * @property isLoadingMoreThreads スレッド追加読み込み中 - * @property hasMoreThreads さらにスレッドが存在するか - * @property isLoadingMessages メッセージ一覧読み込み中 - * @property isLoadingModels モデル一覧読み込み中 - * @property threadsError スレッド関連のエラーメッセージ - * @property messagesError メッセージ関連のエラーメッセージ - * @property modelsError モデル関連のエラーメッセージ - * @property chatError チャット機能全体のエラー状態 - */ -data class ChatState( - val threadList: ThreadListState = ThreadListState(), - val messageList: MessageListState = MessageListState(), - val composer: ComposerState = ComposerState(), - val chatError: ChatErrorState? = null, -) { - /** - * スレッドが選択されているかどうか - */ - val hasSelectedThread: Boolean - get() = threadList.selectedThread != null - - /** - * いずれかの読み込み中フラグが有効かどうか - */ - val isLoading: Boolean - get() = - threadList.isLoading || - threadList.isLoadingMore || - messageList.isLoading || - composer.isLoadingModels || - composer.isSending - - /** - * いずれかのエラーが存在するかどうか - */ - val hasError: Boolean - get() = threadList.error != null || messageList.error != null || composer.modelsError != null || chatError != null -} - -data class ThreadListState( - val threads: List = emptyList(), - val selectedThread: Thread? = null, - val isLoading: Boolean = false, - val isLoadingMore: Boolean = false, - val hasMore: Boolean = false, - val error: String? = null, -) - -data class MessageListState( - val messages: List = emptyList(), - val isLoading: Boolean = false, - val error: String? = null, - val streamingMessageId: String? = null, - val activeAssistantTask: String? = null, -) - -data class ComposerState( - val models: List = emptyList(), - val selectedModelId: String? = null, - val isLoadingModels: Boolean = false, - val modelsError: String? = null, - val isSending: Boolean = false, -) diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposer.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposer.kt deleted file mode 100644 index 5688268..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposer.kt +++ /dev/null @@ -1,87 +0,0 @@ -package dev.plexus.shared.features.chat.components - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import dev.plexus.shared.core.domain.model.LLMModel -import dev.plexus.shared.core.ui.components.rememberVoiceInputCoordinator - -/** - * チャット入力欄と送信/音声入力操作をまとめたコンポーザー。 - * - * @param models モデル選択に表示する候補一覧 - * @param selectedModelId 現在選択中のモデルID - * @param isLoadingModels モデル一覧の読み込み状態 - * @param modelsError モデル一覧取得エラー - * @param onModelSelected モデル選択時のコールバック - * @param onSendMessage 送信時のコールバック - * @param isLoading メッセージ送信中かどうか - * @param modifier 追加のModifier - */ -@Composable -fun ChatComposer( - models: List, - selectedModelId: String?, - isLoadingModels: Boolean, - modelsError: String?, - onModelSelected: (String) -> Unit, - onSendMessage: (String) -> Unit, - isLoading: Boolean = false, - modifier: Modifier = Modifier, -) { - var text by remember { mutableStateOf("") } - var voiceInputError by remember { mutableStateOf(null) } - val voiceInputCoordinator = - rememberVoiceInputCoordinator( - onRecognizedText = { recognizedText -> - val current = text.trim() - val recognized = recognizedText.trim() - if (recognized.isNotEmpty()) { - text = - if (current.isEmpty()) { - recognized - } else { - "$current $recognized" - } - } - }, - onError = { error -> - if (error.isNotEmpty()) { - voiceInputError = error - } - }, - ) - - ChatComposerField( - text = text, - onTextChange = { text = it }, - isLoading = isLoading, - models = models, - voiceInputError = voiceInputError, - onClearVoiceInputError = { voiceInputError = null }, - selectedModelId = selectedModelId, - isLoadingModels = isLoadingModels, - modelsError = modelsError, - onModelSelected = onModelSelected, - onSendMessage = { - val message = text.trim() - if (message.isEmpty()) return@ChatComposerField - onSendMessage(message) - text = "" - }, - onVoiceInputClick = voiceInputCoordinator.onToggle, - isVoiceInputActive = voiceInputCoordinator.isActive, - modifier = - modifier - .fillMaxWidth() - .padding( - horizontal = ChatComposerMetrics.outerHorizontalPadding, - vertical = ChatComposerMetrics.outerVerticalPadding, - ), - ) -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposerComponents.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposerComponents.kt deleted file mode 100644 index cb7a7f5..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatComposerComponents.kt +++ /dev/null @@ -1,258 +0,0 @@ -package dev.plexus.shared.features.chat.components - -import androidx.compose.foundation.border -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Send -import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.outlined.Error -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.unit.dp -import dev.plexus.shared.core.domain.model.LLMModel -import dev.plexus.shared.core.ui.common.testTagResourceId -import dev.plexus.shared.core.ui.components.VoiceInputToggleButton -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens - -internal object ChatComposerMetrics { - val outerHorizontalPadding - @Composable get() = PlexusThemeTokens.dimens.space12 - - val outerVerticalPadding - @Composable get() = PlexusThemeTokens.dimens.space12 - - val actionButtonsSpacing - @Composable get() = PlexusThemeTokens.dimens.space8 - - val containerMinHeight - @Composable get() = PlexusThemeTokens.dimens.chatComposerMinHeight - - const val INPUT_MIN_LINES = 1 - const val INPUT_MAX_LINES = 3 - - val contentHorizontalPadding - @Composable get() = PlexusThemeTokens.dimens.space16 - - val contentTopPadding - @Composable get() = PlexusThemeTokens.dimens.space12 - - val contentBottomPadding - @Composable get() = PlexusThemeTokens.dimens.space8 - - val modelSelectorSpacing - @Composable get() = PlexusThemeTokens.dimens.space8 -} - -@OptIn(ExperimentalMaterial3Api::class) -/** - * チャット入力本体のUI。 - * - * モデル選択、送信、音声入力ボタンを同一コンテナで扱う。 - */ -@Composable -internal fun ChatComposerField( - text: String, - onTextChange: (String) -> Unit, - isLoading: Boolean, - models: List, - selectedModelId: String?, - isLoadingModels: Boolean, - modelsError: String?, - onModelSelected: (String) -> Unit, - onSendMessage: () -> Unit, - onVoiceInputClick: (() -> Unit)? = null, - isVoiceInputActive: Boolean = false, - voiceInputError: String? = null, - onClearVoiceInputError: () -> Unit = {}, - modifier: Modifier = Modifier, -) { - val interactionSource = remember { MutableInteractionSource() } - val dimens = PlexusThemeTokens.dimens - val shapes = PlexusThemeTokens.shapes - - val colors = OutlinedTextFieldDefaults.colors() - val shape: Shape = shapes.radiusXl - val inputContainerColor = MaterialTheme.colorScheme.surface - val inputBorderColor = MaterialTheme.colorScheme.outline - - Column(modifier = modifier) { - BasicTextField( - value = text, - onValueChange = onTextChange, - modifier = - Modifier - .testTagResourceId("chat_input_field") - .fillMaxWidth() - .heightIn(min = ChatComposerMetrics.containerMinHeight), - textStyle = - LocalTextStyle.current.copy(color = colors.unfocusedTextColor), - enabled = !isLoading, - minLines = ChatComposerMetrics.INPUT_MIN_LINES, - maxLines = ChatComposerMetrics.INPUT_MAX_LINES, - interactionSource = interactionSource, - cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary), - decorationBox = { innerTextField -> - Surface( - modifier = - Modifier - .fillMaxWidth() - .clip(shape) - .border( - width = dimens.borderWidthThin, - color = inputBorderColor, - shape = shape, - ), - shape = shape, - color = inputContainerColor, - contentColor = MaterialTheme.colorScheme.onSurface, - ) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding( - start = ChatComposerMetrics.contentHorizontalPadding, - top = ChatComposerMetrics.contentTopPadding, - end = ChatComposerMetrics.contentHorizontalPadding, - bottom = ChatComposerMetrics.contentBottomPadding, - ), - ) { - Box( - modifier = Modifier.fillMaxWidth().heightIn(min = dimens.chatComposerTextLaneMinHeight), - ) { - if (text.isEmpty()) { - Text( - text = "Type a message...", - color = colors.unfocusedPlaceholderColor, - ) - } - innerTextField() - } - Spacer(modifier = Modifier.height(ChatComposerMetrics.modelSelectorSpacing)) - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - ChatModelSelector( - models = models, - selectedModelId = selectedModelId, - isLoading = isLoadingModels, - error = modelsError, - onModelSelected = onModelSelected, - modifier = Modifier.weight(1f), - ) - - onVoiceInputClick?.let { voiceInputClick -> - Spacer(modifier = Modifier.width(ChatComposerMetrics.actionButtonsSpacing)) - VoiceInputToggleButton( - isActive = isVoiceInputActive, - onClick = voiceInputClick, - testTag = "mic_button", - ) - } - - Spacer(modifier = Modifier.width(ChatComposerMetrics.actionButtonsSpacing)) - SendButton( - enabled = text.isNotBlank() && !isLoading, - onClick = onSendMessage, - ) - } - } - } - }, - ) - - if (voiceInputError != null) { - Spacer(modifier = Modifier.height(dimens.space8)) - Card( - colors = - CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - ), - shape = shapes.radiusSm, - modifier = Modifier.fillMaxWidth(), - ) { - Row( - modifier = Modifier.padding(dimens.space8), - horizontalArrangement = Arrangement.spacedBy(dimens.space8), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - imageVector = Icons.Outlined.Error, - contentDescription = "Error", - tint = MaterialTheme.colorScheme.onErrorContainer, - modifier = Modifier.size(20.dp), - ) - Text( - text = voiceInputError, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onErrorContainer, - modifier = Modifier.weight(1f), - ) - IconButton( - onClick = onClearVoiceInputError, - modifier = Modifier.size(24.dp), - ) { - Icon( - imageVector = Icons.Outlined.Close, - contentDescription = "Dismiss", - tint = MaterialTheme.colorScheme.onErrorContainer, - modifier = Modifier.size(18.dp), - ) - } - } - } - } - } -} - -/** - * 送信アイコンボタン。 - * - * @param enabled 押下可能かどうか - * @param onClick 押下時の処理 - */ -@Composable -internal fun SendButton( - enabled: Boolean, - onClick: () -> Unit, -) { - IconButton( - onClick = onClick, - enabled = enabled, - modifier = Modifier.testTagResourceId("send_button"), - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.Send, - contentDescription = "Send", - tint = if (enabled) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.onSurfaceVariant, - ) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatMessage.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatMessage.kt deleted file mode 100644 index 59efedb..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatMessage.kt +++ /dev/null @@ -1,340 +0,0 @@ -package dev.plexus.shared.features.chat.components - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.mikepenz.markdown.annotator.annotatorSettings -import com.mikepenz.markdown.compose.LocalMarkdownDimens -import com.mikepenz.markdown.compose.components.markdownComponents -import com.mikepenz.markdown.compose.elements.MarkdownTable -import com.mikepenz.markdown.compose.elements.MarkdownTableBasicText -import com.mikepenz.markdown.compose.elements.highlightedCodeBlock -import com.mikepenz.markdown.compose.elements.highlightedCodeFence -import com.mikepenz.markdown.m3.Markdown -import com.mikepenz.markdown.m3.markdownColor -import com.mikepenz.markdown.m3.markdownTypography -import com.mikepenz.markdown.model.markdownDimens -import dev.plexus.shared.core.domain.model.MessageRole -import dev.plexus.shared.core.domain.model.ThreadMessage -import dev.plexus.shared.core.ui.common.testTagResourceId -import dev.plexus.shared.core.ui.components.AssistantContentBlock -import dev.plexus.shared.core.ui.components.MermaidDiagram -import dev.plexus.shared.core.ui.components.splitAssistantContent -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens -import org.intellij.markdown.ast.ASTNode -import org.intellij.markdown.ast.getTextInNode -import org.intellij.markdown.flavours.gfm.GFMElementTypes.HEADER -import org.intellij.markdown.flavours.gfm.GFMElementTypes.ROW -import org.intellij.markdown.flavours.gfm.GFMTokenTypes.CELL - -/** - * チャットメッセージコンポーネント - * - * ユーザーメッセージとアシスタントメッセージを表示する。 - * - * @param message メッセージ - * @param modifier Modifier - * @param isStreaming ストリーミング中フラグ - * @param activeAssistantTask アクティブなアシスタントタスク - */ -@Composable -fun ChatMessage( - message: ThreadMessage, - modifier: Modifier = Modifier, - isStreaming: Boolean = false, - activeAssistantTask: String? = null, -) { - when (message.role) { - MessageRole.USER -> UserMessage(message, modifier) - MessageRole.ASSISTANT -> AssistantMessage(message, modifier, isStreaming, activeAssistantTask) - MessageRole.SYSTEM, - MessageRole.TOOL, - -> Unit - } -} - -@Composable -private fun UserMessage( - message: ThreadMessage, - modifier: Modifier = Modifier, -) { - val dimens = PlexusThemeTokens.dimens - - Row( - modifier = - modifier - .fillMaxWidth() - .padding(horizontal = dimens.space16, vertical = dimens.space8), - horizontalArrangement = Arrangement.End, - ) { - Column( - horizontalAlignment = Alignment.End, - modifier = Modifier.weight(1f, fill = false), - ) { - MessageBubble(isUser = true) { - Text( - text = message.content, - modifier = Modifier.padding(dimens.space12), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onPrimaryContainer, - ) - } - } - } -} - -@Composable -private fun AssistantMessage( - message: ThreadMessage, - modifier: Modifier = Modifier, - isStreaming: Boolean = false, - activeAssistantTask: String? = null, -) { - val dimens = PlexusThemeTokens.dimens - val contentBlocks = remember(message.content) { splitAssistantContent(message.content) } - val textColor = MaterialTheme.colorScheme.onSurfaceVariant - val assistantBodyStyle = - MaterialTheme.typography.bodyMedium.copy( - lineHeight = 28.sp, - ) - val markdownTextStyles = - markdownTypography( - h1 = MaterialTheme.typography.titleLarge, - h2 = MaterialTheme.typography.titleMedium, - h3 = MaterialTheme.typography.titleSmall, - h4 = MaterialTheme.typography.bodyLarge, - h5 = MaterialTheme.typography.bodyMedium, - h6 = MaterialTheme.typography.bodyMedium, - text = assistantBodyStyle, - paragraph = assistantBodyStyle, - ordered = assistantBodyStyle, - bullet = assistantBodyStyle, - list = assistantBodyStyle, - table = MaterialTheme.typography.bodySmall.copy(lineHeight = 22.sp), - ) - val markdownColors = markdownColor(text = textColor) - val markdownDimens = - markdownDimens( - tableCellWidth = 96.dp, - tableMaxWidth = 360.dp, - tableCellPadding = 6.dp, - ) - val markdownRendererComponents = - remember { - markdownComponents( - codeBlock = highlightedCodeBlock, - codeFence = highlightedCodeFence, - table = { model -> - val columnWeights = remember(model.node, model.content) { calculateTableColumnWeights(model.node, model.content) } - MarkdownTable( - content = model.content, - node = model.node, - style = model.typography.table, - headerBlock = { content, header, tableWidth, style -> - DynamicTableLine( - content = content, - row = header, - tableWidth = tableWidth, - style = style.copy(fontWeight = FontWeight.Bold), - columnWeights = columnWeights, - ) - }, - rowBlock = { content, row, tableWidth, style -> - DynamicTableLine( - content = content, - row = row, - tableWidth = tableWidth, - style = style, - columnWeights = columnWeights, - ) - }, - ) - }, - ) - } - - Row( - modifier = - modifier - .fillMaxWidth() - .padding(horizontal = dimens.space16, vertical = dimens.space8), - horizontalArrangement = Arrangement.Start, - ) { - Column( - horizontalAlignment = Alignment.Start, - modifier = Modifier.weight(1f), - ) { - if (!isStreaming) { - contentBlocks.forEach { block -> - when (block) { - is AssistantContentBlock.Markdown -> { - Markdown( - content = block.content, - modifier = Modifier.padding(horizontal = dimens.space6, vertical = dimens.space4), - colors = markdownColors, - typography = markdownTextStyles, - components = markdownRendererComponents, - dimens = markdownDimens, - ) - } - - is AssistantContentBlock.Mermaid -> { - MermaidDiagram( - mermaidCode = block.code, - modifier = Modifier.fillMaxWidth(), - ) - } - } - } - } else { - if (message.content.isBlank()) { - val statusText = activeAssistantTask?.let { "Running $it..." } ?: "Thinking..." - Row( - modifier = Modifier.padding(horizontal = dimens.space6, vertical = dimens.space4), - verticalAlignment = Alignment.CenterVertically, - ) { - CircularProgressIndicator( - modifier = Modifier.size(dimens.iconSizeSmall), - strokeWidth = dimens.space2, - color = MaterialTheme.colorScheme.secondary, - ) - Spacer(modifier = Modifier.width(dimens.space8)) - Text( - text = statusText, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } else { - Markdown( - content = message.content, - modifier = Modifier.padding(horizontal = dimens.space6, vertical = dimens.space4), - colors = markdownColors, - typography = markdownTextStyles, - components = markdownRendererComponents, - dimens = markdownDimens, - ) - } - } - - if (message.modelName != null) { - Text( - text = message.modelName, - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), - modifier = Modifier.padding(top = dimens.space4, start = dimens.space4), - ) - } - } - } -} - -private fun calculateTableColumnWeights( - tableNode: ASTNode, - content: String, -): List { - val rows = tableNode.children.filter { it.type == HEADER || it.type == ROW } - val columnCount = rows.maxOfOrNull { row -> row.children.count { it.type == CELL } } ?: return emptyList() - val maxLengths = MutableList(columnCount) { 1 } - - rows.forEach { row -> - row.children.filter { it.type == CELL }.forEachIndexed { index, cell -> - val normalizedLength = - cell - .getTextInNode(content) - .toString() - .trim() - .replace('\n', ' ') - .length - .coerceAtLeast(1) - if (normalizedLength > maxLengths[index]) { - maxLengths[index] = normalizedLength - } - } - } - - return maxLengths.map { length -> - val boundedLength = length.coerceIn(6, 36) - boundedLength.toFloat() - } -} - -@Composable -private fun DynamicTableLine( - content: String, - row: ASTNode, - tableWidth: androidx.compose.ui.unit.Dp, - style: androidx.compose.ui.text.TextStyle, - columnWeights: List, -) { - val tableCellPadding = LocalMarkdownDimens.current.tableCellPadding - val cells = row.children.filter { it.type == CELL } - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.widthIn(max = tableWidth).height(IntrinsicSize.Max), - ) { - cells.forEachIndexed { index, cell -> - val columnWeight = columnWeights.getOrNull(index) ?: 1f - Column( - modifier = - Modifier - .padding(tableCellPadding) - .weight(columnWeight.coerceAtLeast(1f)), - ) { - MarkdownTableBasicText( - content = content, - cell = cell, - style = style, - maxLines = Int.MAX_VALUE, - overflow = TextOverflow.Clip, - annotatorSettings = annotatorSettings(), - ) - } - } - } -} - -@Composable -private fun MessageBubble( - isUser: Boolean, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { - val shapes = PlexusThemeTokens.shapes - - Surface( - shape = shapes.radiusMd, - color = - if (isUser) { - MaterialTheme.colorScheme.primaryContainer - } else { - MaterialTheme.colorScheme.surfaceVariant - }, - modifier = - modifier - .testTagResourceId(if (isUser) "user_message_bubble" else "assistant_message_bubble"), - ) { - content() - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatModelSelector.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatModelSelector.kt deleted file mode 100644 index e8454fc..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ChatModelSelector.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.plexus.shared.features.chat.components - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import dev.plexus.shared.core.domain.model.LLMModel - -@Composable -fun ChatModelSelector( - models: List, - selectedModelId: String?, - isLoading: Boolean, - error: String?, - onModelSelected: (String) -> Unit, - modifier: Modifier = Modifier, -) { - ModelSelector( - models = models, - selectedModelId = selectedModelId, - isLoading = isLoading, - error = error, - onModelSelected = onModelSelected, - modifier = modifier, - ) -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ErrorBanner.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ErrorBanner.kt deleted file mode 100644 index a6c2d96..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ErrorBanner.kt +++ /dev/null @@ -1,94 +0,0 @@ -package dev.plexus.shared.features.chat.components - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Refresh -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens -import dev.plexus.shared.features.chat.ChatErrorState - -/** - * エラーバナー コンポーネント - * - * チャット機能で発生したエラーを表示し、ユーザーに適切なアクションを提案します。 - * - * @param errorState エラー状態 - * @param onRetry リトライ時のコールバック(省略可能) - * @param onDismiss 閉じる際のコールバック - * @param modifier 修飾子 - */ -@Composable -fun ErrorBanner( - errorState: ChatErrorState, - onRetry: (() -> Unit)? = null, - onDismiss: () -> Unit, - modifier: Modifier = Modifier, -) { - val dimens = PlexusThemeTokens.dimens - val backgroundColor = - when (errorState.severity) { - dev.plexus.shared.core.domain.repository.ErrorSeverity.INFO -> MaterialTheme.colorScheme.primaryContainer - dev.plexus.shared.core.domain.repository.ErrorSeverity.WARNING -> MaterialTheme.colorScheme.tertiaryContainer - dev.plexus.shared.core.domain.repository.ErrorSeverity.ERROR -> MaterialTheme.colorScheme.errorContainer - dev.plexus.shared.core.domain.repository.ErrorSeverity.CRITICAL -> MaterialTheme.colorScheme.error - } - - val contentColor = - when (errorState.severity) { - dev.plexus.shared.core.domain.repository.ErrorSeverity.CRITICAL -> MaterialTheme.colorScheme.onError - dev.plexus.shared.core.domain.repository.ErrorSeverity.ERROR -> MaterialTheme.colorScheme.onErrorContainer - dev.plexus.shared.core.domain.repository.ErrorSeverity.WARNING -> MaterialTheme.colorScheme.onTertiaryContainer - dev.plexus.shared.core.domain.repository.ErrorSeverity.INFO -> MaterialTheme.colorScheme.onPrimaryContainer - } - - Surface( - modifier = modifier.fillMaxWidth(), - color = backgroundColor, - ) { - Row( - modifier = Modifier.fillMaxWidth().padding(dimens.space16), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = errorState.message, - style = MaterialTheme.typography.bodyMedium, - color = contentColor, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f), - ) - - if (errorState.isRetryable && onRetry != null) { - Spacer(modifier = Modifier.width(dimens.space8)) - IconButton(onClick = onRetry) { - Icon( - imageVector = Icons.Default.Refresh, - contentDescription = "再試行", - tint = contentColor, - ) - } - } - - IconButton(onClick = onDismiss) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "閉じる", - tint = contentColor, - ) - } - } - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/MessageList.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/MessageList.kt deleted file mode 100644 index 71a59cb..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/MessageList.kt +++ /dev/null @@ -1,152 +0,0 @@ -package dev.plexus.shared.features.chat.components - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import dev.plexus.shared.core.domain.model.ThreadMessage -import dev.plexus.shared.core.ui.common.ListStateContent -import dev.plexus.shared.core.ui.common.testTagResourceId -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.filter - -/** - * メッセージ一覧コンポーネント - * - * チャットのメッセージリストを表示する。スクロール時にキーボードを非表示にする。 - * - * @param messages メッセージ一覧 - * @param modifier Modifier - * @param isLoading 読み込み中フラグ - * @param errorMessage エラーメッセージ - * @param streamingMessageId ストリーミング中のメッセージID - * @param activeAssistantTask アクティブなアシスタントタスク - */ -@Composable -fun MessageList( - messages: List, - modifier: Modifier = Modifier, - isLoading: Boolean = false, - errorMessage: String? = null, - streamingMessageId: String? = null, - activeAssistantTask: String? = null, -) { - val dimens = PlexusThemeTokens.dimens - val listState = rememberLazyListState() - val reversedMessages = remember(messages) { messages.asReversed() } - val keyboardController = LocalSoftwareKeyboardController.current - val focusManager = LocalFocusManager.current - - LaunchedEffect(listState) { - snapshotFlow { listState.isScrollInProgress } - .filter { it } - .collectLatest { - keyboardController?.hide() - focusManager.clearFocus() - } - } - - ListStateContent( - items = messages, - isLoading = isLoading, - errorMessage = errorMessage, - modifier = modifier.fillMaxSize(), - loading = { containerModifier -> - Box(modifier = containerModifier, contentAlignment = Alignment.Center) { - CircularProgressIndicator(color = MaterialTheme.colorScheme.secondary) - } - }, - empty = { containerModifier -> - Box(modifier = containerModifier, contentAlignment = Alignment.Center) { - MessageListEmpty() - } - }, - error = { message, containerModifier -> - Box(modifier = containerModifier, contentAlignment = Alignment.Center) { - Text( - text = message, - color = MaterialTheme.colorScheme.error, - modifier = Modifier.testTagResourceId("error_message"), - ) - } - }, - content = { _, containerModifier -> - LazyColumn( - state = listState, - reverseLayout = true, - modifier = - containerModifier - .testTagResourceId("message_list"), - contentPadding = PaddingValues(vertical = dimens.space16), - ) { - if (isLoading) { - item { - Box( - modifier = - Modifier - .fillMaxWidth() - .padding(dimens.space16), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator( - color = MaterialTheme.colorScheme.secondary, - modifier = Modifier.padding(dimens.space8), - ) - } - } - } - - items( - items = reversedMessages, - key = { it.messageId }, - ) { message -> - ChatMessage( - message = message, - isStreaming = message.messageId == streamingMessageId, - activeAssistantTask = activeAssistantTask, - ) - } - } - }, - ) -} - -/** - * メッセージ一覧が空の場合の表示 - * - * @param modifier Modifier - */ -@Composable -fun MessageListEmpty(modifier: Modifier = Modifier) { - val dimens = PlexusThemeTokens.dimens - - Box( - modifier = - modifier - .fillMaxWidth() - .padding(dimens.space32), - contentAlignment = Alignment.Center, - ) { - Text( - text = "No messages yet", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ModelSelector.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ModelSelector.kt deleted file mode 100644 index 3fe376c..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/components/ModelSelector.kt +++ /dev/null @@ -1,151 +0,0 @@ -package dev.plexus.shared.features.chat.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.widthIn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow -import dev.plexus.shared.core.domain.model.LLMModel -import dev.plexus.shared.core.ui.common.testTagResourceId -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens - -/** - * モデル選択コンポーネント - * - * ドロップダウンでLLMモデルを選択する。 - * - * @param models モデル一覧 - * @param selectedModelId 選択中のモデルID - * @param isLoading 読み込み中フラグ - * @param error エラーメッセージ - * @param onModelSelected モデル選択コールバック - * @param modifier Modifier - */ -@Composable -fun ModelSelector( - models: List, - selectedModelId: String?, - isLoading: Boolean, - error: String?, - onModelSelected: (String) -> Unit, - modifier: Modifier = Modifier, -) { - val dimens = PlexusThemeTokens.dimens - val shapes = PlexusThemeTokens.shapes - var expanded by remember { mutableStateOf(false) } - - val selectedModel = models.find { it.id == selectedModelId } - - val displayText = - when { - isLoading -> "Loading..." - error != null -> "Error" - models.isEmpty() -> "No models" - selectedModel != null -> selectedModel.name - else -> "Select Model" - } - - val isEnabled = !isLoading && error == null && models.isNotEmpty() - - val selectorBg = MaterialTheme.colorScheme.tertiaryContainer - val selectorFg = MaterialTheme.colorScheme.onTertiaryContainer - - Box( - modifier = - modifier - .testTagResourceId("model_selector"), - ) { - Surface( - color = selectorBg, - contentColor = selectorFg, - shape = shapes.radiusSm, - modifier = - Modifier - .testTagResourceId("model_selector_surface") - .widthIn(max = dimens.modelSelectorMaxWidth) - .clickable(enabled = isEnabled) { expanded = !expanded }, - ) { - Row( - modifier = Modifier.padding(horizontal = dimens.space8, vertical = dimens.space4), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = displayText, - style = MaterialTheme.typography.labelSmall, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = selectorFg, - modifier = - Modifier - .testTagResourceId("model_selector_label") - .weight(1f), - ) - Icon( - imageVector = Icons.Filled.ArrowDropDown, - contentDescription = null, - tint = selectorFg, - modifier = Modifier.padding(start = dimens.space2), - ) - } - } - - if (isEnabled) { - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - containerColor = selectorBg, - ) { - models.forEach { model -> - DropdownMenuItem( - text = { - Column { - Text( - text = model.name, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.secondary, - ) - Text( - text = formatCost(model), - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onTertiaryContainer, - ) - } - }, - onClick = { - onModelSelected(model.id) - expanded = false - }, - ) - } - } - } - } -} - -private fun formatCost(model: LLMModel): String { - if (model.isFree) return "Free" - val inputCost = model.inputCostPer1m - val outputCost = model.outputCostPer1m - return if (inputCost == outputCost) { - "$${String.format("%.2f", inputCost)}/1M" - } else { - "In: $${String.format("%.2f", inputCost)}/1M, Out: $${String.format("%.2f", outputCost)}/1M" - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/reducer/ChatStreamReduceResult.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/reducer/ChatStreamReduceResult.kt deleted file mode 100644 index eb5d5d5..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/reducer/ChatStreamReduceResult.kt +++ /dev/null @@ -1,75 +0,0 @@ -package dev.plexus.shared.features.chat.reducer - -import dev.plexus.shared.core.domain.model.StreamChunk -import dev.plexus.shared.core.domain.model.StreamChunkType -import dev.plexus.shared.features.chat.MessageListState - -data class ChatStreamReduceResult( - val state: MessageListState, - val uiMessage: String? = null, - val newThreadId: String? = null, -) - -fun reduceChatStreamChunk( - state: MessageListState, - chunk: StreamChunk, - streamingMessageId: String, -): ChatStreamReduceResult = - when (chunk.type) { - StreamChunkType.DELTA -> { - val delta = chunk.delta.orEmpty() - if (delta.isEmpty()) { - ChatStreamReduceResult(state = state) - } else { - ChatStreamReduceResult( - state = - state.copy( - messages = - state.messages.map { message -> - if (message.messageId == streamingMessageId) { - message.copy(content = message.content + delta) - } else { - message - } - }, - ), - ) - } - } - - StreamChunkType.TOOL_CALL -> { - val taskName = chunk.toolName ?: chunk.toolCalls?.firstOrNull()?.name - // タスク名が取得できない場合は現在の状態を維持 - if (taskName != null) { - ChatStreamReduceResult(state = state.copy(activeAssistantTask = taskName)) - } else { - ChatStreamReduceResult(state = state) - } - } - - StreamChunkType.TOOL_RESULT -> { - ChatStreamReduceResult(state = state.copy(activeAssistantTask = null)) - } - - StreamChunkType.DONE -> { - ChatStreamReduceResult( - state = - state.copy( - streamingMessageId = null, - activeAssistantTask = null, - ), - newThreadId = chunk.threadId, - ) - } - - StreamChunkType.ERROR -> { - ChatStreamReduceResult( - state = - state.copy( - streamingMessageId = null, - activeAssistantTask = null, - ), - uiMessage = chunk.error ?: "不明なエラー", - ) - } - } diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadItem.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadItem.kt deleted file mode 100644 index 05504a1..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadItem.kt +++ /dev/null @@ -1,87 +0,0 @@ -package dev.plexus.shared.features.chat.threads - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.style.TextOverflow -import dev.plexus.shared.core.domain.model.Thread -import dev.plexus.shared.core.ui.common.testTagResourceId -import dev.plexus.shared.core.ui.common.toCompactIsoDateTime -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens - -/** - * スレッドリストアイテムコンポーネント - * - * @param thread スレッド情報 - * @param isActive アクティブフラグ - * @param onClick クリックコールバック - * @param modifier Modifier - */ -@Composable -fun ThreadItem( - thread: Thread, - isActive: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - val dimens = PlexusThemeTokens.dimens - val shapes = PlexusThemeTokens.shapes - - val backgroundColor = - if (isActive) { - MaterialTheme.colorScheme.secondaryContainer - } else { - MaterialTheme.colorScheme.surface - } - - val contentColor = - if (isActive) { - MaterialTheme.colorScheme.onSecondaryContainer - } else { - MaterialTheme.colorScheme.onSurface - } - - val borderColor = - if (isActive) { - MaterialTheme.colorScheme.secondary - } else { - MaterialTheme.colorScheme.outlineVariant - } - - Column( - modifier = - modifier - .testTagResourceId("thread_item") - .fillMaxWidth() - .clip(shapes.radiusSm) - .background(backgroundColor) - .border( - width = dimens.borderWidthThin, - color = borderColor, - shape = shapes.radiusSm, - ).clickable(onClick = onClick) - .padding(dimens.space12), - ) { - Text( - text = thread.title, - style = MaterialTheme.typography.bodyMedium, - color = contentColor, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = thread.createdAt.toCompactIsoDateTime(), - style = MaterialTheme.typography.bodySmall, - color = contentColor.copy(alpha = 0.7f), - modifier = Modifier.padding(top = dimens.space4), - ) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadList.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadList.kt deleted file mode 100644 index e5591bc..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadList.kt +++ /dev/null @@ -1,215 +0,0 @@ -package dev.plexus.shared.features.chat.threads - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.pulltorefresh.PullToRefreshBox -import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import dev.plexus.shared.core.domain.model.Thread -import dev.plexus.shared.core.ui.common.testTagResourceId -import dev.plexus.shared.core.ui.components.EmptyView -import dev.plexus.shared.core.ui.components.ErrorView -import dev.plexus.shared.core.ui.components.LoadingView -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens - -/** - * スレッド一覧コンポーネント - * - * チャットスレッドの一覧を表示する。Pull-to-refreshと無限スクロールをサポート。 - * - * @param threads スレッド一覧 - * @param selectedThreadId 選択中のスレッドID - * @param isLoading 読み込み中フラグ - * @param isLoadingMore 追加読み込み中フラグ - * @param hasMore さらに項目があるかどうか - * @param error エラーメッセージ - * @param onThreadClick スレッド選択コールバック - * @param onRefresh 更新コールバック - * @param onLoadMore 追加読み込みコールバック - * @param modifier Modifier - */ -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ThreadList( - threads: List, - selectedThreadId: String?, - isLoading: Boolean, - isLoadingMore: Boolean, - hasMore: Boolean, - error: String?, - onThreadClick: (String) -> Unit, - onRefresh: () -> Unit, - onLoadMore: () -> Unit, - modifier: Modifier = Modifier, -) { - val pullRefreshState = rememberPullToRefreshState() - val listState = rememberLazyListState() - - InfiniteScrollEffect( - listState = listState, - itemCount = threads.size, - isLoading = isLoading, - isLoadingMore = isLoadingMore, - hasMore = hasMore, - error = error, - onLoadMore = onLoadMore, - ) - - Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - if (isLoading && threads.isEmpty()) { - LoadingView() - } else { - PullToRefreshBox( - isRefreshing = isLoading, - onRefresh = onRefresh, - state = pullRefreshState, - modifier = Modifier.fillMaxSize(), - ) { - ThreadListContent( - threads = threads, - selectedThreadId = selectedThreadId, - isLoadingMore = isLoadingMore, - error = error, - listState = listState, - onThreadClick = onThreadClick, - ) - } - } - } -} - -@Composable -private fun ThreadListContent( - threads: List, - selectedThreadId: String?, - isLoadingMore: Boolean, - error: String?, - listState: LazyListState, - onThreadClick: (String) -> Unit, -) { - val dimens = PlexusThemeTokens.dimens - - if (error != null && threads.isEmpty()) { - Box( - modifier = - Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()), - ) { - ErrorView(message = error) - } - } else if (threads.isEmpty()) { - Box( - modifier = - Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()), - ) { - EmptyView(message = "No threads found") - } - } else { - LazyColumn( - state = listState, - contentPadding = PaddingValues(dimens.space16), - verticalArrangement = Arrangement.spacedBy(dimens.space8), - modifier = - Modifier - .testTagResourceId("thread_list") - .fillMaxSize(), - ) { - items( - items = threads, - key = { it.threadId }, - ) { thread -> - ThreadItem( - thread = thread, - isActive = thread.threadId == selectedThreadId, - onClick = { onThreadClick(thread.threadId) }, - ) - } - - if (isLoadingMore) { - item { - LoadingMoreIndicator() - } - } - } - } -} - -@Composable -private fun LoadingMoreIndicator() { - val dimens = PlexusThemeTokens.dimens - - Box( - modifier = - Modifier - .fillMaxWidth() - .padding(dimens.space12), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator() - } -} - -@Composable -private fun InfiniteScrollEffect( - listState: LazyListState, - itemCount: Int, - isLoading: Boolean, - isLoadingMore: Boolean, - hasMore: Boolean, - error: String?, - onLoadMore: () -> Unit, -) { - var lastRequestedSize by remember { mutableIntStateOf(0) } - val lastVisibleIndex by remember { - derivedStateOf { - listState.layoutInfo.visibleItemsInfo - .lastOrNull() - ?.index - } - } - - LaunchedEffect(error) { - if (error != null) { - lastRequestedSize = 0 - } - } - - LaunchedEffect(lastVisibleIndex, itemCount, isLoading, isLoadingMore, hasMore) { - val lastIndex = lastVisibleIndex - if ( - lastIndex != null && - itemCount > 0 && - lastIndex >= itemCount - 4 && - hasMore && - !isLoading && - !isLoadingMore && - itemCount != lastRequestedSize - ) { - lastRequestedSize = itemCount - onLoadMore() - } - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListEmpty.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListEmpty.kt deleted file mode 100644 index 408a5d9..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListEmpty.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.plexus.shared.features.chat.threads - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens - -@Composable -fun ThreadListEmpty(modifier: Modifier = Modifier) { - val dimens = PlexusThemeTokens.dimens - - Box( - modifier = - modifier - .fillMaxWidth() - .padding(dimens.space32), - contentAlignment = Alignment.Center, - ) { - Text( - text = "No threads found", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListError.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListError.kt deleted file mode 100644 index 118014f..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListError.kt +++ /dev/null @@ -1,33 +0,0 @@ -package dev.plexus.shared.features.chat.threads - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens - -@Composable -fun ThreadListError( - message: String, - modifier: Modifier = Modifier, -) { - val dimens = PlexusThemeTokens.dimens - - Box( - modifier = - modifier - .fillMaxWidth() - .padding(dimens.space16), - contentAlignment = Alignment.Center, - ) { - Text( - text = message, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.error, - ) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListLoading.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListLoading.kt deleted file mode 100644 index d235476..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListLoading.kt +++ /dev/null @@ -1,33 +0,0 @@ -package dev.plexus.shared.features.chat.threads - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens - -@Composable -fun ThreadListLoading( - modifier: Modifier = Modifier, - message: String = "Loading...", -) { - val dimens = PlexusThemeTokens.dimens - - Box( - modifier = - modifier - .fillMaxWidth() - .height(dimens.listLoadingHeight), - contentAlignment = Alignment.Center, - ) { - Text( - text = message, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListScreen.kt deleted file mode 100644 index fd01788..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadListScreen.kt +++ /dev/null @@ -1,51 +0,0 @@ -package dev.plexus.shared.features.chat.threads - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.koin.koinScreenModel -import dev.plexus.shared.features.chat.ChatScreenModel -import dev.plexus.shared.features.chat.ChatState - -/** - * スレッド一覧画面 - * - * チャットスレッドの一覧を表示する画面。 - */ -class ThreadListScreen : Screen { - @Composable - override fun Content() { - val screenModel = koinScreenModel() - val state by screenModel.state.collectAsState() - - ThreadListScreenContent( - state = state, - screenModel = screenModel, - ) - } -} - -@Composable -private fun ThreadListScreenContent( - state: ChatState, - screenModel: ChatScreenModel, -) { - ThreadList( - threads = state.threadList.threads, - selectedThreadId = state.threadList.selectedThread?.threadId, - isLoading = state.threadList.isLoading, - isLoadingMore = state.threadList.isLoadingMore, - hasMore = state.threadList.hasMore, - error = state.threadList.error, - onThreadClick = { threadId -> - screenModel.selectThread(threadId) - }, - onRefresh = { - screenModel.loadThreads() - }, - onLoadMore = { - screenModel.loadMoreThreads() - }, - ) -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadTitleFormatter.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadTitleFormatter.kt deleted file mode 100644 index 6f805a7..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/chat/threads/ThreadTitleFormatter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package dev.plexus.shared.features.chat.threads - -/** - * スレッドタイトルをフォーマットする - * - * 長いタイトルを指定された最大長に切り詰める。 - * - * @param maxLength 最大文字数(デフォルト48) - * @return フォーマットされたタイトル - */ -fun String.toThreadTitle(maxLength: Int = 48): String { - if (maxLength <= 0) { - return "" - } - - val trimmed = trim() - if (trimmed.isEmpty()) { - val fallback = "New chat" - return if (fallback.length > maxLength) { - if (maxLength <= 3) "...".take(maxLength) else fallback.take(maxLength - 3) + "..." - } else { - fallback - } - } - if (trimmed.length <= maxLength) { - return trimmed - } - - if (maxLength <= 3) { - return "...".take(maxLength) - } - - return trimmed.take(maxLength - 3) + "..." -} diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatErrorStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatErrorStateTest.kt deleted file mode 100644 index 490c147..0000000 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatErrorStateTest.kt +++ /dev/null @@ -1,163 +0,0 @@ -package dev.plexus.shared.features.chat - -import dev.plexus.shared.core.domain.repository.ApiError -import dev.plexus.shared.core.domain.repository.ErrorAction -import dev.plexus.shared.core.domain.repository.ErrorSeverity -import dev.plexus.shared.core.domain.repository.TimeoutType -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -/** - * [ChatErrorState]のテスト - * - * [ApiError]から[ChatErrorState]への変換ロジックを検証します。 - */ -class ChatErrorStateTest { - @Test - fun `NetworkError maps correctly`() { - val networkError = - ApiError.NetworkError( - exception = RuntimeException("Connection failed"), - isRetryable = true, - suggestedAction = ErrorAction.RETRY, - severity = ErrorSeverity.ERROR, - ) - - val errorState = networkError.toChatErrorState() - - assertEquals(dev.plexus.shared.features.chat.ErrorType.NETWORK, errorState.type) - assertEquals("ネットワークエラーが発生しました", errorState.message) - assertEquals("Connection failed", errorState.detail) - assertEquals(ErrorAction.RETRY, errorState.action) - assertTrue(errorState.canRetry) - assertTrue(errorState.isRetryable) - assertEquals(ErrorSeverity.ERROR, errorState.severity) - } - - @Test - fun `TimeoutError shows retry action`() { - val timeoutError = - ApiError.TimeoutError( - timeoutType = TimeoutType.STREAMING, - timeoutMillis = 300_000, - suggestedAction = ErrorAction.RETRY, - severity = ErrorSeverity.WARNING, - ) - - val errorState = timeoutError.toChatErrorState() - - assertEquals(dev.plexus.shared.features.chat.ErrorType.TIMEOUT, errorState.type) - assertEquals("応答がタイムアウトしました", errorState.message) - assertEquals(ErrorAction.RETRY, errorState.action) - assertTrue(errorState.canRetry) - assertTrue(errorState.isRetryable) - assertEquals(ErrorSeverity.WARNING, errorState.severity) - } - - @Test - fun `AuthenticationError shows no retry`() { - val authError = - ApiError.AuthenticationError( - errorMessage = "認証に失敗しました", - detail = "APIキーが無効です", - suggestedAction = ErrorAction.REAUTHENTICATE, - severity = ErrorSeverity.ERROR, - ) - - val errorState = authError.toChatErrorState() - - assertEquals(dev.plexus.shared.features.chat.ErrorType.AUTHENTICATION, errorState.type) - assertEquals("認証に失敗しました", errorState.message) - assertEquals("APIキーが無効です", errorState.detail) - assertEquals(ErrorAction.REAUTHENTICATE, errorState.action) - assertFalse(errorState.canRetry) - assertFalse(errorState.isRetryable) - assertEquals(ErrorSeverity.ERROR, errorState.severity) - } - - @Test - fun `HttpError 401 shows authentication error`() { - val httpError = - ApiError.HttpError( - code = 401, - errorMessage = "Unauthorized", - detail = "Invalid API key", - ) - - val errorState = httpError.toChatErrorState() - - assertEquals(dev.plexus.shared.features.chat.ErrorType.AUTHENTICATION, errorState.type) - assertEquals("APIキーが無効です", errorState.message) - assertEquals(ErrorAction.REAUTHENTICATE, errorState.action) - assertFalse(errorState.canRetry) - assertFalse(errorState.isRetryable) - } - - @Test - fun `HttpError 500 shows server error with retry`() { - val httpError = - ApiError.HttpError( - code = 500, - errorMessage = "Internal Server Error", - detail = "Something went wrong", - ) - - val errorState = httpError.toChatErrorState() - - assertEquals(dev.plexus.shared.features.chat.ErrorType.SERVER, errorState.type) - assertEquals("サーバーエラーが発生しました", errorState.message) - assertEquals(ErrorAction.RETRY, errorState.action) - assertTrue(errorState.canRetry) - assertTrue(errorState.isRetryable) - assertEquals(ErrorSeverity.CRITICAL, errorState.severity) - } - - @Test - fun `HttpError 404 shows unknown error with no retry`() { - val httpError = - ApiError.HttpError( - code = 404, - errorMessage = "Not Found", - ) - - val errorState = httpError.toChatErrorState() - - assertEquals(dev.plexus.shared.features.chat.ErrorType.UNKNOWN, errorState.type) - assertTrue(errorState.message.startsWith("HTTP 404")) - assertEquals(ErrorAction.DISMISS, errorState.action) - assertFalse(errorState.canRetry) - assertFalse(errorState.isRetryable) - } - - @Test - fun `UnknownError maps correctly`() { - val unknownError = - ApiError.UnknownError( - exception = RuntimeException("Unknown issue"), - ) - - val errorState = unknownError.toChatErrorState() - - assertEquals(dev.plexus.shared.features.chat.ErrorType.UNKNOWN, errorState.type) - assertTrue(errorState.message.contains("Unknown error")) - assertEquals(ErrorAction.DISMISS, errorState.action) - assertFalse(errorState.canRetry) - } - - @Test - fun `SerializationError maps correctly`() { - val serializationError = - ApiError.SerializationError( - exception = RuntimeException("JSON parse error"), - ) - - val errorState = serializationError.toChatErrorState() - - assertEquals(dev.plexus.shared.features.chat.ErrorType.UNKNOWN, errorState.type) - assertEquals("データの解析に失敗しました", errorState.message) - assertEquals(ErrorAction.DISMISS, errorState.action) - assertFalse(errorState.canRetry) - } -} diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatRepositoryImplTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatRepositoryImplTest.kt deleted file mode 100644 index 75cd4c3..0000000 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatRepositoryImplTest.kt +++ /dev/null @@ -1,524 +0,0 @@ -package dev.plexus.shared.features.chat - -import dev.plexus.shared.core.data.repository.ChatRepositoryImpl -import dev.plexus.shared.core.data.repository.internal.RepositoryClient -import dev.plexus.shared.core.domain.model.ChatRequest -import dev.plexus.shared.core.domain.model.ChatResponse -import dev.plexus.shared.core.domain.model.LLMModel -import dev.plexus.shared.core.domain.model.Message -import dev.plexus.shared.core.domain.model.MessageRole -import dev.plexus.shared.core.domain.model.ModelsResponse -import dev.plexus.shared.core.domain.model.StreamChunk -import dev.plexus.shared.core.domain.model.StreamChunkType -import dev.plexus.shared.core.domain.repository.ApiError -import dev.plexus.shared.core.network.HttpClientConfig -import io.ktor.client.HttpClient -import io.ktor.client.engine.mock.MockEngine -import io.ktor.client.engine.mock.respond -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.http.headersOf -import io.ktor.serialization.kotlinx.json.json -import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.core.buildPacket -import io.ktor.utils.io.core.writeText -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertIs -import kotlin.test.assertTrue - -/** - * ChatRepositoryImplのテスト - * - * Ktor MockEngineを使用してHTTPリクエスト/レスポンスをテストします。 - */ -class ChatRepositoryImplTest { - private val json = - Json { - ignoreUnknownKeys = true - isLenient = true - } - - private val baseUrl = "http://test.example.com" - private val apiKey = "test-api-key" - - /** テスト用HttpClientConfig */ - private val testHttpClientConfig = - HttpClientConfig( - connectTimeoutMillis = 10_000, - socketTimeoutMillis = 30_000, - requestTimeoutMillis = 30_000, - streamingTimeoutMillis = 300_000, - maxRetries = 3, - retryBaseDelayMs = 1_000, - retryMaxDelayMs = 4_000, - ) - - /** - * テスト用HttpClientを作成する - */ - private fun createMockHttpClient(engine: MockEngine): HttpClient = - HttpClient(engine) { - install(ContentNegotiation) { - json(json) - } - } - - /** - * テスト用RepositoryClientを作成する - */ - private fun createMockRepositoryClient(engine: MockEngine): RepositoryClient { - val httpClient = createMockHttpClient(engine) - return RepositoryClient(httpClient, baseUrl, apiKey) - } - - /** - * テスト用ChatRepositoryImplを作成する - */ - private fun createChatRepository(repositoryClient: RepositoryClient): ChatRepositoryImpl = - ChatRepositoryImpl( - repositoryClient = repositoryClient, - httpClientConfig = testHttpClientConfig, - json = json, - ) - - // ==================== sendMessageSync() テスト ==================== - - @Test - fun `sendMessageSync - success returns ChatResponse`() = - runTest { - // Arrange: モックHTTPレスポンスの設定 - val expectedResponse = - ChatResponse( - id = "resp-123", - message = - Message( - role = MessageRole.ASSISTANT, - content = "Hello!", - ), - threadId = "thread-456", - modelName = "gpt-4", - ) - val responseBody = json.encodeToString(expectedResponse) - - val mockEngine = - MockEngine { - // HTTPリクエストのアサーション - assertEquals(HttpMethod.Post, it.method) - assertEquals("$baseUrl/v1/chat", it.url.toString()) - assertEquals(apiKey, it.headers["X-API-Key"]) - - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - val request = - ChatRequest( - messages = listOf(Message(role = MessageRole.USER, content = "Hi")), - stream = false, - ) - - // Act: テスト対象メソッドの実行 - val result = repository.sendMessageSync(request) - - // Assert: 結果の検証 - assertTrue(result.isSuccess) - val actual = result.getOrNull()!! - assertEquals("resp-123", actual.id) - assertEquals("Hello!", actual.message.content) - assertEquals("thread-456", actual.threadId) - assertEquals("gpt-4", actual.modelName) - } - - @Test - fun `sendMessageSync - HTTP 400 returns HttpError`() = - runTest { - // Arrange: 400 Bad Errorのモック設定 - val mockEngine = - MockEngine { - assertEquals(HttpMethod.Post, it.method) - respond( - content = """{"detail": "Invalid request"}""", - status = HttpStatusCode.BadRequest, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - val request = - ChatRequest( - messages = emptyList(), - stream = false, - ) - - // Act - val result = repository.sendMessageSync(request) - - // Assert - assertTrue(result.isFailure) - val error = assertIs(result.exceptionOrNull()) - assertEquals(400, error.code) - assertEquals("Bad Request", error.errorMessage) - assertEquals("""{"detail": "Invalid request"}""", error.detail) - } - - @Test - fun `sendMessageSync - HTTP 401 Unauthorized`() = - runTest { - // Arrange: 401 Unauthorizedのモック設定 - val mockEngine = - MockEngine { - respond( - content = """{"detail": "Invalid API key"}""", - status = HttpStatusCode.Unauthorized, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - val request = - ChatRequest( - messages = emptyList(), - stream = false, - ) - - // Act - val result = repository.sendMessageSync(request) - - // Assert - assertTrue(result.isFailure) - val error = assertIs(result.exceptionOrNull()) - assertEquals(401, error.code) - assertEquals("Unauthorized", error.errorMessage) - } - - @Test - fun `sendMessageSync - HTTP 500 Internal Server Error`() = - runTest { - // Arrange: 500 Internal Server Errorのモック設定 - val mockEngine = - MockEngine { - respond( - content = """{"detail": "Internal server error"}""", - status = HttpStatusCode.InternalServerError, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - val request = - ChatRequest( - messages = emptyList(), - stream = false, - ) - - // Act - val result = repository.sendMessageSync(request) - - // Assert - assertTrue(result.isFailure) - val error = assertIs(result.exceptionOrNull()) - assertEquals(500, error.code) - } - - @Test - fun `sendMessageSync - network error returns NetworkError`() = - runTest { - // Arrange: ネットワークエラーのモック設定 - val mockEngine = - MockEngine { - throw Exception("Connection timeout") - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - val request = - ChatRequest( - messages = emptyList(), - stream = false, - ) - - // Act - val result = repository.sendMessageSync(request) - - // Assert - assertTrue(result.isFailure) - val error = assertIs(result.exceptionOrNull()) - assertTrue(error.cause is Exception) - } - - // ==================== sendMessage() テスト (Streaming) ==================== - - @Test - fun `sendMessage - SSE streaming returns chunks`() = - runTest { - // Arrange: SSEストリーミングレスポンスの設定 - val sseData = - """ - data: {"type":"delta","delta":"Hello"} - - data: {"type":"delta","delta":" World"} - - data: {"type":"done","finish_reason":"stop"} - - """.trimIndent() - - val mockEngine = - MockEngine { - assertEquals(HttpMethod.Post, it.method) - assertEquals("$baseUrl/v1/chat", it.url.toString()) - - val responseContent = - buildPacket { - writeText(sseData) - } - - respond( - content = ByteReadChannel(responseContent), - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "text/event-stream"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - val request = - ChatRequest( - messages = listOf(Message(role = MessageRole.USER, content = "Hi")), - stream = true, - ) - - // Act: ストリームからチャンクを収集 - val chunks = mutableListOf() - repository.sendMessage(request).collect { result -> - if (result.isSuccess) { - chunks.add(result.getOrNull()!!) - } - } - - // Assert: チャンクの検証 - assertTrue(chunks.size >= 2) - assertEquals(StreamChunkType.DELTA, chunks[0].type) - assertEquals("Hello", chunks[0].delta) - assertEquals(StreamChunkType.DELTA, chunks[1].type) - assertEquals(" World", chunks[1].delta) - } - - @Test - fun `sendMessage - error chunk returns HttpError`() = - runTest { - // Arrange: エラーチャンクを含むSSEレスポンス - val sseData = - """ - data: {"type":"error","error":"Tool execution failed"} - - """.trimIndent() - - val mockEngine = - MockEngine { - val responseContent = - buildPacket { - writeText(sseData) - } - - respond( - content = ByteReadChannel(responseContent), - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "text/event-stream"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - val request = - ChatRequest( - messages = emptyList(), - stream = true, - ) - - // Act: エラー結果を収集 - val errors = mutableListOf() - repository.sendMessage(request).collect { result -> - if (result.isFailure) { - result.exceptionOrNull()?.let { - assertIs(it) - errors.add(it) - } - } - } - - // Assert: エラーの検証 - assertTrue(errors.isNotEmpty()) - val error = assertIs(errors.first()) - assertEquals(500, error.code) - assertEquals("Stream error", error.errorMessage) - assertEquals("Tool execution failed", error.detail) - } - - @Test - fun `sendMessage - includes API key header`() = - runTest { - // Arrange: APIキーヘッダーを検証するモック - val sseData = "data: {\"type\":\"done\"}\n" - var receivedApiKey: String? = null - - val mockEngine = - MockEngine { - receivedApiKey = it.headers["X-API-Key"] - - val responseContent = - buildPacket { - writeText(sseData) - } - - respond( - content = ByteReadChannel(responseContent), - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "text/event-stream"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - val request = - ChatRequest( - messages = emptyList(), - stream = true, - ) - - // Act - repository.sendMessage(request).collect {} - - // Assert: APIキーヘッダーが送信されたことを検証 - assertEquals(apiKey, receivedApiKey) - } - - // ==================== getModels() テスト ==================== - - @Test - fun `getModels - success returns ModelsResponse`() = - runTest { - // Arrange: モデル一覧レスポンスのモック設定 - val expectedResponse = - ModelsResponse( - models = - listOf( - LLMModel( - id = "gpt-4", - name = "GPT-4", - provider = "openai", - inputCostPer1m = 30.0, - outputCostPer1m = 60.0, - isFree = false, - ), - LLMModel( - id = "claude-3", - name = "Claude 3", - provider = "anthropic", - inputCostPer1m = 15.0, - outputCostPer1m = 75.0, - isFree = false, - ), - ), - defaultModel = "gpt-4", - ) - val responseBody = json.encodeToString(expectedResponse) - - val mockEngine = - MockEngine { - assertEquals(HttpMethod.Get, it.method) - assertEquals("$baseUrl/v1/chat/models", it.url.toString()) - assertEquals(apiKey, it.headers["X-API-Key"]) - - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - // Act - val result = repository.getModels() - - // Assert - assertTrue(result.isSuccess) - val actual = result.getOrNull()!! - assertEquals(2, actual.models.size) - assertEquals("gpt-4", actual.models[0].id) - assertEquals("GPT-4", actual.models[0].name) - assertEquals("claude-3", actual.models[1].id) - assertEquals("gpt-4", actual.defaultModel) - } - - @Test - fun `getModels - HTTP 404 Not Found`() = - runTest { - // Arrange: 404 Not Foundのモック設定 - val mockEngine = - MockEngine { - assertEquals(HttpMethod.Get, it.method) - respond( - content = """{"detail": "Models endpoint not found"}""", - status = HttpStatusCode.NotFound, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - // Act - val result = repository.getModels() - - // Assert - assertTrue(result.isFailure) - val error = assertIs(result.exceptionOrNull()) - assertEquals(404, error.code) - // detail にはレスポンスボディ全体が含まれる - assertTrue(error.detail?.contains("Models endpoint not found") == true) - } - - @Test - fun `getModels - network error returns NetworkError`() = - runTest { - // Arrange: ネットワークエラーのモック - val mockEngine = - MockEngine { - throw Exception("Network unreachable") - } - - val repositoryClient = createMockRepositoryClient(mockEngine) - val repository = createChatRepository(repositoryClient) - - // Act - val result = repository.getModels() - - // Assert - assertTrue(result.isFailure) - val error = assertIs(result.exceptionOrNull()) - assertTrue(error.cause is Exception) - } -} diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatStateTest.kt deleted file mode 100644 index 509f20a..0000000 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatStateTest.kt +++ /dev/null @@ -1,124 +0,0 @@ -package dev.plexus.shared.features.chat - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNull -import kotlin.test.assertTrue - -/** - * ChatState のテスト - * - * ChatState の初期状態と派生プロパティを検証します。 - */ -class ChatStateTest { - @Test - fun `ChatState starts with empty collections`() { - val state = ChatState() - - assertEquals(0, state.threadList.threads.size) - assertEquals(0, state.messageList.messages.size) - assertEquals(0, state.composer.models.size) - } - - @Test - fun `ChatState starts without selected thread and model`() { - val state = ChatState() - - assertNull(state.threadList.selectedThread) - assertNull(state.composer.selectedModelId) - } - - @Test - fun `ChatState default flags are false`() { - val state = ChatState() - - assertFalse(state.threadList.isLoading) - assertFalse(state.messageList.isLoading) - assertFalse(state.composer.isLoadingModels) - assertFalse(state.composer.isSending) - assertFalse(state.threadList.isLoadingMore) - } - - @Test - fun `ChatState hasSelectedThread is false by default`() { - val state = ChatState() - - assertFalse(state.hasSelectedThread) - } - - @Test - fun `ChatState isLoading becomes true when a loading flag is true`() { - val state = ChatState(messageList = MessageListState(isLoading = true)) - - assertTrue(state.isLoading) - } - - @Test - fun `ChatState isLoading is true when sending`() { - val state = ChatState(composer = ComposerState(isSending = true)) - - assertTrue(state.isLoading) - } - - @Test - fun `ChatState hasError is true when threadList error exists`() { - val state = ChatState(threadList = ThreadListState(error = "failed")) - - assertTrue(state.hasError) - } - - @Test - fun `ChatState hasError is true when messageList error exists`() { - val state = ChatState(messageList = MessageListState(error = "failed")) - - assertTrue(state.hasError) - } - - @Test - fun `ChatState hasError is true when composer modelsError exists`() { - val state = ChatState(composer = ComposerState(modelsError = "failed")) - - assertTrue(state.hasError) - } - - @Test - fun `ChatState hasError is false when no errors exist`() { - val state = ChatState() - - assertFalse(state.hasError) - } - - @Test - fun `ChatState isLoading is true when any loading flag is true`() { - assertTrue( - ChatState(threadList = ThreadListState(isLoading = true)).isLoading, - ) - assertTrue( - ChatState(messageList = MessageListState(isLoading = true)).isLoading, - ) - assertTrue( - ChatState(composer = ComposerState(isSending = true)).isLoading, - ) - assertTrue( - ChatState(composer = ComposerState(isLoadingModels = true)).isLoading, - ) - assertTrue( - ChatState(threadList = ThreadListState(isLoadingMore = true)).isLoading, - ) - } - - @Test - fun `ChatState isLoading is false by default`() { - val state = ChatState() - - assertFalse(state.isLoading) - } - - @Test - fun `ChatState isLoadingMoreThreads is false by default`() { - val state = ChatState() - - assertFalse(state.threadList.isLoadingMore) - } -} diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatUtilsTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatUtilsTest.kt deleted file mode 100644 index 0656ad2..0000000 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ChatUtilsTest.kt +++ /dev/null @@ -1,281 +0,0 @@ -package dev.plexus.shared.features.chat - -import dev.plexus.shared.features.chat.threads.toThreadTitle -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -/** - * ChatUtilsのテスト - * - * toThreadTitle()関数のエッジケースを検証します。 - */ -class ChatUtilsTest { - @Test - fun `toThreadTitle with empty string returns "New chat"`() { - // Arrange - val input = "" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("New chat", result) - } - - @Test - fun `toThreadTitle with whitespace only returns "New chat"`() { - // Arrange - val input = " " - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("New chat", result) - } - - @Test - fun `toThreadTitle with whitespace around content trims and returns content`() { - // Arrange - val input = " Hello World " - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("Hello World", result) - } - - @Test - fun `toThreadTitle with tab and newline characters trims correctly`() { - // Arrange - val input = "\t\n Hello \n\t" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("Hello", result) - } - - @Test - fun `toThreadTitle with short content returns content unchanged`() { - // Arrange - val input = "Hello" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("Hello", result) - } - - @Test - fun `toThreadTitle with content exactly at maxLength returns content unchanged`() { - // Arrange - val input = "a".repeat(48) - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals(48, result.length) - assertEquals(input, result) - } - - @Test - fun `toThreadTitle with content at maxLength + 1 truncates with ellipsis`() { - // Arrange - val input = "a".repeat(49) - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals(48, result.length) - assertTrue(result.endsWith("...")) - } - - @Test - fun `toThreadTitle with long content truncates and adds ellipsis`() { - // Arrange - val input = "This is a very long title that should be truncated" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals(48, result.length) - assertTrue(result.endsWith("...")) - assertTrue(result.length < input.length) - } - - @Test - fun `toThreadTitle with custom maxLength respects custom limit`() { - // Arrange - val input = "Hello World" - val maxLength = 10 - - // Act - val result = input.toThreadTitle(maxLength) - - // Assert - assertEquals(10, result.length) - assertTrue(result.endsWith("...")) - } - - @Test - fun `toThreadTitle with custom maxLength of zero returns empty string`() { - // Arrange - val input = "Hello World" - val maxLength = 0 - - // Act - val result = input.toThreadTitle(maxLength) - - // Assert - assertEquals("", result) - } - - @Test - fun `toThreadTitle with custom maxLength of 3 returns ellipsis only`() { - // Arrange - val input = "Hello World" - val maxLength = 3 - - // Act - val result = input.toThreadTitle(maxLength) - - // Assert - assertEquals("...", result) - } - - @Test - fun `toThreadTitle truncates at custom maxLength minus 3 for ellipsis`() { - // Arrange - val input = "Hello World" - val maxLength = 8 - - // Act - val result = input.toThreadTitle(maxLength) - - // Assert - assertEquals(8, result.length) - assertTrue(result.endsWith("...")) - assertEquals("Hello...", result) - } - - @Test - fun `toThreadTitle with very long content after whitespace is trimmed`() { - // Arrange - val input = " ".repeat(20) + "Short" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("Short", result) - } - - @Test - fun `toThreadTitle with content needing trailing space trim after truncation`() { - // Arrange - val input = "This is a very long title that needs truncation and more content " - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals(48, result.length) - assertTrue(result.endsWith("...")) - assertFalse(result.endsWith(" ...")) - } - - @Test - fun `toThreadTitle with mixed whitespace content`() { - // Arrange - val input = " \t \n " - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("New chat", result) - } - - @Test - fun `toThreadTitle with unicode characters`() { - // Arrange - val input = "こんにちは世界" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("こんにちは世界", result) - } - - @Test - fun `toThreadTitle with unicode characters shorter than maxLength returns unchanged`() { - // Arrange - val input = "こんにちは世界これは非常に長いタイトルです" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("こんにちは世界これは非常に長いタイトルです", result) - } - - @Test - fun `toThreadTitle with single word shorter than maxLength`() { - // Arrange - val input = "Test" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("Test", result) - } - - @Test - fun `toThreadTitle with single word exactly at maxLength`() { - // Arrange - val input = "a".repeat(48) - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals(48, result.length) - assertFalse(result.endsWith("...")) - } - - @Test - fun `toThreadTitle preserves internal whitespace`() { - // Arrange - val input = "Hello World Test" - - // Act - val result = input.toThreadTitle() - - // Assert - assertEquals("Hello World Test", result) - } - - @Test - fun `toThreadTitle with negative maxLength returns empty string`() { - // Arrange - val input = "Hello World" - val maxLength = -5 - - // Act - val result = input.toThreadTitle(maxLength) - - // Assert - assertEquals("", result) - } -} diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/MessageRepositoryImplTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/MessageRepositoryImplTest.kt deleted file mode 100644 index 85ccfbb..0000000 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/MessageRepositoryImplTest.kt +++ /dev/null @@ -1,292 +0,0 @@ -package dev.plexus.shared.features.chat - -import dev.plexus.shared.core.data.repository.MessageRepositoryImpl -import dev.plexus.shared.core.data.repository.internal.RepositoryClient -import dev.plexus.shared.core.domain.model.MessageRole -import dev.plexus.shared.core.domain.model.ThreadMessage -import dev.plexus.shared.core.domain.model.ThreadMessagesResponse -import dev.plexus.shared.core.domain.repository.ApiError -import io.ktor.client.HttpClient -import io.ktor.client.engine.mock.MockEngine -import io.ktor.client.engine.mock.respond -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.http.headersOf -import io.ktor.serialization.kotlinx.json.json -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertIs -import kotlin.test.assertTrue - -/** - * MessageRepositoryImplのテスト - * - * Ktor MockEngineを使用してHTTPリクエスト/レスポンスをテストします。 - */ -class MessageRepositoryImplTest { - private val json = - Json { - ignoreUnknownKeys = true - isLenient = true - } - - private val baseUrl = "http://test.example.com" - private val apiKey = "test-api-key" - - /** - * テスト用HttpClientを作成する - */ - private fun createMockHttpClient(engine: MockEngine): HttpClient = - HttpClient(engine) { - install(ContentNegotiation) { - json(json) - } - } - - /** - * テスト用RepositoryClientを作成する - */ - private fun createMockRepositoryClient(engine: MockEngine): RepositoryClient { - val httpClient = createMockHttpClient(engine) - return RepositoryClient(httpClient, baseUrl, apiKey) - } - - // ==================== getMessages() テスト ==================== - - @Test - fun `getMessages - success returns thread messages`() = - runTest { - // Arrange: スレッドメッセージ一覧レスポンスのモック設定 - val expectedResponse = - ThreadMessagesResponse( - threadId = "thread-123", - messages = - listOf( - ThreadMessage( - messageId = "msg-1", - threadId = "thread-123", - userId = "user-456", - role = MessageRole.USER, - content = "Hello", - createdAt = "2025-01-01T00:00:00Z", - modelName = null, - ), - ThreadMessage( - messageId = "msg-2", - threadId = "thread-123", - userId = "user-456", - role = MessageRole.ASSISTANT, - content = "Hi there!", - createdAt = "2025-01-01T00:01:00Z", - modelName = "gpt-4", - ), - ), - ) - val responseBody = json.encodeToString(expectedResponse) - - val mockEngine = - MockEngine { - // HTTPリクエストのアサーション - assertEquals(HttpMethod.Get, it.method) - assertEquals("$baseUrl/v1/threads/thread-123/messages", it.url.toString()) - assertEquals(apiKey, it.headers["X-API-Key"]) - - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = MessageRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: メッセージ一覧を収集 - val results = mutableListOf() - repository.getMessages("thread-123").collect { result -> - if (result.isSuccess) { - results.add(result.getOrNull()!!) - } - } - - // Assert: 結果の検証 - assertEquals(1, results.size) - val actual = results[0] - assertEquals("thread-123", actual.threadId) - assertEquals(2, actual.messages.size) - assertEquals("msg-1", actual.messages[0].messageId) - assertEquals(MessageRole.USER, actual.messages[0].role) - assertEquals("Hello", actual.messages[0].content) - assertEquals("msg-2", actual.messages[1].messageId) - assertEquals(MessageRole.ASSISTANT, actual.messages[1].role) - assertEquals("Hi there!", actual.messages[1].content) - assertEquals("gpt-4", actual.messages[1].modelName) - } - - @Test - fun `getMessages - HTTP 404 Not Found`() = - runTest { - // Arrange: 404 Not Foundのモック設定 - val mockEngine = - MockEngine { - assertEquals(HttpMethod.Get, it.method) - assertEquals("$baseUrl/v1/threads/nonexistent/messages", it.url.toString()) - - respond( - content = """{"detail": "Thread not found"}""", - status = HttpStatusCode.NotFound, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = MessageRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: エラー結果を収集 - val errors = mutableListOf() - repository.getMessages("nonexistent").collect { result -> - if (result.isFailure) { - result.exceptionOrNull()?.let { - assertIs(it) - errors.add(it) - } - } - } - - // Assert - assertTrue(errors.isNotEmpty()) - val error = assertIs(errors.first()) - assertEquals(404, error.code) - // detail にはレスポンスボディ全体が含まれる - assertTrue(error.detail?.contains("Thread not found") == true) - } - - @Test - fun `getMessages - empty message list returns empty response`() = - runTest { - // Arrange: 空のメッセージリストのモック設定 - val expectedResponse = - ThreadMessagesResponse( - threadId = "thread-empty", - messages = emptyList(), - ) - val responseBody = json.encodeToString(expectedResponse) - - val mockEngine = - MockEngine { - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = MessageRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act - val results = mutableListOf() - repository.getMessages("thread-empty").collect { result -> - if (result.isSuccess) { - results.add(result.getOrNull()!!) - } - } - - // Assert - assertEquals(1, results.size) - val actual = results[0] - assertEquals("thread-empty", actual.threadId) - assertTrue(actual.messages.isEmpty()) - } - - @Test - fun `getMessages - HTTP 500 Internal Server Error`() = - runTest { - // Arrange: 500 Internal Server Errorのモック設定 - val mockEngine = - MockEngine { - respond( - content = """{"detail": "Internal server error"}""", - status = HttpStatusCode.InternalServerError, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = MessageRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: エラー結果を収集 - val errors = mutableListOf() - repository.getMessages("thread-123").collect { result -> - if (result.isFailure) { - result.exceptionOrNull()?.let { - assertIs(it) - errors.add(it) - } - } - } - - // Assert - assertTrue(errors.isNotEmpty()) - val error = assertIs(errors.first()) - assertEquals(500, error.code) - } - - @Test - fun `getMessages - cache returns same result on second call`() = - runTest { - // Arrange: キャッシュ動作の検証 - val expectedResponse = - ThreadMessagesResponse( - threadId = "thread-cache", - messages = - listOf( - ThreadMessage( - messageId = "msg-cache", - threadId = "thread-cache", - userId = "user-789", - role = MessageRole.USER, - content = "Cached message", - createdAt = "2025-01-01T00:00:00Z", - modelName = null, - ), - ), - ) - val responseBody = json.encodeToString(expectedResponse) - - var requestCount = 0 - val mockEngine = - MockEngine { - requestCount++ - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = MessageRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: 同じスレッドのメッセージを2回取得 - val firstResults = mutableListOf() - repository.getMessages("thread-cache").collect { result -> - if (result.isSuccess) { - firstResults.add(result.getOrNull()!!) - } - } - - val secondResults = mutableListOf() - repository.getMessages("thread-cache").collect { result -> - if (result.isSuccess) { - secondResults.add(result.getOrNull()!!) - } - } - - // Assert: 2回目はキャッシュから返されるのでHTTPリクエストは1回のみ - assertEquals(1, requestCount) - assertEquals(1, firstResults.size) - assertEquals(1, secondResults.size) - assertEquals(firstResults[0].threadId, secondResults[0].threadId) - assertEquals(1, firstResults[0].messages.size) - } -} diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ThreadRepositoryImplTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ThreadRepositoryImplTest.kt deleted file mode 100644 index 1749296..0000000 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/chat/ThreadRepositoryImplTest.kt +++ /dev/null @@ -1,417 +0,0 @@ -package dev.plexus.shared.features.chat - -import dev.plexus.shared.core.data.repository.ThreadRepositoryImpl -import dev.plexus.shared.core.data.repository.internal.RepositoryClient -import dev.plexus.shared.core.domain.model.Thread -import dev.plexus.shared.core.domain.model.ThreadListResponse -import dev.plexus.shared.core.domain.repository.ApiError -import io.ktor.client.HttpClient -import io.ktor.client.engine.mock.MockEngine -import io.ktor.client.engine.mock.respond -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.http.headersOf -import io.ktor.serialization.kotlinx.json.json -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertIs -import kotlin.test.assertTrue - -/** - * ThreadRepositoryImplのテスト - * - * Ktor MockEngineを使用してHTTPリクエスト/レスポンスをテストします。 - */ -class ThreadRepositoryImplTest { - private val json = - Json { - ignoreUnknownKeys = true - isLenient = true - } - - private val baseUrl = "http://test.example.com" - private val apiKey = "test-api-key" - - /** - * テスト用HttpClientを作成する - */ - private fun createMockHttpClient(engine: MockEngine): HttpClient = - HttpClient(engine) { - install(ContentNegotiation) { - json(json) - } - } - - /** - * テスト用RepositoryClientを作成する - */ - private fun createMockRepositoryClient(engine: MockEngine): RepositoryClient { - val httpClient = createMockHttpClient(engine) - return RepositoryClient(httpClient, baseUrl, apiKey) - } - - // ==================== getThreads() テスト ==================== - - @Test - fun `getThreads - success returns thread list`() = - runTest { - // Arrange: スレッド一覧レスポンスのモック設定 - val expectedResponse = - ThreadListResponse( - threads = - listOf( - Thread( - threadId = "thread-1", - userId = "user-123", - title = "Thread 1", - preview = "Preview 1", - messageCount = 5, - createdAt = "2025-01-01T00:00:00Z", - lastMessageAt = "2025-01-01T01:00:00Z", - ), - Thread( - threadId = "thread-2", - userId = "user-123", - title = "Thread 2", - preview = null, - messageCount = 3, - createdAt = "2025-01-02T00:00:00Z", - lastMessageAt = "2025-01-02T00:30:00Z", - ), - ), - total = 2, - limit = 20, - offset = 0, - ) - val responseBody = json.encodeToString(expectedResponse) - - val mockEngine = - MockEngine { - // HTTPリクエストのアサーション - assertEquals(HttpMethod.Get, it.method) - assertEquals("/v1/threads", it.url.encodedPath) - assertEquals("20", it.url.parameters["limit"]) - assertEquals("0", it.url.parameters["offset"]) - assertEquals(apiKey, it.headers["X-API-Key"]) - - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = ThreadRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: スレッド一覧を収集 - val results = mutableListOf() - repository.getThreads(limit = 20, offset = 0).collect { result -> - if (result.isSuccess) { - results.add(result.getOrNull()!!) - } - } - - // Assert: 結果の検証 - assertEquals(1, results.size) - val actual = results[0] - assertEquals(2, actual.threads.size) - assertEquals("thread-1", actual.threads[0].threadId) - assertEquals("Thread 1", actual.threads[0].title) - assertEquals("thread-2", actual.threads[1].threadId) - assertEquals(2, actual.total) - assertEquals(20, actual.limit) - assertEquals(0, actual.offset) - } - - @Test - fun `getThreads - pagination with limit and offset`() = - runTest { - // Arrange: ページネーションのモック設定 - val expectedResponse = - ThreadListResponse( - threads = - listOf( - Thread( - threadId = "thread-3", - userId = "user-123", - title = "Thread 3", - preview = null, - messageCount = 1, - createdAt = "2025-01-03T00:00:00Z", - lastMessageAt = "2025-01-03T00:00:00Z", - ), - ), - total = 3, - limit = 10, - offset = 2, - ) - val responseBody = json.encodeToString(expectedResponse) - - val mockEngine = - MockEngine { - assertEquals(HttpMethod.Get, it.method) - assertEquals("10", it.url.parameters["limit"]) - assertEquals("2", it.url.parameters["offset"]) - - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = ThreadRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act - val results = mutableListOf() - repository.getThreads(limit = 10, offset = 2).collect { result -> - if (result.isSuccess) { - results.add(result.getOrNull()!!) - } - } - - // Assert - assertEquals(1, results.size) - val actual = results[0] - assertEquals(1, actual.threads.size) - assertEquals("thread-3", actual.threads[0].threadId) - assertEquals(3, actual.total) - assertEquals(10, actual.limit) - assertEquals(2, actual.offset) - } - - @Test - fun `getThreads - empty list returns empty response`() = - runTest { - // Arrange: 空のスレッドリストのモック設定 - val expectedResponse = - ThreadListResponse( - threads = emptyList(), - total = 0, - limit = 20, - offset = 0, - ) - val responseBody = json.encodeToString(expectedResponse) - - val mockEngine = - MockEngine { - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = ThreadRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act - val results = mutableListOf() - repository.getThreads(limit = 20, offset = 0).collect { result -> - if (result.isSuccess) { - results.add(result.getOrNull()!!) - } - } - - // Assert - assertEquals(1, results.size) - val actual = results[0] - assertTrue(actual.threads.isEmpty()) - assertEquals(0, actual.total) - } - - @Test - fun `getThreads - HTTP 500 returns HttpError`() = - runTest { - // Arrange: 500 Internal Server Errorのモック設定 - val mockEngine = - MockEngine { - respond( - content = """{"detail": "Internal server error"}""", - status = HttpStatusCode.InternalServerError, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = ThreadRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: エラー結果を収集 - val errors = mutableListOf() - repository.getThreads(limit = 20, offset = 0).collect { result -> - if (result.isFailure) { - result.exceptionOrNull()?.let { errors.add(assertIs(it)) } - } - } - - // Assert - assertTrue(errors.isNotEmpty()) - val error = assertIs(errors.first()) - assertEquals(500, error.code) - } - - // ==================== getThread() テスト ==================== - - @Test - fun `getThread - success returns thread details`() = - runTest { - // Arrange: スレッド詳細レスポンスのモック設定 - val expectedResponse = - Thread( - threadId = "thread-123", - userId = "user-456", - title = "Test Thread", - preview = "This is a test thread", - messageCount = 10, - createdAt = "2025-01-01T00:00:00Z", - lastMessageAt = "2025-01-01T02:00:00Z", - ) - val responseBody = json.encodeToString(expectedResponse) - - val mockEngine = - MockEngine { - // HTTPリクエストのアサーション - assertEquals(HttpMethod.Get, it.method) - assertEquals("/v1/threads/thread-123", it.url.encodedPath) - assertEquals(apiKey, it.headers["X-API-Key"]) - - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = ThreadRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: スレッド詳細を収集 - val results = mutableListOf() - repository.getThread("thread-123").collect { result -> - if (result.isSuccess) { - results.add(result.getOrNull()!!) - } - } - - // Assert: 結果の検証 - assertEquals(1, results.size) - val actual = results[0] - assertEquals("thread-123", actual.threadId) - assertEquals("user-456", actual.userId) - assertEquals("Test Thread", actual.title) - assertEquals("This is a test thread", actual.preview) - assertEquals(10, actual.messageCount) - } - - @Test - fun `getThread - HTTP 404 Not Found`() = - runTest { - // Arrange: 404 Not Foundのモック設定 - val mockEngine = - MockEngine { - assertEquals(HttpMethod.Get, it.method) - assertEquals("/v1/threads/nonexistent", it.url.encodedPath) - - respond( - content = """{"detail": "Thread not found"}""", - status = HttpStatusCode.NotFound, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = ThreadRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: エラー結果を収集 - val errors = mutableListOf() - repository.getThread("nonexistent").collect { result -> - if (result.isFailure) { - result.exceptionOrNull()?.let { errors.add(assertIs(it)) } - } - } - - // Assert - assertTrue(errors.isNotEmpty()) - val error = assertIs(errors.first()) - assertEquals(404, error.code) - // detail にはレスポンスボディ全体が含まれる - assertTrue(error.detail?.contains("Thread not found") == true) - } - - @Test - fun `getThread - cache returns same result on second call`() = - runTest { - // Arrange: キャッシュ動作の検証 - val expectedResponse = - Thread( - threadId = "thread-cache", - userId = "user-789", - title = "Cache Test", - preview = null, - messageCount = 1, - createdAt = "2025-01-01T00:00:00Z", - lastMessageAt = "2025-01-01T00:00:00Z", - ) - val responseBody = json.encodeToString(expectedResponse) - - var requestCount = 0 - val mockEngine = - MockEngine { - requestCount++ - respond( - content = responseBody, - status = HttpStatusCode.OK, - headers = headersOf("Content-Type", "application/json"), - ) - } - - val repository = ThreadRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act: 同じスレッドを2回取得 - val firstResults = mutableListOf() - repository.getThread("thread-cache").collect { result -> - if (result.isSuccess) { - firstResults.add(result.getOrNull()!!) - } - } - - val secondResults = mutableListOf() - repository.getThread("thread-cache").collect { result -> - if (result.isSuccess) { - secondResults.add(result.getOrNull()!!) - } - } - - // Assert: 2回目はキャッシュから返されるのでHTTPリクエストは1回のみ - assertEquals(1, requestCount) - assertEquals(1, firstResults.size) - assertEquals(1, secondResults.size) - assertEquals(firstResults[0].threadId, secondResults[0].threadId) - } - - // ==================== createThread() テスト ==================== - - @Test - fun `createThread - returns 501 Not Implemented`() = - runTest { - // Arrange: createThreadはまだ実装されていないため501を返す - - val mockEngine = - MockEngine { - // このテストではHTTPリクエストが送信されないことを検証 - throw Exception("HTTP request should not be sent") - } - - val repository = ThreadRepositoryImpl(createMockRepositoryClient(mockEngine)) - - // Act - val result = repository.createThread("New Thread") - - // Assert: 501 Not Implemented エラーを返す - assertTrue(result.isFailure) - val error = assertIs(result.exceptionOrNull()) - assertEquals(501, error.code) - assertEquals("Not Implemented", error.errorMessage) - assertEquals("Thread creation is not yet supported", error.detail) - } -} From d735e3430d17618d86f7365a467647bc769eac65 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:38:06 +0000 Subject: [PATCH 08/14] fix: restore gateway runtime dependencies --- gateway/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gateway/pyproject.toml b/gateway/pyproject.toml index df628de..d5c8d8f 100644 --- a/gateway/pyproject.toml +++ b/gateway/pyproject.toml @@ -4,6 +4,8 @@ version = "0.1.0" description = "Terminal Gateway for mobile tmux access" requires-python = ">=3.12" dependencies = [ + "starlette>=0.46.0", + "anyio>=4.8.0", "uvicorn[standard]>=0.34.0", "websockets>=14.0", "pydantic>=2.10.0", From bbfb1215d0765d831767568cd61cfc9cacf6d7b5 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:38:24 +0000 Subject: [PATCH 09/14] refactor: simplify navigation for terminal runtime --- .../kotlin/dev/plexus/shared/di/AppModule.kt | 37 -------- .../features/navigation/MainNavigationHost.kt | 5 +- .../shared/features/navigation/MainView.kt | 1 - .../features/navigation/MainViewTransition.kt | 10 +- .../navigation/SwipeNavigationContainer.kt | 19 +--- .../shared/features/sidebar/SidebarFooter.kt | 59 ------------ .../shared/features/sidebar/SidebarHeader.kt | 77 ---------------- .../shared/features/sidebar/SidebarScreen.kt | 92 ++++--------------- .../kotlin/dev/plexus/shared/di/KoinDiTest.kt | 25 ----- 9 files changed, 22 insertions(+), 303 deletions(-) delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarHeader.kt diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt index 6ebc3ba..0d3cf78 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt @@ -2,16 +2,10 @@ package dev.plexus.shared.di import dev.plexus.shared.cache.DiskCache import dev.plexus.shared.cache.DiskCacheContext -import dev.plexus.shared.core.data.repository.ChatRepositoryImpl -import dev.plexus.shared.core.data.repository.MessageRepositoryImpl import dev.plexus.shared.core.data.repository.SystemPromptRepositoryImpl -import dev.plexus.shared.core.data.repository.ThreadRepositoryImpl import dev.plexus.shared.core.data.repository.internal.InMemoryCache import dev.plexus.shared.core.data.repository.internal.RepositoryClient -import dev.plexus.shared.core.domain.repository.ChatRepository -import dev.plexus.shared.core.domain.repository.MessageRepository import dev.plexus.shared.core.domain.repository.SystemPromptRepository -import dev.plexus.shared.core.domain.repository.ThreadRepository import dev.plexus.shared.core.network.HttpClientConfig import dev.plexus.shared.core.network.HttpClientConfigProvider import dev.plexus.shared.core.network.provideHttpClient @@ -22,7 +16,6 @@ import dev.plexus.shared.core.platform.getDefaultBaseUrl import dev.plexus.shared.core.platform.normalizeBaseUrl import dev.plexus.shared.core.settings.ThemeRepository import dev.plexus.shared.core.settings.ThemeRepositoryImpl -import dev.plexus.shared.features.chat.ChatScreenModel import dev.plexus.shared.features.settings.SettingsScreenModel import dev.plexus.shared.features.systemprompt.SystemPromptEditorScreenModel import dev.plexus.shared.features.terminal.agentlist.AgentListScreenModel @@ -85,13 +78,6 @@ val appModule = single> { InMemoryCache() } // === Repositories === - single { - ThreadRepositoryImpl( - repositoryClient = get(qualifier = named("BackendClient")), - diskCache = getOrNull(), - ) - } - single { SystemPromptRepositoryImpl( repositoryClient = get(qualifier = named("BackendClient")), @@ -99,34 +85,11 @@ val appModule = ) } - single { - MessageRepositoryImpl( - repositoryClient = get(qualifier = named("BackendClient")), - diskCache = getOrNull(), - ) - } - - single { - ChatRepositoryImpl( - repositoryClient = get(qualifier = named("BackendClient")), - httpClientConfig = get(), - ) - } - single { ThemeRepositoryImpl(preferences = get()) } // === ScreenModels === - single { - ChatScreenModel( - threadRepository = get(), - messageRepository = get(), - chatRepository = get(), - preferences = get(), - ) - } - factory { AgentListScreenModel( terminalRepository = get(), diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainNavigationHost.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainNavigationHost.kt index b371f05..d007c51 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainNavigationHost.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainNavigationHost.kt @@ -9,8 +9,7 @@ import androidx.compose.runtime.Composable * * @param activeView 現在のアクティブなView * @param onSwipeToSidebar スワイプでサイドバーへ遷移するコールバック - * @param onSwipeToTerminal スワイプでターミナルへ遷移するコールバック - * @param onSwipeToChat スワイプでチャットへ遷移するコールバック + * @param onSwipeToTerminal スワイプでターミナル一覧へ遷移するコールバック * @param content 表示するコンテンツ */ @Composable @@ -18,14 +17,12 @@ fun MainNavigationHost( activeView: MainView, onSwipeToSidebar: () -> Unit, onSwipeToTerminal: () -> Unit, - onSwipeToChat: () -> Unit, content: @Composable (MainView) -> Unit, ) { SwipeNavigationContainer( activeView = activeView, onSwipeToSidebar = onSwipeToSidebar, onSwipeToTerminal = onSwipeToTerminal, - onSwipeToChat = onSwipeToChat, ) { MainViewTransition( activeView = activeView, diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt index d4373a4..90ec537 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt @@ -4,7 +4,6 @@ package dev.plexus.shared.features.navigation * メインのViewを表す列挙型 */ enum class MainView { - Chat, SystemPrompt, Settings, Terminal, diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainViewTransition.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainViewTransition.kt index e89c110..3238462 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainViewTransition.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainViewTransition.kt @@ -23,16 +23,10 @@ fun MainViewTransition( targetState = activeView, transitionSpec = { when { - initialState == MainView.Chat && targetState == MainView.Terminal -> { + initialState == MainView.Terminal && targetState == MainView.TerminalSession -> { slideInHorizontally { fullWidth -> fullWidth } togetherWith slideOutHorizontally { fullWidth -> -fullWidth } } - initialState == MainView.Chat && targetState == MainView.TerminalSession -> { - slideInHorizontally { fullWidth -> fullWidth } togetherWith slideOutHorizontally { fullWidth -> -fullWidth } - } - initialState == MainView.Terminal && targetState == MainView.Chat -> { - slideInHorizontally { fullWidth -> -fullWidth } togetherWith slideOutHorizontally { fullWidth -> fullWidth } - } - initialState == MainView.TerminalSession && targetState == MainView.Chat -> { + initialState == MainView.TerminalSession && targetState == MainView.Terminal -> { slideInHorizontally { fullWidth -> -fullWidth } togetherWith slideOutHorizontally { fullWidth -> fullWidth } } else -> EnterTransition.None togetherWith ExitTransition.None diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/SwipeNavigationContainer.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/SwipeNavigationContainer.kt index e3b77fc..fa8a8c0 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/SwipeNavigationContainer.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/SwipeNavigationContainer.kt @@ -13,8 +13,7 @@ import androidx.compose.ui.input.pointer.pointerInput * * @param activeView 現在のアクティブなView * @param onSwipeToSidebar スワイプでサイドバーへ遷移するコールバック - * @param onSwipeToTerminal スワイプでターミナルへ遷移するコールバック - * @param onSwipeToChat スワイプでチャットへ遷移するコールバック + * @param onSwipeToTerminal スワイプでターミナル一覧へ遷移するコールバック * @param content 表示するコンテンツ */ @Composable @@ -22,7 +21,6 @@ fun SwipeNavigationContainer( activeView: MainView, onSwipeToSidebar: () -> Unit, onSwipeToTerminal: () -> Unit, - onSwipeToChat: () -> Unit, content: @Composable BoxScope.() -> Unit, ) { Box( @@ -47,26 +45,15 @@ fun SwipeNavigationContainer( val swipeThreshold = size.width * 0.2f when { - activeView == MainView.Chat && accumulatedDragX >= swipeThreshold -> { + activeView == MainView.Terminal && accumulatedDragX >= swipeThreshold -> { handled = true onSwipeToSidebar() change.consume() } - activeView == MainView.Chat && accumulatedDragX <= -swipeThreshold -> { - handled = true - onSwipeToTerminal() - change.consume() - } - activeView == MainView.Terminal && - (accumulatedDragX >= swipeThreshold || accumulatedDragX <= -swipeThreshold) -> { - handled = true - onSwipeToChat() - change.consume() - } activeView == MainView.TerminalSession && (accumulatedDragX >= swipeThreshold || accumulatedDragX <= -swipeThreshold) -> { handled = true - onSwipeToChat() + onSwipeToTerminal() change.consume() } } diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt index 3f83086..7b14f5b 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt @@ -4,14 +4,11 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Computer import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Tune @@ -22,13 +19,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow import dev.plexus.shared.core.ui.common.testTagResourceId import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @Composable fun SidebarFooter( - onNewChatClick: () -> Unit, onSettingsClick: () -> Unit, onTerminalClick: () -> Unit, onSystemPromptClick: () -> Unit, @@ -65,14 +60,6 @@ fun SidebarFooter( testTag = "terminal_button", modifier = Modifier.weight(1f), ) - - FooterIconWithLabelButton( - icon = Icons.Outlined.Add, - label = "New", - onClick = onNewChatClick, - testTag = "new_chat_button", - modifier = Modifier.weight(1f), - ) } } @@ -112,49 +99,3 @@ private fun FooterIconButton( } } } - -@Composable -private fun FooterIconWithLabelButton( - icon: androidx.compose.ui.graphics.vector.ImageVector, - label: String, - onClick: () -> Unit, - testTag: String, - modifier: Modifier = Modifier, -) { - val dimens = PlexusThemeTokens.dimens - val shapes = PlexusThemeTokens.shapes - - Surface( - onClick = onClick, - shape = shapes.radiusLg, - color = MaterialTheme.colorScheme.surfaceContainerLow, - border = BorderStroke(dimens.borderWidthThin, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - tonalElevation = dimens.zero, - shadowElevation = dimens.zero, - modifier = - modifier - .height(dimens.space36) - .testTagResourceId(testTag), - ) { - Row( - modifier = Modifier.padding(horizontal = dimens.space12), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Icon( - imageVector = icon, - contentDescription = null, - modifier = Modifier.size(dimens.iconSizeSmall), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Spacer(modifier = Modifier.width(dimens.space4)) - Text( - text = label, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarHeader.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarHeader.kt deleted file mode 100644 index 7316234..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarHeader.kt +++ /dev/null @@ -1,77 +0,0 @@ -package dev.plexus.shared.features.sidebar - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material.icons.outlined.Computer -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import dev.plexus.shared.core.ui.common.CompactActionButton -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens - -/** - * サイドバーのヘッダーコンポーネント - * - * 新しいチャット、設定、ターミナルへのボタンを含むヘッダー。 - * - * @param onNewChatClick 新規チャットボタンクリックコールバック - * @param onSettingsClick 設定ボタンクリックコールバック - * @param onTerminalClick ターミナルボタンクリックコールバック - */ -@Composable -fun SidebarHeader( - onNewChatClick: () -> Unit, - onSettingsClick: () -> Unit = {}, - onTerminalClick: () -> Unit = {}, -) { - val dimens = PlexusThemeTokens.dimens - - Row( - modifier = - Modifier - .fillMaxWidth() - .padding(dimens.space16), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = "History", - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Spacer(modifier = Modifier.weight(1f)) - - CompactActionButton( - onClick = onSettingsClick, - icon = Icons.Default.Settings, - contentDescription = "Settings", - testTag = "settings_button", - ) - - Spacer(modifier = Modifier.width(dimens.space8)) - - CompactActionButton( - onClick = onTerminalClick, - icon = Icons.Outlined.Computer, - contentDescription = "Terminal", - testTag = "terminal_button", - ) - - Spacer(modifier = Modifier.width(dimens.space8)) - - CompactActionButton( - onClick = onNewChatClick, - icon = Icons.Default.Add, - contentDescription = null, - text = "New", - testTag = "new_chat_button", - ) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt index c18c2b4..3d8d99a 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt @@ -14,7 +14,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -26,16 +25,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.koin.koinScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator import dev.plexus.shared.core.platform.PlatformPreferences import dev.plexus.shared.core.platform.PlatformPrefsDefaults import dev.plexus.shared.core.platform.PlatformPrefsKeys import dev.plexus.shared.core.ui.theme.PlexusThemeTokens -import dev.plexus.shared.features.chat.ChatScreen -import dev.plexus.shared.features.chat.ChatScreenModel -import dev.plexus.shared.features.chat.ChatState -import dev.plexus.shared.features.chat.threads.ThreadList import dev.plexus.shared.features.navigation.MainNavigationHost import dev.plexus.shared.features.navigation.MainView import dev.plexus.shared.features.settings.SettingsScreen @@ -49,18 +42,15 @@ import org.koin.compose.koinInject /** * サイドバー画面 * - * ナビゲーションコントローラーと画面コンテンツを管理するメイン画面。 + * ターミナル中心のナビゲーションと画面コンテンツを管理するメイン画面。 */ class SidebarScreen : Screen { @Composable override fun Content() { val dimens = PlexusThemeTokens.dimens - val navigator = requireNotNull(LocalNavigator.current) - val screenModel = koinScreenModel() - val state: ChatState by screenModel.state.collectAsState() val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val scope = rememberCoroutineScope() - var activeView by rememberSaveable { mutableStateOf(MainView.Chat) } + var activeView by rememberSaveable { mutableStateOf(MainView.Terminal) } val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current @@ -69,23 +59,6 @@ class SidebarScreen : Screen { focusManager.clearFocus(force = true) } - val chatScreen = - remember(drawerState, scope, screenModel) { - ChatScreen( - onOpenSidebar = { - dismissKeyboard() - scope.launch { drawerState.open() } - }, - onOpenTerminal = { - activeView = MainView.Terminal - }, - onNewChat = { - activeView = MainView.Chat - screenModel.clearThreadSelection() - }, - ) - } - LaunchedEffect(drawerState) { snapshotFlow { drawerState.targetValue } .collect { targetValue -> @@ -98,9 +71,9 @@ class SidebarScreen : Screen { val preferences = koinInject() val agentListScreen = - remember(navigator) { + remember { AgentListScreen( - onSessionSelected = { sessionId -> + onSessionSelected = { activeView = MainView.TerminalSession }, onOpenGatewaySettings = { @@ -120,45 +93,23 @@ class SidebarScreen : Screen { .padding(horizontal = dimens.space16, vertical = dimens.space12), ) { Text( - text = "History", + text = "Plexus", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) + Spacer(modifier = Modifier.height(dimens.space4)) + Text( + text = "tmux-centered mobile terminal runtime", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } HorizontalDivider() - ThreadList( - threads = state.threadList.threads, - selectedThreadId = state.threadList.selectedThread?.threadId, - isLoading = state.threadList.isLoading, - isLoadingMore = state.threadList.isLoadingMore, - hasMore = state.threadList.hasMore, - error = state.threadList.error, - onThreadClick = { threadId -> - activeView = MainView.Chat - screenModel.selectThread(threadId) - scope.launch { drawerState.close() } - }, - onRefresh = { - screenModel.loadThreads() - }, - onLoadMore = { - screenModel.loadMoreThreads() - }, - modifier = Modifier.weight(1f), - ) - - HorizontalDivider() - - Spacer(modifier = Modifier.height(dimens.space8)) + Spacer(modifier = Modifier.weight(1f)) SidebarFooter( - onNewChatClick = { - activeView = MainView.Chat - screenModel.clearThreadSelection() - scope.launch { drawerState.close() } - }, onSettingsClick = { activeView = MainView.Settings scope.launch { drawerState.close() } @@ -176,7 +127,7 @@ class SidebarScreen : Screen { Spacer(modifier = Modifier.height(dimens.space12)) } }, - gesturesEnabled = activeView == MainView.Chat || activeView == MainView.TerminalSession, + gesturesEnabled = activeView == MainView.Terminal || activeView == MainView.TerminalSession, ) { MainNavigationHost( activeView = activeView, @@ -185,26 +136,15 @@ class SidebarScreen : Screen { scope.launch { drawerState.open() } }, onSwipeToTerminal = { - val lastSessionId = - preferences.getString( - PlatformPrefsKeys.KEY_LAST_TERMINAL_SESSION, - PlatformPrefsDefaults.DEFAULT_LAST_TERMINAL_SESSION, - ) - if (lastSessionId.isNotBlank()) { - activeView = MainView.TerminalSession - } else { - activeView = MainView.Terminal - } + activeView = MainView.Terminal }, - onSwipeToChat = { activeView = MainView.Chat }, ) { targetView -> when (targetView) { - MainView.Chat -> chatScreen.Content() MainView.SystemPrompt -> { val promptScreen = remember { SystemPromptEditorScreen( - onBack = { activeView = MainView.Chat }, + onBack = { activeView = MainView.Terminal }, ) } promptScreen.Content() @@ -214,7 +154,7 @@ class SidebarScreen : Screen { val settingsScreen = remember { SettingsScreen( - onBack = { activeView = MainView.Chat }, + onBack = { activeView = MainView.Terminal }, ) } settingsScreen.Content() diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/di/KoinDiTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/di/KoinDiTest.kt index 3db423c..075273a 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/di/KoinDiTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/di/KoinDiTest.kt @@ -3,7 +3,6 @@ package dev.plexus.shared.di import dev.plexus.shared.core.data.repository.MessageRepositoryImpl import dev.plexus.shared.core.data.repository.ThreadRepositoryImpl import dev.plexus.shared.core.data.repository.internal.RepositoryClient -import dev.plexus.shared.core.domain.repository.ChatRepository import dev.plexus.shared.core.domain.repository.MessageRepository import dev.plexus.shared.core.domain.repository.ThreadRepository import io.ktor.client.HttpClient @@ -108,30 +107,6 @@ class KoinDiTest : KoinTest { } } - @Test - fun `ChatRepository should be injectable`() { - // Arrange - // Koin module is configured - - try { - // Act - startKoin { - modules(appModule) - } - - val repository: ChatRepository by inject() - - // Assert - assertNotNull(repository) - } catch (e: Exception) { - println("Error injecting ChatRepository: ${e.message}") - e.printStackTrace() - throw e - } finally { - stopKoin() - } - } - @Test fun `HttpClient should be injectable`() { // Arrange From 11e52c4865984497f027628d8b833c0b4db2bbec Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:44:39 +0000 Subject: [PATCH 10/14] refactor: default app theme to dark --- .../kotlin/dev/plexus/app/MainActivity.kt | 8 +------- .../core/platform/PlatformPreferences.kt | 2 +- .../plexus/shared/core/settings/AppTheme.kt | 5 ++--- .../shared/core/settings/ThemeRepository.kt | 2 +- .../shared/features/settings/SettingsState.kt | 4 ++-- .../terminal/session/TerminalScreen.kt | 9 +-------- .../shared/core/settings/AppThemeTest.kt | 19 ++----------------- .../core/settings/ThemeRepositoryTest.kt | 13 ++++++------- .../features/settings/SettingsStateTest.kt | 4 ++-- 9 files changed, 18 insertions(+), 48 deletions(-) diff --git a/frontend/androidApp/src/main/kotlin/dev/plexus/app/MainActivity.kt b/frontend/androidApp/src/main/kotlin/dev/plexus/app/MainActivity.kt index be2d867..ba2d85a 100644 --- a/frontend/androidApp/src/main/kotlin/dev/plexus/app/MainActivity.kt +++ b/frontend/androidApp/src/main/kotlin/dev/plexus/app/MainActivity.kt @@ -8,7 +8,6 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.lifecycle.Lifecycle @@ -89,12 +88,7 @@ class MainActivity : ComponentActivity() { val themeRepository = koinInject() val theme by themeRepository.theme.collectAsState() - val darkTheme = - when (theme) { - AppTheme.LIGHT -> false - AppTheme.DARK -> true - AppTheme.SYSTEM -> isSystemInDarkTheme() - } + val darkTheme = theme == AppTheme.DARK PlexusTheme(darkTheme = darkTheme) { Navigator(SidebarScreen()) { diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.kt index 1aeabbc..f0f1df2 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/platform/PlatformPreferences.kt @@ -41,7 +41,7 @@ object PlatformPrefsValues { } object PlatformPrefsDefaults { - const val DEFAULT_THEME = PlatformPrefsValues.THEME_LIGHT + const val DEFAULT_THEME = PlatformPrefsValues.THEME_DARK const val DEFAULT_API_URL = "" const val DEFAULT_API_KEY = "" const val DEFAULT_GATEWAY_API_URL = "" diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/AppTheme.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/AppTheme.kt index 0480af8..5f85778 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/AppTheme.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/AppTheme.kt @@ -8,7 +8,6 @@ enum class AppTheme( ) { LIGHT("Light"), DARK("Dark"), - SYSTEM("System"), } /** @@ -17,8 +16,8 @@ enum class AppTheme( fun String.toAppTheme(): AppTheme = when (this.lowercase()) { "dark" -> AppTheme.DARK - "system" -> AppTheme.SYSTEM - else -> AppTheme.LIGHT + "light" -> AppTheme.LIGHT + else -> AppTheme.DARK } /** diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/ThemeRepository.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/ThemeRepository.kt index 23bc251..e51db39 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/ThemeRepository.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/core/settings/ThemeRepository.kt @@ -31,7 +31,7 @@ interface ThemeRepository { class ThemeRepositoryImpl( private val preferences: PlatformPreferences, ) : ThemeRepository { - private val _theme = MutableStateFlow(AppTheme.SYSTEM) + private val _theme = MutableStateFlow(AppTheme.DARK) override val theme: StateFlow = _theme.asStateFlow() private val scope = CoroutineScope(Dispatchers.Default) diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt index a86aeae..46fa10a 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt @@ -5,14 +5,14 @@ import dev.plexus.shared.core.settings.AppTheme /** * アプリケーション設定画面のUI状態 * - * @property selectedTheme 選択中のテーマ(LIGHT/DARK/SYSTEM) + * @property selectedTheme 選択中のテーマ(LIGHT/DARK) * @property inputUrl 入力されたAPI URL * @property inputKey 入力されたAPI Key * @property isSaving 保存処理中かどうか */ data class SettingsState( - val selectedTheme: AppTheme = AppTheme.SYSTEM, + val selectedTheme: AppTheme = AppTheme.DARK, val inputUrl: String = "", val inputKey: String = "", val isSaving: Boolean = false, diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalScreen.kt index 7f66674..3c5b28c 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/session/TerminalScreen.kt @@ -1,7 +1,6 @@ package dev.plexus.shared.features.terminal.session import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -84,7 +83,6 @@ private fun TerminalContent( val themeRepository = koinInject() val terminalRepository = koinInject() val selectedTheme by themeRepository.theme.collectAsState() - val systemDarkTheme = isSystemInDarkTheme() val connectionState by webView.connectionState.collectAsState(initial = false) val keyboardState = rememberKeyboardState() val density = LocalDensity.current @@ -108,12 +106,7 @@ private fun TerminalContent( onError = { message -> voiceInputError = message.ifBlank { null } }, ) - val darkMode = - when (selectedTheme) { - AppTheme.DARK -> true - AppTheme.LIGHT -> false - AppTheme.SYSTEM -> systemDarkTheme - } + val darkMode = selectedTheme == AppTheme.DARK val terminalSettings = rememberTerminalSettings(agentId = agentId, preferences = preferences) diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/AppThemeTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/AppThemeTest.kt index b1298df..53d6034 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/AppThemeTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/AppThemeTest.kt @@ -8,17 +8,14 @@ class AppThemeTest { fun `toAppTheme should parse lowercase theme strings`() { // Arrange val inputDark = "dark" - val inputSystem = "system" val inputLight = "light" // Act val resultDark = inputDark.toAppTheme() - val resultSystem = inputSystem.toAppTheme() val resultLight = inputLight.toAppTheme() // Assert assertEquals(AppTheme.DARK, resultDark) - assertEquals(AppTheme.SYSTEM, resultSystem) assertEquals(AppTheme.LIGHT, resultLight) } @@ -26,17 +23,14 @@ class AppThemeTest { fun `toAppTheme should parse uppercase theme strings`() { // Arrange val inputDark = "DARK" - val inputSystem = "SYSTEM" val inputLight = "LIGHT" // Act val resultDark = inputDark.toAppTheme() - val resultSystem = inputSystem.toAppTheme() val resultLight = inputLight.toAppTheme() // Assert assertEquals(AppTheme.DARK, resultDark) - assertEquals(AppTheme.SYSTEM, resultSystem) assertEquals(AppTheme.LIGHT, resultLight) } @@ -44,22 +38,19 @@ class AppThemeTest { fun `toAppTheme should parse mixed case theme strings`() { // Arrange val inputDark = "DaRk" - val inputSystem = "SyStEm" val inputLight = "LiGhT" // Act val resultDark = inputDark.toAppTheme() - val resultSystem = inputSystem.toAppTheme() val resultLight = inputLight.toAppTheme() // Assert assertEquals(AppTheme.DARK, resultDark) - assertEquals(AppTheme.SYSTEM, resultSystem) assertEquals(AppTheme.LIGHT, resultLight) } @Test - fun `toAppTheme should return LIGHT as default for invalid inputs`() { + fun `toAppTheme should return DARK as default for invalid inputs`() { // Arrange val invalidInputs = listOf("invalid_theme", "", " ", "darkness") @@ -67,7 +58,7 @@ class AppThemeTest { val results = invalidInputs.map { it.toAppTheme() } // Assert - results.forEach { assertEquals(AppTheme.LIGHT, it) } + results.forEach { assertEquals(AppTheme.DARK, it) } } @Test @@ -75,17 +66,14 @@ class AppThemeTest { // Arrange val lightTheme = AppTheme.LIGHT val darkTheme = AppTheme.DARK - val systemTheme = AppTheme.SYSTEM // Act val lightString = lightTheme.toStorageString() val darkString = darkTheme.toStorageString() - val systemString = systemTheme.toStorageString() // Assert assertEquals("light", lightString) assertEquals("dark", darkString) - assertEquals("system", systemString) } @Test @@ -93,17 +81,14 @@ class AppThemeTest { // Arrange val lightTheme = AppTheme.LIGHT val darkTheme = AppTheme.DARK - val systemTheme = AppTheme.SYSTEM // Act val lightDisplayName = lightTheme.displayName val darkDisplayName = darkTheme.displayName - val systemDisplayName = systemTheme.displayName // Assert assertEquals("Light", lightDisplayName) assertEquals("Dark", darkDisplayName) - assertEquals("System", systemDisplayName) } @Test diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/ThemeRepositoryTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/ThemeRepositoryTest.kt index aed0810..2e3c703 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/ThemeRepositoryTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/core/settings/ThemeRepositoryTest.kt @@ -7,16 +7,16 @@ import kotlin.test.assertTrue class ThemeRepositoryTest { @Test - fun `default theme value should be LIGHT`() { + fun `default theme value should be DARK`() { // Assert - assertEquals(PlatformPrefsDefaults.DEFAULT_THEME, "light") + assertEquals(PlatformPrefsDefaults.DEFAULT_THEME, "dark") } @Test - fun `theme repository should have three variants`() { + fun `theme repository should have two variants`() { // Assert - assertEquals(3, AppTheme.entries.size) - assertEquals(setOf(AppTheme.LIGHT, AppTheme.DARK, AppTheme.SYSTEM), AppTheme.entries.toSet()) + assertEquals(2, AppTheme.entries.size) + assertEquals(setOf(AppTheme.LIGHT, AppTheme.DARK), AppTheme.entries.toSet()) } @Test @@ -25,9 +25,8 @@ class ThemeRepositoryTest { val themes = AppTheme.entries // Assert - assertEquals(3, themes.size) + assertEquals(2, themes.size) assertTrue(themes.contains(AppTheme.LIGHT)) assertTrue(themes.contains(AppTheme.DARK)) - assertTrue(themes.contains(AppTheme.SYSTEM)) } } diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt index effad3b..dc9b8c7 100644 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt +++ b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt @@ -11,10 +11,10 @@ import kotlin.test.assertFalse */ class SettingsStateTest { @Test - fun `SettingsState starts with SYSTEM theme`() { + fun `SettingsState starts with DARK theme`() { val state = SettingsState() - assertEquals(dev.plexus.shared.core.settings.AppTheme.SYSTEM, state.selectedTheme) + assertEquals(dev.plexus.shared.core.settings.AppTheme.DARK, state.selectedTheme) } @Test From 9ded3d6f4680c7b85f4ea80c3d3c0bcfbc57a2cb Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:48:18 +0000 Subject: [PATCH 11/14] refactor: consolidate app settings into gateway settings --- .../kotlin/dev/plexus/shared/di/AppModule.kt | 9 +- .../shared/features/navigation/MainView.kt | 1 - .../features/settings/SettingsEffect.kt | 15 -- .../features/settings/SettingsScreen.kt | 225 ------------------ .../features/settings/SettingsScreenModel.kt | 117 --------- .../shared/features/settings/SettingsState.kt | 19 -- .../shared/features/sidebar/SidebarScreen.kt | 13 +- .../settings/GatewaySettingsScreen.kt | 53 ++++- .../settings/GatewaySettingsScreenModel.kt | 13 + .../terminal/settings/GatewaySettingsState.kt | 4 + .../features/settings/SettingsStateTest.kt | 50 ---- 11 files changed, 71 insertions(+), 448 deletions(-) delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsEffect.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreen.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreenModel.kt delete mode 100644 frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt delete mode 100644 frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt index 0d3cf78..a8520d6 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/di/AppModule.kt @@ -16,7 +16,6 @@ import dev.plexus.shared.core.platform.getDefaultBaseUrl import dev.plexus.shared.core.platform.normalizeBaseUrl import dev.plexus.shared.core.settings.ThemeRepository import dev.plexus.shared.core.settings.ThemeRepositoryImpl -import dev.plexus.shared.features.settings.SettingsScreenModel import dev.plexus.shared.features.systemprompt.SystemPromptEditorScreenModel import dev.plexus.shared.features.terminal.agentlist.AgentListScreenModel import dev.plexus.shared.features.terminal.settings.GatewaySettingsScreenModel @@ -100,6 +99,7 @@ val appModule = factory { GatewaySettingsScreenModel( preferences = get(), + themeRepository = get(), ) } @@ -108,11 +108,4 @@ val appModule = repository = get(), ) } - - factory { - SettingsScreenModel( - preferences = get(), - themeRepository = get(), - ) - } } diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt index 90ec537..975267b 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/navigation/MainView.kt @@ -5,7 +5,6 @@ package dev.plexus.shared.features.navigation */ enum class MainView { SystemPrompt, - Settings, Terminal, GatewaySettings, TerminalSession, diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsEffect.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsEffect.kt deleted file mode 100644 index f97bc69..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsEffect.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.plexus.shared.features.settings - -/** - * アプリケーション設定画面のOne-shotイベント - * - * 画面遷移やメッセージ表示など、状態に依存しない単発イベントを表現する。 - */ - -sealed class SettingsEffect { - data class ShowMessage( - val message: String, - ) : SettingsEffect() - - data object NavigateBack : SettingsEffect() -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreen.kt deleted file mode 100644 index e8a85c8..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreen.kt +++ /dev/null @@ -1,225 +0,0 @@ -package dev.plexus.shared.features.settings - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.koin.koinScreenModel -import dev.plexus.shared.core.platform.isValidUrl -import dev.plexus.shared.core.settings.AppTheme -import dev.plexus.shared.core.ui.common.testTagResourceId -import dev.plexus.shared.core.ui.components.SecretTextField -import dev.plexus.shared.core.ui.components.SettingsTopBar -import dev.plexus.shared.core.ui.theme.PlexusThemeTokens -import kotlinx.coroutines.launch - -/** - * 設定画面 - * - * テーマ選択、API URL、API Keyの設定を行う。 - * - * @param onBack 戻るボタンコールバック - */ -class SettingsScreen( - private val onBack: () -> Unit = {}, -) : Screen { - @Composable - override fun Content() { - val screenModel = koinScreenModel() - val state by screenModel.state.collectAsState() - val snackbarHostState = remember { SnackbarHostState() } - val dimens = PlexusThemeTokens.dimens - - LaunchedEffect(Unit) { - screenModel.effect.collect { effect -> - when (effect) { - is SettingsEffect.ShowMessage -> launch { snackbarHostState.showSnackbar(effect.message) } - SettingsEffect.NavigateBack -> onBack() - } - } - } - - Scaffold( - snackbarHost = { SnackbarHost(snackbarHostState) }, - topBar = { - SettingsTopBar(title = "Settings", onBack = onBack) - }, - ) { paddingValues -> - Surface( - modifier = - Modifier - .fillMaxSize() - .padding(paddingValues), - ) { - Column( - modifier = - Modifier - .fillMaxSize() - .padding(dimens.space16), - ) { - AppearanceSection( - selectedTheme = state.selectedTheme, - onThemeSelected = screenModel::onThemeSelected, - ) - - Spacer(modifier = Modifier.height(dimens.space24)) - - ApiConfigurationSection( - inputUrl = state.inputUrl, - onUrlChange = screenModel::onUrlChange, - inputKey = state.inputKey, - onKeyChange = screenModel::onKeyChange, - ) - - Spacer(modifier = Modifier.height(dimens.space16)) - - SettingsActions( - inputUrl = state.inputUrl, - isSaving = state.isSaving, - onSave = screenModel::saveSettings, - ) - } - } - } - } -} - -@Composable -private fun AppearanceSection( - selectedTheme: AppTheme, - onThemeSelected: (AppTheme) -> Unit, -) { - val dimens = PlexusThemeTokens.dimens - - Text( - text = "Appearance", - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(bottom = dimens.space8), - ) - - AppTheme.entries.forEach { theme -> - ThemeOption( - text = theme.displayName, - selected = selectedTheme == theme, - onClick = { - onThemeSelected(theme) - }, - ) - } -} - -@Composable -private fun ApiConfigurationSection( - inputUrl: String, - onUrlChange: (String) -> Unit, - inputKey: String, - onKeyChange: (String) -> Unit, -) { - val dimens = PlexusThemeTokens.dimens - - Text( - text = "API Configuration", - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(bottom = dimens.space8), - ) - - OutlinedTextField( - value = inputUrl, - onValueChange = onUrlChange, - label = { Text("API URL") }, - placeholder = { Text("https://api.example.com") }, - modifier = - Modifier - .testTagResourceId("api_url_input") - .fillMaxWidth(), - singleLine = true, - isError = inputUrl.isNotBlank() && !isValidUrl(inputUrl), - supportingText = { - Text( - text = "Production: your hosted API | Tailscale: http://100.x.x.x:8000", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - }, - ) - - Spacer(modifier = Modifier.height(dimens.space16)) - - SecretTextField( - value = inputKey, - onValueChange = onKeyChange, - label = "API Key", - placeholder = "Optional: Enter your API key", - modifier = - Modifier - .testTagResourceId("api_key_input") - .fillMaxWidth(), - showContentDescription = "Show API Key", - hideContentDescription = "Hide API Key", - ) -} - -@Composable -private fun SettingsActions( - inputUrl: String, - isSaving: Boolean, - onSave: () -> Unit, -) { - Button( - onClick = onSave, - modifier = - Modifier - .testTagResourceId("save_settings_button") - .fillMaxWidth(), - enabled = !isSaving && isValidUrl(inputUrl), - ) { - Text("Save Settings") - } -} - -@Composable -private fun ThemeOption( - text: String, - selected: Boolean, - onClick: () -> Unit, -) { - val dimens = PlexusThemeTokens.dimens - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .fillMaxWidth() - .clickable(onClick = onClick) - .padding(vertical = dimens.space4), - ) { - RadioButton( - selected = selected, - onClick = onClick, - ) - Spacer(modifier = Modifier.width(dimens.space8)) - Text(text) - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreenModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreenModel.kt deleted file mode 100644 index 96dfb48..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsScreenModel.kt +++ /dev/null @@ -1,117 +0,0 @@ -package dev.plexus.shared.features.settings - -import cafe.adriel.voyager.core.model.ScreenModel -import cafe.adriel.voyager.core.model.screenModelScope -import dev.plexus.shared.core.platform.PlatformPreferences -import dev.plexus.shared.core.platform.PlatformPrefsDefaults -import dev.plexus.shared.core.platform.PlatformPrefsKeys -import dev.plexus.shared.core.platform.isValidUrl -import dev.plexus.shared.core.platform.normalizeBaseUrl -import dev.plexus.shared.core.settings.AppTheme -import dev.plexus.shared.core.settings.ThemeRepository -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch - -/** - * アプリケーション設定画面のScreenModel - * - * API接続設定(URL、API Key)とテーマ設定の管理・保存を行う。 - * 入力値の検証、正規化、永続化を担当し、UI StateとOne-shotイベントを管理する。 - * - * @property preferences プラットフォーム設定ストア(URL/Key永続化用) - * @property themeRepository テーマ設定のRepository - */ - -class SettingsScreenModel( - private val preferences: PlatformPreferences, - private val themeRepository: ThemeRepository, -) : ScreenModel { - private val _state = - MutableStateFlow( - SettingsState( - inputUrl = - preferences.getString( - PlatformPrefsKeys.KEY_API_URL, - PlatformPrefsDefaults.DEFAULT_API_URL, - ), - inputKey = - preferences.getString( - PlatformPrefsKeys.KEY_API_KEY, - PlatformPrefsDefaults.DEFAULT_API_KEY, - ), - ), - ) - val state: StateFlow = _state.asStateFlow() - - private val _effect = Channel(Channel.BUFFERED) - val effect: Flow = _effect.receiveAsFlow() - - init { - screenModelScope.launch { - themeRepository.theme.collect { theme -> - _state.update { it.copy(selectedTheme = theme) } - } - } - } - - fun onThemeSelected(theme: AppTheme) { - themeRepository.setTheme(theme) - } - - fun onUrlChange(value: String) { - _state.update { it.copy(inputUrl = value) } - } - - fun onKeyChange(value: String) { - _state.update { it.copy(inputKey = value) } - } - - fun saveSettings() { - val current = _state.value - if (current.isSaving) { - return - } - _state.update { it.copy(isSaving = true) } - - screenModelScope.launch { - try { - val trimmedUrl = current.inputUrl.trim() - val normalizedUrl = if (isValidUrl(trimmedUrl)) normalizeBaseUrl(trimmedUrl) else null - if (trimmedUrl.isNotBlank() && normalizedUrl == null) { - _effect.send(SettingsEffect.ShowMessage("Invalid URL format")) - return@launch - } - val savedUrl = normalizedUrl ?: current.inputUrl - val savedKey = current.inputKey.trim() - - if (normalizedUrl != null) { - preferences.putString(PlatformPrefsKeys.KEY_API_URL, normalizedUrl) - } - preferences.putString(PlatformPrefsKeys.KEY_API_KEY, savedKey) - - _state.update { - it.copy( - inputUrl = savedUrl, - inputKey = savedKey, - ) - } - _effect.send(SettingsEffect.ShowMessage("Settings saved")) - _effect.send(SettingsEffect.NavigateBack) - } catch (e: CancellationException) { - throw e - } catch (e: Exception) { - _effect.send(SettingsEffect.ShowMessage("Failed to save settings: ${e.message}")) - } finally { - _state.update { it.copy(isSaving = false) } - } - } - } -} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt deleted file mode 100644 index 46fa10a..0000000 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/settings/SettingsState.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.plexus.shared.features.settings - -import dev.plexus.shared.core.settings.AppTheme - -/** - * アプリケーション設定画面のUI状態 - * - * @property selectedTheme 選択中のテーマ(LIGHT/DARK) - * @property inputUrl 入力されたAPI URL - * @property inputKey 入力されたAPI Key - * @property isSaving 保存処理中かどうか - */ - -data class SettingsState( - val selectedTheme: AppTheme = AppTheme.DARK, - val inputUrl: String = "", - val inputKey: String = "", - val isSaving: Boolean = false, -) diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt index 3d8d99a..e881326 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarScreen.kt @@ -31,7 +31,6 @@ import dev.plexus.shared.core.platform.PlatformPrefsKeys import dev.plexus.shared.core.ui.theme.PlexusThemeTokens import dev.plexus.shared.features.navigation.MainNavigationHost import dev.plexus.shared.features.navigation.MainView -import dev.plexus.shared.features.settings.SettingsScreen import dev.plexus.shared.features.systemprompt.SystemPromptEditorScreen import dev.plexus.shared.features.terminal.agentlist.AgentListScreen import dev.plexus.shared.features.terminal.session.TerminalScreen @@ -111,7 +110,7 @@ class SidebarScreen : Screen { SidebarFooter( onSettingsClick = { - activeView = MainView.Settings + activeView = MainView.GatewaySettings scope.launch { drawerState.close() } }, onTerminalClick = { @@ -150,16 +149,6 @@ class SidebarScreen : Screen { promptScreen.Content() } - MainView.Settings -> { - val settingsScreen = - remember { - SettingsScreen( - onBack = { activeView = MainView.Terminal }, - ) - } - settingsScreen.Content() - } - MainView.Terminal -> agentListScreen.Content() MainView.GatewaySettings -> { val gatewaySettingsScreen = diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt index 1b1dab1..f4d32e7 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt @@ -1,14 +1,18 @@ package dev.plexus.shared.features.terminal.settings import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.clickable import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -19,10 +23,12 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.koinScreenModel import dev.plexus.shared.core.platform.isValidUrl +import dev.plexus.shared.core.settings.AppTheme import dev.plexus.shared.core.ui.components.SecretTextField import dev.plexus.shared.core.ui.components.SettingsTopBar import dev.plexus.shared.core.ui.theme.PlexusThemeTokens @@ -66,6 +72,8 @@ class GatewaySettingsScreen( .padding(paddingValues), ) { GatewaySettingsContent( + selectedTheme = state.selectedTheme, + onThemeSelected = screenModel::onThemeSelected, gatewayUrl = state.inputGatewayUrl, onGatewayUrlChange = screenModel::onGatewayUrlChange, apiKey = state.inputApiKey, @@ -80,13 +88,15 @@ class GatewaySettingsScreen( @Composable private fun GatewaySettingsContent( + selectedTheme: AppTheme, + onThemeSelected: (AppTheme) -> Unit, gatewayUrl: String, onGatewayUrlChange: (String) -> Unit, apiKey: String, onApiKeyChange: (String) -> Unit, onSave: () -> Unit, isSaving: Boolean, -) { + ) { val dimens = PlexusThemeTokens.dimens Column( @@ -95,6 +105,22 @@ private fun GatewaySettingsContent( .fillMaxSize() .padding(dimens.space16), ) { + Text( + text = "Appearance", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(bottom = dimens.space8), + ) + + AppTheme.entries.forEach { theme -> + ThemeOption( + text = theme.displayName, + selected = selectedTheme == theme, + onClick = { onThemeSelected(theme) }, + ) + } + + Spacer(modifier = Modifier.height(dimens.space24)) + Text( text = "Gateway API Configuration", style = MaterialTheme.typography.titleMedium, @@ -141,3 +167,28 @@ private fun GatewaySettingsContent( } } } + +@Composable +private fun ThemeOption( + text: String, + selected: Boolean, + onClick: () -> Unit, +) { + val dimens = PlexusThemeTokens.dimens + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(vertical = dimens.space4), + ) { + RadioButton( + selected = selected, + onClick = onClick, + ) + Spacer(modifier = Modifier.width(dimens.space8)) + Text(text) + } +} diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreenModel.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreenModel.kt index 0c5ab21..8ee9e78 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreenModel.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreenModel.kt @@ -8,6 +8,8 @@ import dev.plexus.shared.core.platform.PlatformPrefsKeys import dev.plexus.shared.core.platform.getDefaultGatewayBaseUrl import dev.plexus.shared.core.platform.isValidUrl import dev.plexus.shared.core.platform.normalizeBaseUrl +import dev.plexus.shared.core.settings.AppTheme +import dev.plexus.shared.core.settings.ThemeRepository import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -30,6 +32,7 @@ import kotlinx.coroutines.sync.Mutex class GatewaySettingsScreenModel( private val preferences: PlatformPreferences, + private val themeRepository: ThemeRepository, ) : ScreenModel { private val saveMutex = Mutex() @@ -55,6 +58,12 @@ class GatewaySettingsScreenModel( ), ) } + + screenModelScope.launch { + themeRepository.theme.collect { theme -> + _state.update { current -> current.copy(selectedTheme = theme) } + } + } } fun onGatewayUrlChange(value: String) { @@ -65,6 +74,10 @@ class GatewaySettingsScreenModel( _state.update { it.copy(inputApiKey = value) } } + fun onThemeSelected(theme: AppTheme) { + themeRepository.setTheme(theme) + } + fun saveSettings() { val current = _state.value if (current.isSaving) { diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsState.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsState.kt index 9bc4ac6..ca73c64 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsState.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsState.kt @@ -1,16 +1,20 @@ package dev.plexus.shared.features.terminal.settings +import dev.plexus.shared.core.settings.AppTheme + /** * Gateway設定画面のUI状態 * * @property inputGatewayUrl 入力されたGateway URL * @property inputApiKey 入力されたAPI Key + * @property selectedTheme 選択中のテーマ * @property isSaving 保存処理中かどうか */ data class GatewaySettingsState( val inputGatewayUrl: String = "", val inputApiKey: String = "", + val selectedTheme: AppTheme = AppTheme.DARK, val isSaving: Boolean = false, ) { val canSave: Boolean diff --git a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt b/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt deleted file mode 100644 index dc9b8c7..0000000 --- a/frontend/shared/src/commonTest/kotlin/dev/plexus/shared/features/settings/SettingsStateTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package dev.plexus.shared.features.settings - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse - -/** - * SettingsState のテスト - * - * SettingsState の初期状態とデフォルト値を検証します。 - */ -class SettingsStateTest { - @Test - fun `SettingsState starts with DARK theme`() { - val state = SettingsState() - - assertEquals(dev.plexus.shared.core.settings.AppTheme.DARK, state.selectedTheme) - } - - @Test - fun `SettingsState starts with empty inputs`() { - val state = SettingsState() - - assertEquals("", state.inputUrl) - assertEquals("", state.inputKey) - } - - @Test - fun `SettingsState starts with isSaving false`() { - val state = SettingsState() - - assertFalse(state.isSaving) - } - - @Test - fun `SettingsState with custom values preserves values`() { - val state = - SettingsState( - selectedTheme = dev.plexus.shared.core.settings.AppTheme.DARK, - inputUrl = "https://api.example.com", - inputKey = "test-key", - isSaving = true, - ) - - assertEquals(dev.plexus.shared.core.settings.AppTheme.DARK, state.selectedTheme) - assertEquals("https://api.example.com", state.inputUrl) - assertEquals("test-key", state.inputKey) - assertEquals(true, state.isSaving) - } -} From 126a61f2c1a26b4fbfac655239bee73d58524493 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:54:48 +0000 Subject: [PATCH 12/14] chore: refresh plexus naming in docs and tests --- .../firebase-fcm-notification-architecture.md | 6 +++--- docs/70.knowledge/webhook-guide.md | 10 +++++----- gateway/__init__.py | 2 +- gateway/tests/unit/test_terminal_api.py | 8 ++++---- gateway/tests/unit/test_tmux.py | 12 ++++++------ 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/70.knowledge/firebase-fcm-notification-architecture.md b/docs/70.knowledge/firebase-fcm-notification-architecture.md index 04f34f2..85828d6 100644 --- a/docs/70.knowledge/firebase-fcm-notification-architecture.md +++ b/docs/70.knowledge/firebase-fcm-notification-architecture.md @@ -1,6 +1,6 @@ # Firebase / FCM 通知アーキテクチャ -このドキュメントは、EgoGraph の通知機能における Firebase / FCM の役割と、実装上の責務分離を整理したものです。 +このドキュメントは、Plexus の通知機能における Firebase / FCM の役割と、実装上の責務分離を整理したものです。 Webhook の手順詳細は `docs/70.knowledge/webhook-guide.md` を参照してください。 ## Firebase とは @@ -44,8 +44,8 @@ Firebase は Google が提供する BaaS 群です。 関連実装: -- `frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmService.kt` -- `frontend/androidApp/src/main/kotlin/dev/egograph/android/fcm/FcmTokenManager.kt` +- `frontend/androidApp/src/main/kotlin/dev/plexus/android/fcm/FcmService.kt` +- `frontend/androidApp/src/main/kotlin/dev/plexus/android/fcm/FcmTokenManager.kt` - `gateway/api/push.py` (`register_token`) - `gateway/infrastructure/repositories.py` (`PushTokenRepository.save_token`) diff --git a/docs/70.knowledge/webhook-guide.md b/docs/70.knowledge/webhook-guide.md index f754ab9..3c3640c 100644 --- a/docs/70.knowledge/webhook-guide.md +++ b/docs/70.knowledge/webhook-guide.md @@ -1,6 +1,6 @@ # Webhook & Push Notification ガイド -このドキュメントは Webhook の基礎概念と EgoGraph でのプッシュ通知仕組み、セキュリティについて説明します。 +このドキュメントは Webhook の基礎概念と Plexus でのプッシュ通知仕組み、セキュリティについて説明します。 --- @@ -54,7 +54,7 @@ Webhook(ウェブフック)は「イベント通知を HTTP 経由で送信 --- -## パート2: EgoGraph での使用 +## パート2: Plexus での使用 ### 使用目的 @@ -372,7 +372,7 @@ openssl rand -base64 32 ## 前提条件 1. **Gateway**: 起動済み(tmuxセッション: `plexus-gateway`) -2. **Androidアプリ**: EgoGraphアプリをインストール済み +2. **Androidアプリ**: Plexus アプリをインストール済み 3. **FCM設定**: `gateway/.env` に `FCM_PROJECT_ID` が設定済み 4. **Webhook Secret**: `gateway/.env` に `GATEWAY_WEBHOOK_SECRET` が設定済み @@ -512,7 +512,7 @@ curl -X POST http://localhost:8001/v1/push/webhook \ ``` ┌─────────────────────────────────────────────────────────────────────────┐ -│ EgoGraph Webhook 通知フロー │ +│ Plexus Webhook 通知フロー │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ [外部サービス] [Gateway] [Firebase] │ @@ -554,7 +554,7 @@ curl -X POST http://localhost:8001/v1/push/webhook \ 2. **デバイスが登録されているか確認** ```bash # Gatewayのデータベースを確認 - cd /root/workspace/ego-graph/wt1/gateway + cd /root/workspace/plexus/gateway uv run python -c " import sqlite3 conn = sqlite3.connect('gateway.db') diff --git a/gateway/__init__.py b/gateway/__init__.py index 4190672..a1ccab3 100644 --- a/gateway/__init__.py +++ b/gateway/__init__.py @@ -1,4 +1,4 @@ -"""Terminal Gateway Service for EgoGraph. +"""Terminal Gateway Service for Plexus. モバイルtmuxアクセスとプッシュ通知を提供するGatewayサービス。 """ diff --git a/gateway/tests/unit/test_terminal_api.py b/gateway/tests/unit/test_terminal_api.py index f82a82c..cbe5196 100644 --- a/gateway/tests/unit/test_terminal_api.py +++ b/gateway/tests/unit/test_terminal_api.py @@ -226,7 +226,7 @@ async def test_keeps_current_path_when_pane_title_is_blank(self, mock_session): patch("gateway.api.terminal.verify_gateway_token"), patch( "gateway.api.terminal.get_active_pane_metadata", - return_value=(None, "/root/workspace/ego-graph"), + return_value=(None, "/root/workspace/plexus"), ), patch("gateway.api.terminal.TmuxAttachManager") as mock_manager_class, ): @@ -237,7 +237,7 @@ async def test_keeps_current_path_when_pane_title_is_blank(self, mock_session): response = await _build_session_response("agent-0001", mock_session) assert response["title"] is None - assert response["current_path"] == "/root/workspace/ego-graph" + assert response["current_path"] == "/root/workspace/plexus" # ============================================================================ @@ -264,7 +264,7 @@ async def test_returns_sessions_with_previews(self, mock_request): mock_run_sync.side_effect = lambda func, *args: ( mock_sessions if getattr(func, "__name__", "") == "list_sessions" - else ("Claude Code", "/root/workspace/ego-graph") + else ("Claude Code", "/root/workspace/plexus") ) mock_manager = MagicMock() mock_manager.capture_snapshot = AsyncMock( @@ -310,7 +310,7 @@ async def test_returns_single_session_with_preview(self, mock_request): mock_run_sync.side_effect = lambda func, *args: ( mock_sessions if getattr(func, "__name__", "") == "list_sessions" - else ("Claude Code", "/root/workspace/ego-graph") + else ("Claude Code", "/root/workspace/plexus") ) mock_manager = MagicMock() mock_manager.capture_snapshot = AsyncMock( diff --git a/gateway/tests/unit/test_tmux.py b/gateway/tests/unit/test_tmux.py index 1d005f8..cb157d2 100644 --- a/gateway/tests/unit/test_tmux.py +++ b/gateway/tests/unit/test_tmux.py @@ -300,33 +300,33 @@ def test_returns_active_pane_title_and_path(self) -> None: result = Mock() result.stdout = ( "0\tother title\t/tmp/other\n" - "1\tClaude Code\t/root/workspace/ego-graph\n" + "1\tClaude Code\t/root/workspace/plexus\n" ) mock_run.return_value = result title, current_path = get_active_pane_metadata("agent-0001") assert title == "Claude Code" - assert current_path == "/root/workspace/ego-graph" + assert current_path == "/root/workspace/plexus" def test_preserves_blank_title_for_active_pane(self) -> None: """active pane のタイトルが空でも current_path が保持されることを検証します。""" with patch("subprocess.run") as mock_run: result = Mock() - result.stdout = "1\t\t/root/workspace/ego-graph\n" + result.stdout = "1\t\t/root/workspace/plexus\n" mock_run.return_value = result title, current_path = get_active_pane_metadata("agent-0001") assert title is None - assert current_path == "/root/workspace/ego-graph" + assert current_path == "/root/workspace/plexus" def test_falls_back_to_first_pane_when_active_flag_missing(self) -> None: """active pane が見つからない場合は先頭 pane の情報へフォールバックすることを検証します。""" with patch("subprocess.run") as mock_run: result = Mock() result.stdout = ( - "0\tClaude Code\t/root/workspace/ego-graph\n" + "0\tClaude Code\t/root/workspace/plexus\n" "0\tOther\t/tmp/other\n" ) mock_run.return_value = result @@ -334,7 +334,7 @@ def test_falls_back_to_first_pane_when_active_flag_missing(self) -> None: title, current_path = get_active_pane_metadata("agent-0001") assert title == "Claude Code" - assert current_path == "/root/workspace/ego-graph" + assert current_path == "/root/workspace/plexus" def test_returns_none_pair_on_tmux_failure(self) -> None: """tmux 実行に失敗した場合は None ペアを返すことを検証します。""" From 5f4048bb525884672f9da9f271fcb18d11d0dbd6 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 16:59:29 +0000 Subject: [PATCH 13/14] fix: align ci checks with plexus repo layout --- .github/workflows/ci-gateway.yml | 2 +- .../dev/plexus/shared/features/sidebar/SidebarFooter.kt | 1 - .../features/terminal/settings/GatewaySettingsScreen.kt | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-gateway.yml b/.github/workflows/ci-gateway.yml index 1099e39..950ce7b 100644 --- a/.github/workflows/ci-gateway.yml +++ b/.github/workflows/ci-gateway.yml @@ -28,7 +28,7 @@ jobs: - name: Install dependencies run: | - uv sync --all-packages --group dev + cd gateway && uv sync --group dev - name: Run tests with coverage run: | diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt index 7b14f5b..7ade241 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/sidebar/SidebarFooter.kt @@ -15,7 +15,6 @@ import androidx.compose.material.icons.outlined.Tune import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt index f4d32e7..69c1901 100644 --- a/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt +++ b/frontend/shared/src/commonMain/kotlin/dev/plexus/shared/features/terminal/settings/GatewaySettingsScreen.kt @@ -1,5 +1,6 @@ package dev.plexus.shared.features.terminal.settings +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -8,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.clickable import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField @@ -96,7 +96,7 @@ private fun GatewaySettingsContent( onApiKeyChange: (String) -> Unit, onSave: () -> Unit, isSaving: Boolean, - ) { +) { val dimens = PlexusThemeTokens.dimens Column( From 5fa8f9b87c4cadaad63cb3466ff60ad4cff51854 Mon Sep 17 00:00:00 2001 From: "ryuto.endo" Date: Sat, 28 Mar 2026 17:41:38 +0000 Subject: [PATCH 14/14] docs: add --- docs/40.deploy/frontend-android.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/40.deploy/frontend-android.md b/docs/40.deploy/frontend-android.md index 521088c..849e824 100644 --- a/docs/40.deploy/frontend-android.md +++ b/docs/40.deploy/frontend-android.md @@ -48,6 +48,13 @@ keytool -genkey -v \ -keyalg RSA -keysize 2048 -validity 10000 ``` +補足: + +- Android の debug ビルドでは `debug.keystore` が必要です +- Android Studio / Gradle が自動生成した標準 debug keystore は通常 `/root/.android/debug.keystore` にあります +- internal 配布用 workflow でも同じ debug keystore を使えます +- 既存の EgoGraph 用 debug keystore を流用しても、`applicationId` が `dev.plexus.app` ならアプリ共存には影響しません + #### B. ビルド実行 環境変数を設定してビルドします。 @@ -73,3 +80,21 @@ export KEY_PASSWORD="your-password" `ci-frontend.yml` で自動テストと debug ビルドを行い、internal debug APK publish workflow で実機確認用 APK を配布できます。 内部配布用 artifact は production release ではありません。用途を明示したうえで GitHub pre-release へ配置してください。 + +### 4.1 GitHub Secrets + +Frontend の CI / internal APK 配布では次の secrets を使います。 + +- `GOOGLE_SERVICES_JSON_BASE64` + `google-services.json` を base64 化した文字列 +- `DEBUG_KEYSTORE_BASE64` + `debug.keystore` を base64 化した文字列 +- `DEBUG_KEYSTORE_PASSWORD` + debug keystore のパスワード + +Linux での生成例: + +```bash +base64 -w0 /absolute/path/to/google-services.json +base64 -w0 /root/.android/debug.keystore +```