✨ feat: 对aide decide进行部分调整
This commit is contained in:
@@ -6,6 +6,7 @@ import json
|
||||
import socket
|
||||
import time
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
from aide.core import output
|
||||
from aide.core.config import ConfigManager
|
||||
@@ -30,7 +31,10 @@ class DecideServer:
|
||||
self.storage = storage
|
||||
self.port = 3721
|
||||
self.timeout = 0
|
||||
self.web_dir = root / "aide" / "decide" / "web"
|
||||
self.bind = "127.0.0.1"
|
||||
self.url = ""
|
||||
# web 资源位于 aide 包目录下,而非项目根目录
|
||||
self.web_dir = Path(__file__).parent / "web"
|
||||
self.should_close = False
|
||||
self.close_reason: str | None = None
|
||||
self.httpd: DecideHTTPServer | None = None
|
||||
@@ -40,6 +44,8 @@ class DecideServer:
|
||||
config = ConfigManager(self.root).load_config()
|
||||
start_port = _get_int(config, "decide", "port", default=3721)
|
||||
self.timeout = _get_int(config, "decide", "timeout", default=0)
|
||||
self.bind = _get_str(config, "decide", "bind", default="127.0.0.1")
|
||||
self.url = _get_str(config, "decide", "url", default="")
|
||||
end_port = start_port + 9
|
||||
available = self._find_available_port(start_port)
|
||||
if available is None:
|
||||
@@ -54,11 +60,17 @@ class DecideServer:
|
||||
stop_callback=self.stop,
|
||||
)
|
||||
RequestHandler = self._build_request_handler(handlers)
|
||||
self.httpd = DecideHTTPServer(("127.0.0.1", self.port), RequestHandler, handlers)
|
||||
self.httpd = DecideHTTPServer((self.bind, self.port), RequestHandler, handlers)
|
||||
self.httpd.timeout = 1.0
|
||||
|
||||
# 生成访问地址:优先使用自定义 url,否则自动生成
|
||||
if self.url:
|
||||
access_url = self.url
|
||||
else:
|
||||
access_url = f"http://localhost:{self.port}"
|
||||
|
||||
output.info("Web 服务已启动")
|
||||
output.info(f"请访问: http://localhost:{self.port}")
|
||||
output.info(f"请访问: {access_url}")
|
||||
output.info("等待用户完成决策...")
|
||||
|
||||
self._serve_forever()
|
||||
@@ -90,7 +102,7 @@ class DecideServer:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
sock.bind(("127.0.0.1", port))
|
||||
sock.bind((self.bind, port))
|
||||
return port
|
||||
except OSError:
|
||||
continue
|
||||
@@ -138,7 +150,7 @@ class DecideServer:
|
||||
content_length = int(length) if length else 0
|
||||
except ValueError:
|
||||
self._send_response(
|
||||
(400, handlers._cors_headers({"Content-Type": "application/json; charset=utf-8"}), b'{"error":"决策数据无效","detail":"无效的 Content-Length"}')
|
||||
(400, handlers._cors_headers({"Content-Type": "application/json; charset=utf-8"}), '{"error":"决策数据无效","detail":"无效的 Content-Length"}'.encode("utf-8"))
|
||||
)
|
||||
return
|
||||
if content_length > 1024 * 1024:
|
||||
@@ -146,7 +158,7 @@ class DecideServer:
|
||||
(
|
||||
413,
|
||||
handlers._cors_headers({"Content-Type": "application/json; charset=utf-8"}),
|
||||
b'{"error":"请求体过大","detail":"单次提交限制 1MB"}',
|
||||
'{"error":"请求体过大","detail":"单次提交限制 1MB"}'.encode("utf-8"),
|
||||
)
|
||||
)
|
||||
return
|
||||
@@ -156,10 +168,10 @@ class DecideServer:
|
||||
response = handlers.handle(method, self.path, body)
|
||||
except Exception as exc: # pragma: no cover - 兜底防御
|
||||
payload = (
|
||||
b'{"error":"服务器内部错误","detail":'
|
||||
+ json.dumps(str(exc), ensure_ascii=False).encode("utf-8")
|
||||
+ b"}"
|
||||
)
|
||||
'{"error":"服务器内部错误","detail":'
|
||||
+ json.dumps(str(exc), ensure_ascii=False)
|
||||
+ "}"
|
||||
).encode("utf-8")
|
||||
response = (
|
||||
500,
|
||||
handlers._cors_headers({"Content-Type": "application/json; charset=utf-8"}),
|
||||
@@ -200,3 +212,14 @@ def _get_int(config: dict, section: str, key: str, default: int) -> int:
|
||||
except Exception:
|
||||
return default
|
||||
return default
|
||||
|
||||
|
||||
def _get_str(config: dict, section: str, key: str, default: str) -> str:
|
||||
try:
|
||||
section_data = config.get(section, {}) if isinstance(config, dict) else {}
|
||||
value = section_data.get(key, default)
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
except Exception:
|
||||
return default
|
||||
return default
|
||||
|
||||
@@ -14,6 +14,13 @@ async function init() {
|
||||
AppState.source = data.source || "";
|
||||
AppState.items = Array.isArray(data.items) ? data.items : [];
|
||||
|
||||
// 如果有推荐项,默认选中推荐项
|
||||
AppState.items.forEach((item) => {
|
||||
if (item.recommend) {
|
||||
AppState.decisions[item.id] = item.recommend;
|
||||
}
|
||||
});
|
||||
|
||||
renderItems(data);
|
||||
bindEvents();
|
||||
} catch (error) {
|
||||
|
||||
@@ -265,6 +265,10 @@ body {
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.success-overlay[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background: var(--color-card);
|
||||
padding: var(--spacing-xl);
|
||||
@@ -294,6 +298,10 @@ body {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.error-toast[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@@ -132,8 +132,18 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
|
||||
# aide decide
|
||||
decide_parser = subparsers.add_parser("decide", help="待定项确认与决策记录")
|
||||
decide_parser.add_argument("data", help="待定项 JSON 数据或 result")
|
||||
decide_parser.set_defaults(func=handle_decide)
|
||||
decide_subparsers = decide_parser.add_subparsers(dest="decide_cmd")
|
||||
|
||||
# aide decide submit '<json>'
|
||||
decide_submit_parser = decide_subparsers.add_parser("submit", help="提交待定项数据并启动 Web 服务")
|
||||
decide_submit_parser.add_argument("data", help="待定项 JSON 数据")
|
||||
decide_submit_parser.set_defaults(func=handle_decide_submit)
|
||||
|
||||
# aide decide result
|
||||
decide_result_parser = decide_subparsers.add_parser("result", help="获取用户决策结果")
|
||||
decide_result_parser.set_defaults(func=handle_decide_result)
|
||||
|
||||
decide_parser.set_defaults(func=handle_decide_help)
|
||||
|
||||
parser.add_argument("--version", action="version", version="aide dev")
|
||||
return parser
|
||||
@@ -288,8 +298,27 @@ def handle_flow_error(args: argparse.Namespace) -> bool:
|
||||
return tracker.error(args.description)
|
||||
|
||||
|
||||
def handle_decide(args: argparse.Namespace) -> bool:
|
||||
return cmd_decide(args)
|
||||
def handle_decide_help(args: argparse.Namespace) -> bool:
|
||||
print("usage: aide decide {submit,result} ...")
|
||||
print("")
|
||||
print("子命令:")
|
||||
print(" submit <json> 提交待定项数据并启动 Web 服务")
|
||||
print(" result 获取用户决策结果")
|
||||
print("")
|
||||
print("示例:")
|
||||
print(" aide decide submit '{\"task\":\"...\",\"source\":\"...\",\"items\":[...]}'")
|
||||
print(" aide decide result")
|
||||
return True
|
||||
|
||||
|
||||
def handle_decide_submit(args: argparse.Namespace) -> bool:
|
||||
from aide.decide import cmd_decide_submit
|
||||
return cmd_decide_submit(args.data)
|
||||
|
||||
|
||||
def handle_decide_result(args: argparse.Namespace) -> bool:
|
||||
from aide.decide import cmd_decide_result
|
||||
return cmd_decide_result()
|
||||
|
||||
|
||||
def _parse_value(raw: str) -> Any:
|
||||
|
||||
@@ -119,24 +119,46 @@ stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 三、端口配置
|
||||
## 三、网络配置
|
||||
|
||||
### 3.1 端口探测策略
|
||||
### 3.1 配置项
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `decide.port` | 3721 | 起始端口 |
|
||||
| `decide.bind` | `"127.0.0.1"` | 监听地址,设为 `"0.0.0.0"` 可允许外部访问 |
|
||||
| `decide.url` | `""` | 自定义访问地址,为空时自动生成 `http://localhost:{port}` |
|
||||
| 最大尝试次数 | 10 | 从起始端口开始尝试 |
|
||||
|
||||
### 3.2 配置示例
|
||||
|
||||
```toml
|
||||
[decide]
|
||||
port = 3721
|
||||
bind = "0.0.0.0" # 监听所有网络接口
|
||||
url = "http://example.dev.net:3721" # 自定义访问地址
|
||||
```
|
||||
|
||||
### 3.3 端口探测策略
|
||||
|
||||
**探测逻辑**:
|
||||
|
||||
1. 从 `decide.port` 开始
|
||||
2. 尝试绑定端口
|
||||
2. 尝试绑定到 `decide.bind:port`
|
||||
3. 若失败,尝试下一个端口
|
||||
4. 最多尝试 10 次
|
||||
5. 全部失败则返回错误
|
||||
|
||||
### 3.2 端口占用检测
|
||||
### 3.4 访问地址生成
|
||||
|
||||
```
|
||||
if decide.url 不为空:
|
||||
access_url = decide.url
|
||||
else:
|
||||
access_url = f"http://localhost:{actual_port}"
|
||||
```
|
||||
|
||||
### 3.5 端口占用检测
|
||||
|
||||
```
|
||||
check_port_available(port: int) -> bool:
|
||||
@@ -144,7 +166,7 @@ check_port_available(port: int) -> bool:
|
||||
检查端口是否可用
|
||||
|
||||
1. 创建 socket
|
||||
2. 尝试绑定到 127.0.0.1:port
|
||||
2. 尝试绑定到 {bind}:{port}
|
||||
3. 成功则端口可用,关闭 socket 返回 True
|
||||
4. 失败则端口被占用,返回 False
|
||||
"""
|
||||
|
||||
@@ -167,10 +167,22 @@ manager = "pnpm"
|
||||
|------|------|--------|------|
|
||||
| `port` | int | `3721` | Web 服务起始端口,端口被占用时向后探测最多 10 次 |
|
||||
| `timeout` | int | `0` | 服务超时时间(秒),0 表示不启用超时 |
|
||||
| `bind` | string | `"127.0.0.1"` | 服务监听地址,设为 `"0.0.0.0"` 可允许外部访问 |
|
||||
| `url` | string | `""` | 自定义访问地址,为空时自动生成 `http://localhost:{port}` |
|
||||
|
||||
**使用场景**:
|
||||
- `aide decide '<json>'` 读取 `port` 作为起始端口
|
||||
- `aide decide '<json>'` 读取 `timeout` 控制服务最长等待时间
|
||||
- `aide decide '<json>'` 读取 `bind` 作为监听地址
|
||||
- `aide decide '<json>'` 读取 `url` 作为输出的访问地址(支持自定义域名)
|
||||
|
||||
**示例配置**:
|
||||
```toml
|
||||
[decide]
|
||||
port = 3721
|
||||
bind = "0.0.0.0" # 监听所有网络接口
|
||||
url = "http://example.dev.net:3721" # 自定义访问地址
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user