mh-local 0.1.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mh_local-0.1.0a1/PKG-INFO +77 -0
- mh_local-0.1.0a1/README.md +68 -0
- mh_local-0.1.0a1/pyproject.toml +30 -0
- mh_local-0.1.0a1/src/mh_local/__init__.py +0 -0
- mh_local-0.1.0a1/src/mh_local/__main__.py +3 -0
- mh_local-0.1.0a1/src/mh_local/app.py +238 -0
- mh_local-0.1.0a1/src/mh_local/auth.py +29 -0
- mh_local-0.1.0a1/src/mh_local/main.py +85 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/AgentManagementPage-BJt2TpK1.css +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/AgentManagementPage-CXjYDSjB.js +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/ManagementNav-B9XyX5Un.js +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/ManagementNav-_BEf98ne.css +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/ProviderManagementPage-BNB9wWWv.js +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/ProviderManagementPage-DBI_1RWV.css +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/SceneManagementPage-BUbMRw_e.css +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/SceneManagementPage-CA7Ob7Hu.js +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/ToolManagementPage-5re12IWS.js +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/ToolManagementPage-Cazlqqda.css +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/index-C1ORYu0Z.css +1 -0
- mh_local-0.1.0a1/src/mh_local/static/assets/index-D8sObRrZ.js +115 -0
- mh_local-0.1.0a1/src/mh_local/static/component/mh-extra-components.umd.js +51 -0
- mh_local-0.1.0a1/src/mh_local/static/component/mh-tool-components.umd.js +1 -0
- mh_local-0.1.0a1/src/mh_local/static/favicon.svg +3 -0
- mh_local-0.1.0a1/src/mh_local/static/index.html +14 -0
- mh_local-0.1.0a1/src/mh_local/storage/__init__.py +0 -0
- mh_local-0.1.0a1/src/mh_local/storage/paths.py +56 -0
- mh_local-0.1.0a1/src/mh_local/storage/provider_store.py +92 -0
- mh_local-0.1.0a1/src/mh_local/storage/registry_store.py +561 -0
- mh_local-0.1.0a1/src/mh_local/storage/session_store.py +254 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mh-local
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Minimal Harness Client — pure local Agent assistant
|
|
5
|
+
Requires-Dist: uvicorn[standard]>=0.30.0
|
|
6
|
+
Requires-Dist: mh-gateway==0.1.0a1
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# mh-local
|
|
11
|
+
|
|
12
|
+
A self-contained local Agent assistant powered by [minimal-harness](https://github.com/J0ey1iu/minimal-harness). It bundles a FastAPI backend with a built-in Vue 3 SPA frontend — no database, no external service dependencies.
|
|
13
|
+
|
|
14
|
+
Latest version: **0.1.0a1**
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **No cloud required** — runs entirely on localhost, single-user
|
|
19
|
+
- **Built-in web UI** — chat interface with agent, scene, tool, and LLM provider management
|
|
20
|
+
- **No database** — file-based sessions and registries under `~/.config/mh-local/`
|
|
21
|
+
- **Auto-discovery** — automatically creates a workspace scene from the current directory
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add mh-local
|
|
27
|
+
uv run mh-local
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Your browser will open automatically to `http://127.0.0.1:<port>`.
|
|
31
|
+
|
|
32
|
+
On first run, you'll be prompted to configure an LLM provider (API key, base URL) via the web UI's Provider management page.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
After starting, mh-local serves a full-featured chat UI at the local URL with:
|
|
37
|
+
|
|
38
|
+
- **Chat** — converse with AI agents with streaming responses
|
|
39
|
+
- **Manage Agents** — view and configure built-in agents (General Assistant, Code Reviewer, Writer)
|
|
40
|
+
- **Manage Tools** — inspect available tools (bash, handoff, discover_agents, and more)
|
|
41
|
+
- **Manage Scenes** — workspace management for different projects
|
|
42
|
+
- **Manage Providers** — configure LLM providers (OpenAI, Anthropic, etc.)
|
|
43
|
+
|
|
44
|
+
## How it works
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
mh-local
|
|
48
|
+
├── FastAPI app (mh_gateway-based) — serves API + static files
|
|
49
|
+
├── Vue 3 SPA — chat UI served at /
|
|
50
|
+
├── File-based stores:
|
|
51
|
+
│ ├── sessions/ — conversation history
|
|
52
|
+
│ ├── providers.json — LLM provider config
|
|
53
|
+
│ └── registry.json — agents, tools, scenes
|
|
54
|
+
└── Local auth — auto-grants admin access
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
All configuration is done through the web UI. Persistent data lives at:
|
|
60
|
+
|
|
61
|
+
| Path | Contents |
|
|
62
|
+
|------|----------|
|
|
63
|
+
| `~/.config/mh-local/providers.json` | LLM provider credentials |
|
|
64
|
+
| `~/.config/mh-local/registry.json` | Agent/tool/scene registrations |
|
|
65
|
+
| `~/.config/mh-local/sessions/` | Chat session files |
|
|
66
|
+
| `~/.config/mh-local/logs/` | Runtime logs |
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Build frontend (SPA + component UMD bundles)
|
|
72
|
+
powershell scripts/build-mh-local-frontend.ps1 # Windows
|
|
73
|
+
bash scripts/build-mh-local-frontend.sh # Linux/macOS
|
|
74
|
+
|
|
75
|
+
# Run
|
|
76
|
+
uv run mh-local
|
|
77
|
+
```
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# mh-local
|
|
2
|
+
|
|
3
|
+
A self-contained local Agent assistant powered by [minimal-harness](https://github.com/J0ey1iu/minimal-harness). It bundles a FastAPI backend with a built-in Vue 3 SPA frontend — no database, no external service dependencies.
|
|
4
|
+
|
|
5
|
+
Latest version: **0.1.0a1**
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **No cloud required** — runs entirely on localhost, single-user
|
|
10
|
+
- **Built-in web UI** — chat interface with agent, scene, tool, and LLM provider management
|
|
11
|
+
- **No database** — file-based sessions and registries under `~/.config/mh-local/`
|
|
12
|
+
- **Auto-discovery** — automatically creates a workspace scene from the current directory
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv add mh-local
|
|
18
|
+
uv run mh-local
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Your browser will open automatically to `http://127.0.0.1:<port>`.
|
|
22
|
+
|
|
23
|
+
On first run, you'll be prompted to configure an LLM provider (API key, base URL) via the web UI's Provider management page.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
After starting, mh-local serves a full-featured chat UI at the local URL with:
|
|
28
|
+
|
|
29
|
+
- **Chat** — converse with AI agents with streaming responses
|
|
30
|
+
- **Manage Agents** — view and configure built-in agents (General Assistant, Code Reviewer, Writer)
|
|
31
|
+
- **Manage Tools** — inspect available tools (bash, handoff, discover_agents, and more)
|
|
32
|
+
- **Manage Scenes** — workspace management for different projects
|
|
33
|
+
- **Manage Providers** — configure LLM providers (OpenAI, Anthropic, etc.)
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
mh-local
|
|
39
|
+
├── FastAPI app (mh_gateway-based) — serves API + static files
|
|
40
|
+
├── Vue 3 SPA — chat UI served at /
|
|
41
|
+
├── File-based stores:
|
|
42
|
+
│ ├── sessions/ — conversation history
|
|
43
|
+
│ ├── providers.json — LLM provider config
|
|
44
|
+
│ └── registry.json — agents, tools, scenes
|
|
45
|
+
└── Local auth — auto-grants admin access
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
All configuration is done through the web UI. Persistent data lives at:
|
|
51
|
+
|
|
52
|
+
| Path | Contents |
|
|
53
|
+
|------|----------|
|
|
54
|
+
| `~/.config/mh-local/providers.json` | LLM provider credentials |
|
|
55
|
+
| `~/.config/mh-local/registry.json` | Agent/tool/scene registrations |
|
|
56
|
+
| `~/.config/mh-local/sessions/` | Chat session files |
|
|
57
|
+
| `~/.config/mh-local/logs/` | Runtime logs |
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Build frontend (SPA + component UMD bundles)
|
|
63
|
+
powershell scripts/build-mh-local-frontend.ps1 # Windows
|
|
64
|
+
bash scripts/build-mh-local-frontend.sh # Linux/macOS
|
|
65
|
+
|
|
66
|
+
# Run
|
|
67
|
+
uv run mh-local
|
|
68
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mh-local"
|
|
3
|
+
version = "0.1.0a1"
|
|
4
|
+
description = "Minimal Harness Client — pure local Agent assistant"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"uvicorn[standard]>=0.30.0",
|
|
9
|
+
"mh-gateway==0.1.0a1",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
mh-local = "mh_local.main:run"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["uv_build>=0.10.12,<0.12"]
|
|
17
|
+
build-backend = "uv_build"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.package-data]
|
|
23
|
+
mh_local = ["static/**/*"]
|
|
24
|
+
|
|
25
|
+
[tool.uv.sources]
|
|
26
|
+
mh-gateway = { workspace = true }
|
|
27
|
+
|
|
28
|
+
[[tool.uv.index]]
|
|
29
|
+
url = "https://mirrors.aliyun.com/pypi/simple/"
|
|
30
|
+
default = true
|
|
File without changes
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import AsyncIterator
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
from fastapi.staticfiles import StaticFiles
|
|
11
|
+
from mh_gateway.adapters import DatabaseProtocol
|
|
12
|
+
from mh_gateway.app import create_app
|
|
13
|
+
from mh_gateway.config import ConfigSchema
|
|
14
|
+
from minimal_harness.llm.factory import register_builtin_providers
|
|
15
|
+
from minimal_harness.llm.llm import LLMProvider, ProviderFactory
|
|
16
|
+
from starlette.responses import FileResponse
|
|
17
|
+
|
|
18
|
+
from mh_local.auth import LocalAuthProvider
|
|
19
|
+
from mh_local.storage.provider_store import FileLLMProviderStore
|
|
20
|
+
from mh_local.storage.registry_store import FileManagementProvider
|
|
21
|
+
from mh_local.storage.session_store import JSONLSessionStore
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("mh_local")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _NullDatabase(DatabaseProtocol):
|
|
27
|
+
async def init(self, dsn: str) -> None:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
async def close(self) -> None:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
async def execute(self, sql: str, params: list | None = None) -> Any:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
async def execute_write(self, sql: str, params: list | None = None) -> int:
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
async def fetch_one(self, sql: str, params: list | None = None) -> dict | None:
|
|
40
|
+
return {"ok": 1}
|
|
41
|
+
|
|
42
|
+
async def fetch_all(self, sql: str, params: list | None = None) -> list[dict]:
|
|
43
|
+
return []
|
|
44
|
+
|
|
45
|
+
async def begin(self) -> None:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
async def commit(self) -> None:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
async def rollback(self) -> None:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
async def executemany(self, sql: str, params_list: list[list]) -> None:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _M2MAuth:
|
|
59
|
+
async def close(self) -> None:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
async def authenticate(self, request: Any) -> str | None:
|
|
63
|
+
return "local"
|
|
64
|
+
|
|
65
|
+
async def get_identity_headers(self, request: Any, identity: str) -> dict[str, str]:
|
|
66
|
+
return {}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _OutboundAuth:
|
|
70
|
+
async def close(self) -> None:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
async def get_headers(
|
|
74
|
+
self, request: Any, target_url: str, target_type: str
|
|
75
|
+
) -> dict[str, str]:
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def create_local_app() -> FastAPI:
|
|
80
|
+
settings = ConfigSchema(
|
|
81
|
+
dev_mode=False,
|
|
82
|
+
log_level="INFO",
|
|
83
|
+
db_path="",
|
|
84
|
+
db_auto_schema=False,
|
|
85
|
+
enable_eval=False,
|
|
86
|
+
metrics_enabled=False,
|
|
87
|
+
cors_origins=[],
|
|
88
|
+
eval_results_dir="",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
auth = LocalAuthProvider()
|
|
92
|
+
session_store = JSONLSessionStore()
|
|
93
|
+
provider_store = FileLLMProviderStore()
|
|
94
|
+
management = FileManagementProvider()
|
|
95
|
+
|
|
96
|
+
@asynccontextmanager
|
|
97
|
+
async def _auth_hook(app: FastAPI) -> AsyncIterator[None]:
|
|
98
|
+
app.state.adapters.token_verifier = auth
|
|
99
|
+
app.state.adapters.permission_checker = auth
|
|
100
|
+
yield
|
|
101
|
+
|
|
102
|
+
@asynccontextmanager
|
|
103
|
+
async def _database_hook(app: FastAPI) -> AsyncIterator[None]:
|
|
104
|
+
app.state.adapters.database_provider = _NullDatabase()
|
|
105
|
+
yield
|
|
106
|
+
|
|
107
|
+
@asynccontextmanager
|
|
108
|
+
async def _session_store_hook(app: FastAPI) -> AsyncIterator[None]:
|
|
109
|
+
app.state.adapters.session_store_provider = session_store
|
|
110
|
+
yield
|
|
111
|
+
|
|
112
|
+
@asynccontextmanager
|
|
113
|
+
async def _management_hook(app: FastAPI) -> AsyncIterator[None]:
|
|
114
|
+
await management.sync_cwd_scene()
|
|
115
|
+
app.state.adapters.management_provider = management
|
|
116
|
+
yield
|
|
117
|
+
|
|
118
|
+
@asynccontextmanager
|
|
119
|
+
async def _llm_registry_hook(app: FastAPI) -> AsyncIterator[None]:
|
|
120
|
+
factory = ProviderFactory()
|
|
121
|
+
register_builtin_providers(factory)
|
|
122
|
+
app.state.adapters.llm_provider_registry = factory
|
|
123
|
+
|
|
124
|
+
def _default_factory() -> LLMProvider:
|
|
125
|
+
return factory.create("openai", {})
|
|
126
|
+
|
|
127
|
+
app.state.adapters.llm_provider_factory = _default_factory
|
|
128
|
+
yield
|
|
129
|
+
|
|
130
|
+
@asynccontextmanager
|
|
131
|
+
async def _provider_store_hook(app: FastAPI) -> AsyncIterator[None]:
|
|
132
|
+
app.state.adapters.llm_provider_store = provider_store
|
|
133
|
+
yield
|
|
134
|
+
await provider_store.close()
|
|
135
|
+
|
|
136
|
+
@asynccontextmanager
|
|
137
|
+
async def _m2m_hook(app: FastAPI) -> AsyncIterator[None]:
|
|
138
|
+
app.state.adapters.m2m_auth_provider = _M2MAuth()
|
|
139
|
+
yield
|
|
140
|
+
|
|
141
|
+
@asynccontextmanager
|
|
142
|
+
async def _outbound_hook(app: FastAPI) -> AsyncIterator[None]:
|
|
143
|
+
app.state.adapters.outbound_auth_provider = _OutboundAuth()
|
|
144
|
+
yield
|
|
145
|
+
|
|
146
|
+
app = create_app(
|
|
147
|
+
settings=settings,
|
|
148
|
+
token_verifier=_auth_hook,
|
|
149
|
+
permission_checker=None,
|
|
150
|
+
management_provider=_management_hook,
|
|
151
|
+
llm_provider_registry=_llm_registry_hook,
|
|
152
|
+
llm_provider_store=_provider_store_hook,
|
|
153
|
+
outbound_auth_provider=_outbound_hook,
|
|
154
|
+
m2m_auth_provider=_m2m_hook,
|
|
155
|
+
database_provider=_database_hook,
|
|
156
|
+
session_store_provider=_session_store_hook,
|
|
157
|
+
lifespan_hooks=None,
|
|
158
|
+
dev_routers=None,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
_mount_mh_local_static(app)
|
|
162
|
+
return app
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _mount_mh_local_static(app: FastAPI) -> None:
|
|
166
|
+
static_dir = Path(__file__).resolve().parent / "static"
|
|
167
|
+
if not static_dir.is_dir():
|
|
168
|
+
logger.warning("MH-Local static directory not found at %s", static_dir)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
subdirs = [
|
|
172
|
+
("/assets", static_dir / "assets"),
|
|
173
|
+
("/component", static_dir / "component"),
|
|
174
|
+
]
|
|
175
|
+
for url_path, fs_path in subdirs:
|
|
176
|
+
if fs_path.is_dir():
|
|
177
|
+
app.mount(
|
|
178
|
+
url_path,
|
|
179
|
+
StaticFiles(directory=str(fs_path)),
|
|
180
|
+
name=f"mh_local_{url_path.lstrip('/')}",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
favicon = static_dir / "favicon.svg"
|
|
184
|
+
if favicon.is_file():
|
|
185
|
+
from fastapi.responses import FileResponse as FR
|
|
186
|
+
|
|
187
|
+
@app.get("/favicon.svg", include_in_schema=False)
|
|
188
|
+
async def _favicon():
|
|
189
|
+
return FR(str(favicon))
|
|
190
|
+
|
|
191
|
+
@app.middleware("http")
|
|
192
|
+
async def _mh_local_spa_fallback(request: Any, call_next: Any) -> Any:
|
|
193
|
+
response = await call_next(request)
|
|
194
|
+
if (
|
|
195
|
+
response.status_code == 404
|
|
196
|
+
and request.method == "GET"
|
|
197
|
+
and request.url.path != "/api"
|
|
198
|
+
and not request.url.path.startswith("/api/")
|
|
199
|
+
and not request.url.path.startswith("/docs")
|
|
200
|
+
and not request.url.path.startswith("/openapi")
|
|
201
|
+
and not request.url.path.startswith("/redoc")
|
|
202
|
+
and not request.url.path.startswith("/assets")
|
|
203
|
+
and not request.url.path.startswith("/component")
|
|
204
|
+
):
|
|
205
|
+
index_path = static_dir / "index.html"
|
|
206
|
+
if index_path.is_file():
|
|
207
|
+
return FileResponse(str(index_path))
|
|
208
|
+
return response
|
|
209
|
+
|
|
210
|
+
_mount_component_sources(app)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _mount_component_sources(app: FastAPI) -> None:
|
|
214
|
+
static_dir = Path(__file__).resolve().parent / "static"
|
|
215
|
+
components = static_dir / "component"
|
|
216
|
+
if not components.is_dir():
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
from fastapi import APIRouter
|
|
220
|
+
|
|
221
|
+
router = APIRouter(prefix="/api/v1/component-sources", tags=["component-sources"])
|
|
222
|
+
|
|
223
|
+
sources = []
|
|
224
|
+
for f in components.iterdir():
|
|
225
|
+
if f.suffix == ".js":
|
|
226
|
+
sources.append(
|
|
227
|
+
{
|
|
228
|
+
"id": f.stem,
|
|
229
|
+
"label": f.stem.replace("mh-", "").replace("-", " ").title(),
|
|
230
|
+
"url": f"/component/{f.name}",
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
@router.get("")
|
|
235
|
+
async def get_component_sources():
|
|
236
|
+
return sources
|
|
237
|
+
|
|
238
|
+
app.include_router(router)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from mh_gateway.adapters import (
|
|
6
|
+
PermissionChecker,
|
|
7
|
+
UserAuthProvider,
|
|
8
|
+
UserIdentity,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LocalAuthProvider(UserAuthProvider, PermissionChecker):
|
|
13
|
+
async def verify(self, request: Any) -> UserIdentity:
|
|
14
|
+
return UserIdentity(
|
|
15
|
+
user_id="local",
|
|
16
|
+
username="Local User",
|
|
17
|
+
roles=["admin"],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
async def logout(self, request: Any, response: Any) -> None:
|
|
21
|
+
response.set_cookie(
|
|
22
|
+
key="x-user-id", value="", httponly=True, max_age=0, path="/"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
async def get_permissions(self, user_id: str) -> list[str]:
|
|
26
|
+
return ["*", "*:*:*"]
|
|
27
|
+
|
|
28
|
+
async def check(self, user_id: str, permission: str) -> bool:
|
|
29
|
+
return True
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import socket
|
|
7
|
+
import sys
|
|
8
|
+
import webbrowser
|
|
9
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
10
|
+
|
|
11
|
+
import uvicorn
|
|
12
|
+
|
|
13
|
+
from mh_local.app import create_local_app
|
|
14
|
+
from mh_local.storage.paths import LOGS_DIR, PROVIDERS_FILE, ensure_dirs
|
|
15
|
+
from mh_local.storage.registry_store import compute_scene_id
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("mh_local")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _find_free_port() -> int:
|
|
21
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
22
|
+
s.bind(("127.0.0.1", 0))
|
|
23
|
+
return s.getsockname()[1]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _has_providers() -> bool:
|
|
27
|
+
if not PROVIDERS_FILE.exists():
|
|
28
|
+
return False
|
|
29
|
+
data = json.loads(PROVIDERS_FILE.read_text("utf-8"))
|
|
30
|
+
return any(p.get("api_key", "").strip() for p in data)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _open_url(port: int) -> str:
|
|
34
|
+
cwd = os.getcwd()
|
|
35
|
+
scene_id = compute_scene_id(cwd)
|
|
36
|
+
url = f"http://127.0.0.1:{port}/#/?scene={scene_id}"
|
|
37
|
+
webbrowser.open(url)
|
|
38
|
+
return url
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run() -> None:
|
|
42
|
+
ensure_dirs()
|
|
43
|
+
|
|
44
|
+
log_file = LOGS_DIR / "mh_local.log"
|
|
45
|
+
file_handler = TimedRotatingFileHandler(
|
|
46
|
+
str(log_file), when="midnight", interval=1, backupCount=7, encoding="utf-8"
|
|
47
|
+
)
|
|
48
|
+
file_handler.setFormatter(
|
|
49
|
+
logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
logging.basicConfig(
|
|
53
|
+
level=logging.INFO,
|
|
54
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
55
|
+
handlers=[logging.StreamHandler(sys.stderr), file_handler],
|
|
56
|
+
)
|
|
57
|
+
logging.getLogger("uvicorn.access").handlers.clear()
|
|
58
|
+
logging.getLogger("uvicorn.access").propagate = False
|
|
59
|
+
|
|
60
|
+
first_run = not _has_providers()
|
|
61
|
+
if first_run:
|
|
62
|
+
logger.info(
|
|
63
|
+
"No LLM provider configured. "
|
|
64
|
+
"A template has been created at %s. "
|
|
65
|
+
"Please configure your API key via the web UI (Manage -> Providers).",
|
|
66
|
+
PROVIDERS_FILE,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
app = create_local_app()
|
|
70
|
+
port = _find_free_port()
|
|
71
|
+
|
|
72
|
+
url = _open_url(port)
|
|
73
|
+
logger.info("Starting mh-local on %s", url)
|
|
74
|
+
|
|
75
|
+
uvicorn.run(
|
|
76
|
+
app,
|
|
77
|
+
host="127.0.0.1",
|
|
78
|
+
port=port,
|
|
79
|
+
log_level="info",
|
|
80
|
+
access_log=False,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
run()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.ta-messages[data-v-3af0e115]{flex:1;overflow-y:auto;margin-bottom:12px;display:flex;flex-direction:column;gap:10px;min-height:200px;max-height:400px}.ta-empty[data-v-3af0e115]{color:var(--text-muted);font-size:13px;text-align:center;padding:48px 16px}.ta-msg[data-v-3af0e115]{padding:10px 12px;border-radius:10px;font-size:13px;line-height:1.5}.ta-msg-user[data-v-3af0e115]{background:var(--accent-dim);align-self:flex-end;max-width:85%}.ta-msg-assistant[data-v-3af0e115]{background:var(--glass-highlight);border:1px solid var(--glass-border);align-self:flex-start;max-width:85%}.ta-msg-label[data-v-3af0e115]{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.3px;color:var(--text-secondary);margin-bottom:4px}.ta-msg-content[data-v-3af0e115]{white-space:pre-wrap;word-break:break-word}.ta-msg-thinking[data-v-3af0e115]{color:var(--text-muted);font-style:italic}.ta-input-row[data-v-3af0e115]{display:flex;gap:8px}.ta-input[data-v-3af0e115]{flex:1;padding:9px 12px;background:var(--glass-highlight);border:1px solid var(--glass-border);border-radius:8px;color:var(--text-primary);font-size:13px;box-sizing:border-box;transition:border-color .15s}.ta-input[data-v-3af0e115]:focus{outline:none;border-color:var(--accent)}.ta-input[data-v-3af0e115]:disabled{opacity:.5}.btn-send[data-v-3af0e115]{padding:9px 18px;background:var(--accent);border:none;border-radius:8px;color:#fff;font-weight:600;font-size:13px;cursor:pointer;transition:opacity .15s;white-space:nowrap}.btn-send[data-v-3af0e115]:hover:not(:disabled){opacity:.9}.btn-send[data-v-3af0e115]:disabled{opacity:.5;cursor:not-allowed}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as de,Q as ce,O as me,y as ue,z as r,g as f,m as O,d as e,I as o,J as s,Z as m,W as u,$ as _e,F as K,E as Q,S as W,e as q,a0 as Z,l as g,f as G,T as H,x as re,c as R,A as c,t as pe,s as ve,K as ge,h as he,o as fe,_ as ye}from"./index-D8sObRrZ.js";import{M as be}from"./ManagementNav-B9XyX5Un.js";const ze={class:"mgmt-page"},we={class:"mgmt-page-content"},ke={class:"mgmt-header"},Ce={class:"mgmt-toolbar"},Ve=["placeholder"],xe={key:0,class:"table-wrap"},Se={class:"mgmt-table"},Ue={key:1,class:"mgmt-empty"},$e={key:2},Me={class:"table-wrap"},Fe={class:"mgmt-table"},Ne=["title"],Ae=["title"],Te=["title"],Je=["title"],Oe=["title"],Pe={class:"cell-actions"},Le=["onClick"],Be=["onClick"],Ie={class:"mgmt-pagination"},De=["disabled"],Ee={class:"mgmt-page-info"},Ke=["disabled"],Qe={class:"dialog dialog-wide"},We={class:"form-group"},qe=["disabled","placeholder"],Ze={class:"form-group"},Ge=["placeholder"],He={class:"form-group"},Re={class:"tc-label-row"},Xe=["title"],Ye={class:"form-group"},je={class:"tc-label-row"},el=["title"],ll={class:"form-group"},tl={class:"form-group"},sl=["placeholder"],al={class:"locale-section"},ol={class:"form-group"},nl={class:"tc-label-row"},il=["title"],dl=["placeholder"],cl={class:"locale-section"},ml={class:"locale-field"},ul={class:"locale-field-label"},_l={class:"locale-input-row"},rl={class:"locale-lang"},pl={class:"locale-lang"},vl=["placeholder"],gl={class:"locale-field"},hl={class:"locale-field-label"},fl={class:"locale-input-row"},yl={class:"locale-lang"},bl=["title"],zl={class:"locale-lang"},wl=["title"],kl=["placeholder"],Cl={class:"locale-field"},Vl={class:"locale-field-label"},xl={class:"locale-input-row"},Sl={class:"locale-lang"},Ul=["title"],$l={class:"locale-lang"},Ml=["title"],Fl=["placeholder"],Nl={class:"dialog-actions"},Al=["disabled"],Tl={class:"tc-overlay-content"},Jl={class:"tc-overlay-header"},Ol={class:"tc-overlay-title"},Pl={class:"tc-overlay-body"},Ll=de({__name:"AgentManagementPage",setup(Bl){const{t:a,localeVal:V}=ce(),k=me(),M=c([]),X=R(()=>M.value.map(n=>({value:n.name,label:n.name}))),F=c([]),N=c(!1),z=c(!1),C=c(!1),d=c({name:"",display_name:"",display_name_locale:"",description:"",description_locale:"",system_prompt:"",system_prompt_locale:"",provider:"openai",model:"",llm_config:{}}),i=c({display_zh:"",display_en:"",desc_zh:"",desc_en:"",prompt_zh:"",prompt_en:""});function A(n){if(!n)return{zh:"",en:""};try{const l=JSON.parse(n);return{zh:l.zh??"",en:l.en??""}}catch{return{zh:"",en:""}}}function T(n,l){return!n&&!l?"":JSON.stringify({zh:n,en:l})}function Y(){const n=A(d.value.display_name_locale);i.value.display_zh=n.zh,i.value.display_en=n.en;const l=A(d.value.description_locale);i.value.desc_zh=l.zh,i.value.desc_en=l.en;const v=A(d.value.system_prompt_locale);i.value.prompt_zh=v.zh,i.value.prompt_en=v.en}function j(){d.value.display_name_locale=T(i.value.display_zh,i.value.display_en),d.value.description_locale=T(i.value.desc_zh,i.value.desc_en),d.value.system_prompt_locale=T(i.value.prompt_zh,i.value.prompt_en)}const x=c(""),h=c(1),S=c(15),P=c(0),ee=[{value:10,label:"10"},{value:15,label:"15"},{value:20,label:"20"},{value:50,label:"50"}],L=R(()=>Math.max(1,Math.ceil(P.value/S.value))),U=c(!1),y=c(""),J=c(!1),B=c(""),p=c("");let _=null;function b(n,l,v){B.value=n,p.value=l,_=v,J.value=!0}function I(){_==="description"?d.value.description=p.value:_==="system_prompt"?d.value.system_prompt=p.value:_==="llm_config"?y.value=p.value:_==="locale_desc_zh"?i.value.desc_zh=p.value:_==="locale_desc_en"?i.value.desc_en=p.value:_==="locale_prompt_zh"?i.value.prompt_zh=p.value:_==="locale_prompt_en"&&(i.value.prompt_en=p.value),J.value=!1,_=null}async function le(){try{const n=await pe({page_size:100});M.value=n.items}catch{M.value=[{name:"openai",provider_type:"openai"},{name:"anthropic",provider_type:"anthropic"}]}}async function w(){N.value=!0;try{const n=await ve({q:x.value,page:h.value,page_size:S.value});F.value=n.items,P.value=n.total}catch(n){k.show("Failed to load agents: "+n.message)}finally{N.value=!1}}function D(){h.value=1,w()}function E(n){h.value=n,w()}function te(){h.value=1,w()}function se(){C.value=!1,d.value={name:"",display_name:"",display_name_locale:"",description:"",description_locale:"",system_prompt:"",system_prompt_locale:"",provider:"openai",model:"",llm_config:{}},i.value={display_zh:"",display_en:"",desc_zh:"",desc_en:"",prompt_zh:"",prompt_en:""},y.value="",z.value=!0}function ae(n){C.value=!0,d.value={...n},y.value=n.llm_config?JSON.stringify(n.llm_config,null,2):"",Y(),z.value=!0}async function oe(){if(j(),y.value.trim())try{d.value.llm_config=JSON.parse(y.value)}catch{k.show("Invalid JSON in model config");return}else d.value.llm_config={};U.value=!0;try{C.value&&d.value.name?await ge(d.value.name,d.value):await he(d.value),z.value=!1,await w()}catch(n){k.show("Failed to save: "+n.message)}finally{U.value=!1}}function $(n,l){if(!n)return"-";const v=n.replace("T"," ").substring(0,16);return l?`${v} by ${l}`:v}async function ne(n){if(await k.confirm(a("alert_confirm_delete_agent",{name:n})))try{await fe(n),await w()}catch(l){k.show("Failed to delete: "+l.message)}}return ue(()=>{le(),w()}),(n,l)=>{var v;return r(),f("div",ze,[O(be),e("div",we,[e("header",ke,[e("h1",null,o(s(a)("mgmt_agents")),1),e("button",{class:"btn-primary",onClick:se},o(s(a)("mgmt_new_agent")),1)]),e("div",null,[e("div",Ce,[m(e("input",{"onUpdate:modelValue":l[0]||(l[0]=t=>x.value=t),class:"mgmt-search",placeholder:s(a)("mgmt_search_placeholder"),onKeyup:_e(D,["enter"])},null,40,Ve),[[u,x.value]]),e("button",{class:"btn-search",onClick:D},o(s(a)("mgmt_search")),1)]),N.value?(r(),f("div",xe,[e("table",Se,[e("thead",null,[e("tr",null,[e("th",null,o(s(a)("mgmt_name")),1),e("th",null,o(s(a)("mgmt_display_name")),1),e("th",null,o(s(a)("mgmt_description")),1),e("th",null,o(s(a)("mgmt_created_at")),1),e("th",null,o(s(a)("mgmt_updated_at")),1),e("th",null,o(s(a)("mgmt_actions")),1)])]),e("tbody",null,[(r(),f(K,null,Q(5,t=>e("tr",{key:t,class:"mgmt-skeleton-row"},[...l[27]||(l[27]=[e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"60%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"50%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"70%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"50%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell mgmt-skeleton-cell-sm",style:{width:"65%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell mgmt-skeleton-cell-sm",style:{width:"65%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"50%"}})],-1)])])),64))])])])):F.value.length===0?(r(),f("div",Ue,o(x.value?s(a)("mgmt_no_results"):s(a)("mgmt_no_agents")),1)):(r(),f("div",$e,[e("div",Me,[e("table",Fe,[e("thead",null,[e("tr",null,[e("th",null,o(s(a)("mgmt_name")),1),e("th",null,o(s(a)("mgmt_display_name")),1),e("th",null,o(s(a)("mgmt_description")),1),e("th",null,o(s(a)("mgmt_created_at")),1),e("th",null,o(s(a)("mgmt_updated_at")),1),e("th",null,o(s(a)("mgmt_actions")),1)])]),e("tbody",null,[(r(!0),f(K,null,Q(F.value,t=>(r(),f("tr",{key:t.name},[e("td",{title:t.name},[e("code",null,o(t.name),1)],8,Ne),e("td",{title:s(V)(t.display_name_locale,t.display_name)},o(s(V)(t.display_name_locale,t.display_name)),9,Ae),e("td",{class:"cell-desc",title:s(V)(t.description_locale,t.description)},o(s(V)(t.description_locale,t.description)),9,Te),e("td",{class:"cell-audit",title:$(t.created_at,t.created_by)},o($(t.created_at,t.created_by)),9,Je),e("td",{class:"cell-audit",title:$(t.updated_at,t.updated_by)},o($(t.updated_at,t.updated_by)),9,Oe),e("td",Pe,[e("button",{class:"btn-action",onClick:ie=>ae(t)},o(s(a)("mgmt_edit")),9,Le),e("button",{class:"btn-action btn-danger",onClick:ie=>ne(t.name)},o(s(a)("mgmt_delete")),9,Be)])]))),128))])])]),e("div",Ie,[e("button",{class:"btn-page",disabled:h.value<=1,onClick:l[1]||(l[1]=t=>E(h.value-1))},o(s(a)("mgmt_prev_page")),9,De),e("span",Ee,o(h.value)+" / "+o(L.value),1),e("button",{class:"btn-page",disabled:h.value>=L.value,onClick:l[2]||(l[2]=t=>E(h.value+1))},o(s(a)("mgmt_next_page")),9,Ke),O(W,{modelValue:S.value,"onUpdate:modelValue":l[3]||(l[3]=t=>S.value=t),modelModifiers:{number:!0},options:ee,searchable:!1,onChange:te},null,8,["modelValue"])])])),(r(),q(H,{to:"body"},[z.value?(r(),f("div",{key:0,class:"dialog-overlay",onClick:l[25]||(l[25]=Z(t=>z.value=!1,["self"]))},[e("div",Qe,[e("h2",null,o(C.value?s(a)("mgmt_edit_agent"):s(a)("mgmt_new_agent_title")),1),e("div",We,[e("label",null,o(s(a)("mgmt_name")),1),m(e("input",{"onUpdate:modelValue":l[4]||(l[4]=t=>d.value.name=t),disabled:C.value,placeholder:s(a)("mgmt_placeholder_id")},null,8,qe),[[u,d.value.name]])]),e("div",Ze,[e("label",null,o(s(a)("mgmt_display_name")),1),m(e("input",{"onUpdate:modelValue":l[5]||(l[5]=t=>d.value.display_name=t),placeholder:s(a)("mgmt_placeholder_display_name")},null,8,Ge),[[u,d.value.display_name]])]),e("div",He,[e("div",Re,[e("label",null,o(s(a)("mgmt_description")),1),e("button",{class:"tc-fullscreen-btn",onClick:l[6]||(l[6]=t=>b(s(a)("mgmt_description"),d.value.description??"","description")),title:s(a)("mgmt_tc_source_code")},"⛶",8,Xe)]),m(e("textarea",{"onUpdate:modelValue":l[7]||(l[7]=t=>d.value.description=t),rows:"2"},null,512),[[u,d.value.description]])]),e("div",Ye,[e("div",je,[e("label",null,o(s(a)("mgmt_system_prompt")),1),e("button",{class:"tc-fullscreen-btn",onClick:l[8]||(l[8]=t=>b(s(a)("mgmt_system_prompt"),d.value.system_prompt??"","system_prompt")),title:s(a)("mgmt_tc_source_code")},"⛶",8,el)]),m(e("textarea",{"onUpdate:modelValue":l[9]||(l[9]=t=>d.value.system_prompt=t),rows:"6",class:"mono"},null,512),[[u,d.value.system_prompt]])]),e("div",ll,[e("label",null,o(s(a)("mgmt_provider")),1),O(W,{modelValue:d.value.provider,"onUpdate:modelValue":l[10]||(l[10]=t=>d.value.provider=t),options:X.value,searchable:!1},null,8,["modelValue","options"])]),e("div",tl,[e("label",null,o(s(a)("mgmt_model")),1),m(e("input",{"onUpdate:modelValue":l[11]||(l[11]=t=>d.value.model=t),placeholder:s(a)("mgmt_model_placeholder")},null,8,sl),[[u,d.value.model]])]),e("details",al,[e("summary",null,o(s(a)("mgmt_llm_config")),1),e("div",ol,[e("div",nl,[l[28]||(l[28]=e("span",null,null,-1)),e("button",{class:"tc-fullscreen-btn",onClick:l[12]||(l[12]=t=>b(s(a)("mgmt_llm_config"),y.value,"llm_config")),title:s(a)("mgmt_tc_source_code")},"⛶",8,il)]),m(e("textarea",{"onUpdate:modelValue":l[13]||(l[13]=t=>y.value=t),rows:"4",class:"mono",placeholder:s(a)("mgmt_llm_config_placeholder")},null,8,dl),[[u,y.value]])])]),e("details",cl,[e("summary",null,o(s(a)("mgmt_translations")),1),e("div",ml,[e("div",ul,o(s(a)("mgmt_locale_display")),1),e("div",_l,[e("label",rl,[l[29]||(l[29]=g("zh ",-1)),m(e("input",{"onUpdate:modelValue":l[14]||(l[14]=t=>i.value.display_zh=t),placeholder:"中文名称"},null,512),[[u,i.value.display_zh]])]),e("label",pl,[l[30]||(l[30]=g("en ",-1)),m(e("input",{"onUpdate:modelValue":l[15]||(l[15]=t=>i.value.display_en=t),placeholder:s(a)("mgmt_placeholder_display_name")},null,8,vl),[[u,i.value.display_en]])])])]),e("div",gl,[e("div",hl,o(s(a)("mgmt_locale_desc")),1),e("div",fl,[e("label",yl,[l[31]||(l[31]=g("zh ",-1)),e("button",{class:"tc-fullscreen-btn",onClick:l[16]||(l[16]=t=>b("zh "+s(a)("mgmt_locale_desc"),i.value.desc_zh,"locale_desc_zh")),title:s(a)("mgmt_tc_source_code")},"⛶",8,bl),l[32]||(l[32]=g()),m(e("textarea",{"onUpdate:modelValue":l[17]||(l[17]=t=>i.value.desc_zh=t),rows:"2",placeholder:"中文描述"},null,512),[[u,i.value.desc_zh]])]),e("label",zl,[l[33]||(l[33]=g("en ",-1)),e("button",{class:"tc-fullscreen-btn",onClick:l[18]||(l[18]=t=>b("en "+s(a)("mgmt_locale_desc"),i.value.desc_en,"locale_desc_en")),title:s(a)("mgmt_tc_source_code")},"⛶",8,wl),l[34]||(l[34]=g()),m(e("textarea",{"onUpdate:modelValue":l[19]||(l[19]=t=>i.value.desc_en=t),rows:"2",placeholder:s(a)("mgmt_placeholder_desc")},null,8,kl),[[u,i.value.desc_en]])])])]),e("div",Cl,[e("div",Vl,o(s(a)("mgmt_locale_prompt")),1),e("div",xl,[e("label",Sl,[l[35]||(l[35]=g("zh ",-1)),e("button",{class:"tc-fullscreen-btn",onClick:l[20]||(l[20]=t=>b("zh "+s(a)("mgmt_locale_prompt"),i.value.prompt_zh,"locale_prompt_zh")),title:s(a)("mgmt_tc_source_code")},"⛶",8,Ul),l[36]||(l[36]=g()),m(e("textarea",{"onUpdate:modelValue":l[21]||(l[21]=t=>i.value.prompt_zh=t),rows:"3",placeholder:"中文系统提示"},null,512),[[u,i.value.prompt_zh]])]),e("label",$l,[l[37]||(l[37]=g("en ",-1)),e("button",{class:"tc-fullscreen-btn",onClick:l[22]||(l[22]=t=>b("en "+s(a)("mgmt_locale_prompt"),i.value.prompt_en,"locale_prompt_en")),title:s(a)("mgmt_tc_source_code")},"⛶",8,Ml),l[38]||(l[38]=g()),m(e("textarea",{"onUpdate:modelValue":l[23]||(l[23]=t=>i.value.prompt_en=t),rows:"3",placeholder:s(a)("mgmt_placeholder_prompt")},null,8,Fl),[[u,i.value.prompt_en]])])])])]),e("div",Nl,[e("button",{class:"btn-cancel",onClick:l[24]||(l[24]=t=>z.value=!1)},o(s(a)("mgmt_cancel")),1),e("button",{class:"btn-primary",disabled:U.value||!d.value.name,onClick:oe},o(U.value?s(a)("mgmt_saving"):s(a)("mgmt_save")),9,Al)])])])):G("",!0)])),(r(),q(H,{to:"body"},[J.value?(r(),f("div",{key:0,class:"tc-overlay",onClick:Z(I,["self"])},[e("div",Tl,[e("div",Jl,[e("span",Ol,o(B.value),1),e("div",{class:"tc-overlay-header-actions"},[e("button",{class:"tc-fullscreen-close",onClick:I},"✕")])]),e("div",Pl,[m(e("textarea",{"onUpdate:modelValue":l[26]||(l[26]=t=>p.value=t),class:re(s(_)==="system_prompt"||s(_)==="llm_config"||(v=s(_))!=null&&v.startsWith("locale_prompt")?"tc-overlay-textarea":"tc-overlay-textarea tc-overlay-textarea-text"),spellcheck:"false"},null,2),[[u,p.value]])])])])):G("",!0)]))])])])}}}),El=ye(Ll,[["__scopeId","data-v-3af0e115"]]);export{El as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as I,R as P,U as H,P as R,H as y,Q as T,A as E,X as K,y as U,G as L,z as i,g as W,d as e,m as b,B as D,J as n,e as h,Y as d,I as c,x as m,f as g,S as G,c as v,_ as J}from"./index-D8sObRrZ.js";const O={class:"mgmt-nav"},Q={class:"mgmt-nav-inner"},X=["title"],Y={class:"nav-tabs"},$={class:"nav-controls"},q=I({__name:"ManagementNav",setup(F){const C=P(),B=H(),p=R(),{user:S}=y(p),x=T(),{t:o,setLocale:M}=x,{locale:w}=y(x);function k(s){var a;const t=(a=S.value)==null?void 0:a.permissions;return t?t.some(l=>l==="*"||l.startsWith(s)):!1}const A=v(()=>k("manage:scene:")),f=v(()=>k("manage:agent:")),z=v(()=>k("manage:tool:")),N=[{value:"light",labelKey:"theme_light"},{value:"dark",labelKey:"theme_dark"}],V=v(()=>N.map(s=>({value:s.value,label:o(s.labelKey)}))),r=E(localStorage.getItem("theme")||"light");function _(s){r.value=s,document.documentElement.setAttribute("data-theme",s),localStorage.setItem("theme",s)}K(r,_),U(async()=>{await p.checkAuth(),_(r.value)});function u(s){return C.path.startsWith(s)}function j(){M(w.value==="zh"?"en":"zh")}return(s,t)=>{const a=L("router-link");return i(),W("nav",O,[e("div",Q,[b(D),e("button",{class:"nav-back",onClick:t[0]||(t[0]=l=>n(B).back()),title:n(o)("mgmt_back")},[...t[2]||(t[2]=[e("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2.2","stroke-linecap":"round","stroke-linejoin":"round"},[e("path",{d:"M19 12H5M12 19l-7-7 7-7"})],-1)])],8,X),e("div",Y,[A.value?(i(),h(a,{key:0,to:"/manage/scenes",class:m(["nav-tab",{active:u("/manage/scenes")}])},{default:d(()=>[t[3]||(t[3]=e("svg",{width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[e("rect",{x:"3",y:"3",width:"7",height:"7",rx:"1"}),e("rect",{x:"14",y:"3",width:"7",height:"7",rx:"1"}),e("rect",{x:"3",y:"14",width:"7",height:"7",rx:"1"}),e("rect",{x:"14",y:"14",width:"7",height:"7",rx:"1"})],-1)),e("span",null,c(n(o)("mgmt_scenes")),1)]),_:1},8,["class"])):g("",!0),f.value?(i(),h(a,{key:1,to:"/manage/agents",class:m(["nav-tab",{active:u("/manage/agents")}])},{default:d(()=>[t[4]||(t[4]=e("svg",{width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[e("path",{d:"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"}),e("circle",{cx:"12",cy:"7",r:"4"})],-1)),e("span",null,c(n(o)("mgmt_agents")),1)]),_:1},8,["class"])):g("",!0),z.value?(i(),h(a,{key:2,to:"/manage/tools",class:m(["nav-tab",{active:u("/manage/tools")}])},{default:d(()=>[t[5]||(t[5]=e("svg",{width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[e("path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"})],-1)),e("span",null,c(n(o)("mgmt_tools")),1)]),_:1},8,["class"])):g("",!0),f.value?(i(),h(a,{key:3,to:"/manage/providers",class:m(["nav-tab",{active:u("/manage/providers")}])},{default:d(()=>[t[6]||(t[6]=e("svg",{width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[e("rect",{x:"3",y:"3",width:"7",height:"7",rx:"1"}),e("rect",{x:"14",y:"3",width:"7",height:"7",rx:"1"}),e("rect",{x:"3",y:"14",width:"7",height:"7",rx:"1"}),e("rect",{x:"14",y:"14",width:"7",height:"7",rx:"1"})],-1)),e("span",null,c(n(o)("mgmt_providers")),1)]),_:1},8,["class"])):g("",!0)]),e("div",$,[b(G,{modelValue:r.value,"onUpdate:modelValue":t[1]||(t[1]=l=>r.value=l),options:V.value,searchable:!1},null,8,["modelValue","options"]),e("button",{class:"nav-lang-btn",onClick:j},c(n(w)==="zh"?"EN":"中"),1)])])])}}}),ee=J(q,[["__scopeId","data-v-7368f836"]]);export{ee as M};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.mgmt-nav[data-v-7368f836]{position:sticky;top:0;z-index:200;background:var(--glass-bg);border-bottom:1px solid var(--glass-border);flex-shrink:0}.mgmt-nav-inner[data-v-7368f836]{max-width:1160px;margin:0 auto;display:flex;align-items:center;gap:14px;padding:10px 16px;height:56px}.nav-back[data-v-7368f836]{display:flex;align-items:center;justify-content:center;width:36px;height:36px;background:var(--glass-highlight);border:1px solid var(--glass-border);border-radius:10px;color:var(--text-secondary);cursor:pointer;flex-shrink:0;transition:all var(--transition-duration)}.nav-back[data-v-7368f836]:hover{background:var(--accent-dim);border-color:var(--accent);color:var(--accent);transform:scale(1.05)}.nav-back[data-v-7368f836]:active{transform:scale(.95)}.nav-tabs[data-v-7368f836]{display:flex;gap:2px;flex:1;background:var(--glass-highlight);border-radius:12px;padding:4px;border:1px solid var(--glass-border)}.nav-tab[data-v-7368f836]{display:flex;align-items:center;gap:7px;padding:7px 16px;border-radius:10px;font-size:13px;font-weight:600;color:var(--text-secondary);text-decoration:none;transition:all .25s cubic-bezier(.4,0,.2,1);position:relative}.nav-tab[data-v-7368f836]:hover{background:var(--surface-bg);color:var(--text-primary)}.nav-tab.active[data-v-7368f836]{background:var(--accent);color:#fff;box-shadow:0 2px 12px #0003}.nav-tab svg[data-v-7368f836]{flex-shrink:0}.nav-controls[data-v-7368f836]{display:flex;align-items:center;gap:8px}.nav-select[data-v-7368f836]{padding:6px 10px;border:1px solid var(--glass-border);border-radius:8px;background:var(--glass-highlight);color:var(--text-primary);font-size:12px;cursor:pointer;font-family:inherit;font-weight:500;transition:border-color var(--transition-duration),box-shadow var(--transition-duration)}.nav-select[data-v-7368f836]:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-dim)}.nav-lang-btn[data-v-7368f836]{background:var(--glass-highlight);border:1px solid var(--glass-border);color:var(--text-primary);padding:6px 12px;border-radius:8px;cursor:pointer;font-size:12px;font-weight:600;font-family:inherit;min-width:36px;text-align:center;transition:all var(--transition-duration)}.nav-lang-btn[data-v-7368f836]:hover{background:var(--accent-dim);border-color:var(--accent);color:var(--accent);transform:scale(1.05)}.nav-lang-btn[data-v-7368f836]:active{transform:scale(.95)}@media(max-width:768px){.mgmt-nav-inner[data-v-7368f836]{padding:0 12px;gap:8px}.nav-tab[data-v-7368f836]{padding:7px 10px;font-size:12px;gap:4px}.nav-tab svg[data-v-7368f836]{width:13px;height:13px}}@media(max-width:480px){.nav-tab span[data-v-7368f836]{display:none}.nav-tab[data-v-7368f836]{padding:7px 12px;gap:0}.nav-tab svg[data-v-7368f836]{width:16px;height:16px;margin:0 auto}.nav-tabs[data-v-7368f836]{justify-content:center}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as I,Q as K,O as L,y as O,z as i,g as m,m as x,d as e,I as a,J as s,Z as c,W as g,$ as Q,F as C,E as M,S as q,e as J,a0 as W,V as Z,f as j,T as G,A as u,c as H,t as R,L as X,i as Y,p as ee,_ as le}from"./index-D8sObRrZ.js";import{M as te}from"./ManagementNav-B9XyX5Un.js";const ae={class:"mgmt-page"},se={class:"mgmt-page-content"},oe={class:"mgmt-header"},ne={class:"mgmt-toolbar"},de=["placeholder"],ie={key:0,class:"table-wrap"},ue={class:"mgmt-table"},re={key:1,class:"mgmt-empty"},me={key:2},ce={class:"table-wrap"},_e={class:"mgmt-table"},ve=["title"],ge=["title"],pe=["title"],he={class:"cell-actions"},be=["onClick"],fe=["onClick"],ye={class:"mgmt-pagination"},ke=["disabled"],we={class:"mgmt-page-info"},Ce=["disabled"],Me={class:"dialog dialog-wide"},Ve={class:"form-group"},Pe=["placeholder","disabled"],Se={class:"form-group"},$e=["value"],Ue={class:"form-group"},xe=["placeholder"],Te={class:"form-group"},ze=["placeholder"],Fe={class:"form-group"},Be=["placeholder"],Ne={class:"form-group"},Ae={class:"dialog-actions"},De=["disabled"],Ee=I({__name:"ProviderManagementPage",setup(Ie){const{t}=K(),h=L(),T=["openai","anthropic"],k=u([]),w=u(!1),b=u(!1),_=u(!1),p=u(!1),n=u({name:"",provider_type:"openai",api_key:"",base_url:"",default_model:"",description:""}),f=u(""),r=u(1),y=u(15),V=u(0),z=[{value:10,label:"10"},{value:15,label:"15"},{value:20,label:"20"},{value:50,label:"50"}],P=H(()=>Math.max(1,Math.ceil(V.value/y.value)));async function v(){w.value=!0;try{const d=await R({q:f.value,page:r.value,page_size:y.value});k.value=d.items,V.value=d.total}catch(d){h.show("Failed to load providers: "+d.message)}finally{w.value=!1}}function S(){r.value=1,v()}function $(d){r.value=d,v()}function F(){r.value=1,v()}function B(){p.value=!1,n.value={name:"",provider_type:"openai",api_key:"",base_url:"",default_model:"",description:""},_.value=!0}function N(d){p.value=!0,n.value={...d},_.value=!0}async function A(){if(n.value.name){b.value=!0;try{p.value&&n.value.name?await X(n.value.name,n.value):await Y(n.value),_.value=!1,await v()}catch(d){h.show("Failed to save: "+d.message)}finally{b.value=!1}}}async function D(d){if(await h.confirm(t("mgmt_confirm_delete")))try{await ee(d),await v()}catch(o){h.show("Failed to delete: "+o.message)}}function U(d,o){if(!d)return"-";const l=d.replace("T"," ").substring(0,16);return o?`${l} by ${o}`:l}return O(v),(d,o)=>(i(),m("div",ae,[x(te),e("div",se,[e("header",oe,[e("h1",null,a(s(t)("mgmt_providers")),1),e("button",{class:"btn-primary",onClick:B},a(s(t)("mgmt_new_provider")),1)]),e("div",ne,[c(e("input",{"onUpdate:modelValue":o[0]||(o[0]=l=>f.value=l),class:"mgmt-search",placeholder:s(t)("mgmt_search_placeholder"),onKeyup:Q(S,["enter"])},null,40,de),[[g,f.value]]),e("button",{class:"btn-search",onClick:S},a(s(t)("mgmt_search")),1)]),w.value?(i(),m("div",ie,[e("table",ue,[e("thead",null,[e("tr",null,[e("th",null,a(s(t)("mgmt_name")),1),e("th",null,a(s(t)("mgmt_provider_type")),1),e("th",null,a(s(t)("mgmt_base_url")),1),e("th",null,a(s(t)("mgmt_default_model")),1),e("th",null,a(s(t)("mgmt_created_at")),1),e("th",null,a(s(t)("mgmt_actions")),1)])]),e("tbody",null,[(i(),m(C,null,M(5,l=>e("tr",{key:l,class:"mgmt-skeleton-row"},[...o[12]||(o[12]=[e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"60%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"40%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"50%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"40%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell mgmt-skeleton-cell-sm",style:{width:"65%"}})],-1),e("td",null,[e("div",{class:"mgmt-skeleton-cell",style:{width:"50%"}})],-1)])])),64))])])])):k.value.length===0?(i(),m("div",re,a(f.value?s(t)("mgmt_no_results"):s(t)("mgmt_no_providers")),1)):(i(),m("div",me,[e("div",ce,[e("table",_e,[e("thead",null,[e("tr",null,[e("th",null,a(s(t)("mgmt_name")),1),e("th",null,a(s(t)("mgmt_provider_type")),1),e("th",null,a(s(t)("mgmt_base_url")),1),e("th",null,a(s(t)("mgmt_default_model")),1),e("th",null,a(s(t)("mgmt_created_at")),1),e("th",null,a(s(t)("mgmt_actions")),1)])]),e("tbody",null,[(i(!0),m(C,null,M(k.value,l=>(i(),m("tr",{key:l.name},[e("td",{title:l.name},[e("code",null,a(l.name),1)],8,ve),e("td",null,[e("code",null,a(l.provider_type),1)]),e("td",null,[e("code",{class:"cell-url",title:l.base_url||"-"},a(l.base_url||"-"),9,ge)]),e("td",null,a(l.default_model||"-"),1),e("td",{class:"cell-audit",title:U(l.created_at,l.created_by)},a(U(l.created_at,l.created_by)),9,pe),e("td",he,[e("button",{class:"btn-action",onClick:E=>N(l)},a(s(t)("mgmt_edit")),9,be),e("button",{class:"btn-action btn-danger",onClick:E=>D(l.name)},a(s(t)("mgmt_delete")),9,fe)])]))),128))])])]),e("div",ye,[e("button",{class:"btn-page",disabled:r.value<=1,onClick:o[1]||(o[1]=l=>$(r.value-1))},a(s(t)("mgmt_prev_page")),9,ke),e("span",we,a(r.value)+" / "+a(P.value),1),e("button",{class:"btn-page",disabled:r.value>=P.value,onClick:o[2]||(o[2]=l=>$(r.value+1))},a(s(t)("mgmt_next_page")),9,Ce),x(q,{modelValue:y.value,"onUpdate:modelValue":o[3]||(o[3]=l=>y.value=l),modelModifiers:{number:!0},options:z,searchable:!1,onChange:F},null,8,["modelValue"])])]))]),(i(),J(G,{to:"body"},[_.value?(i(),m("div",{key:0,class:"dialog-overlay",onClick:o[11]||(o[11]=W(l=>_.value=!1,["self"]))},[e("div",Me,[e("h2",null,a(p.value?s(t)("mgmt_edit_provider"):s(t)("mgmt_new_provider_title")),1),e("div",Ve,[e("label",null,a(s(t)("mgmt_name")),1),c(e("input",{"onUpdate:modelValue":o[4]||(o[4]=l=>n.value.name=l),placeholder:s(t)("mgmt_placeholder_name"),disabled:p.value},null,8,Pe),[[g,n.value.name]])]),e("div",Se,[e("label",null,a(s(t)("mgmt_provider_type")),1),c(e("select",{"onUpdate:modelValue":o[5]||(o[5]=l=>n.value.provider_type=l),class:"mgmt-dialog-select"},[(i(),m(C,null,M(T,l=>e("option",{key:l,value:l},a(l),9,$e)),64))],512),[[Z,n.value.provider_type]])]),e("div",Ue,[e("label",null,a(s(t)("mgmt_api_key")),1),c(e("input",{"onUpdate:modelValue":o[6]||(o[6]=l=>n.value.api_key=l),type:"password",placeholder:s(t)("mgmt_api_key_placeholder")},null,8,xe),[[g,n.value.api_key]])]),e("div",Te,[e("label",null,a(s(t)("mgmt_base_url")),1),c(e("input",{"onUpdate:modelValue":o[7]||(o[7]=l=>n.value.base_url=l),placeholder:s(t)("mgmt_base_url_placeholder")},null,8,ze),[[g,n.value.base_url]])]),e("div",Fe,[e("label",null,a(s(t)("mgmt_default_model")),1),c(e("input",{"onUpdate:modelValue":o[8]||(o[8]=l=>n.value.default_model=l),placeholder:s(t)("mgmt_model_placeholder")},null,8,Be),[[g,n.value.default_model]])]),e("div",Ne,[e("label",null,a(s(t)("mgmt_description")),1),c(e("textarea",{"onUpdate:modelValue":o[9]||(o[9]=l=>n.value.description=l),rows:"2"},null,512),[[g,n.value.description]])]),e("div",Ae,[e("button",{class:"btn-action",onClick:o[10]||(o[10]=l=>_.value=!1)},a(s(t)("mgmt_cancel")),1),e("button",{class:"btn-primary",onClick:A,disabled:!n.value.name||b.value},a(b.value?s(t)("mgmt_saving"):s(t)("mgmt_save")),9,De)])])])):j("",!0)]))]))}}),Oe=le(Ee,[["__scopeId","data-v-6a84bb90"]]);export{Oe as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.mgmt-dialog-select[data-v-6a84bb90]{display:block;width:100%;padding:10px 14px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-highlight);color:var(--text-primary);font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color var(--transition-duration),box-shadow var(--transition-duration)}.mgmt-dialog-select[data-v-6a84bb90]:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-dim)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.detail-header-actions[data-v-a0db1094]{display:flex;align-items:center;gap:8px}.btn-chat[data-v-a0db1094]{background:var(--accent-dim);color:var(--accent);border-color:var(--accent)}.btn-chat[data-v-a0db1094]:hover{background:var(--accent);color:#fff}.detail-icon[data-v-a0db1094]{font-size:28px;line-height:1}.detail-meta-row[data-v-a0db1094]{display:flex;align-items:center;gap:10px;margin-bottom:8px}.detail-id[data-v-a0db1094]{font-size:12px;font-family:var(--font-mono);opacity:.7}.agent-card-info[data-v-a0db1094]{display:flex;align-items:center;gap:8px;flex:1;min-width:0}
|