File size: 6,923 Bytes
d9d3238
 
 
 
 
 
 
8e0cf1c
d9d3238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8e0cf1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9d3238
 
 
 
 
 
8e0cf1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9d3238
 
 
 
 
 
 
 
 
8e0cf1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9d3238
 
 
 
 
 
8e0cf1c
d9d3238
 
 
 
 
 
 
 
 
 
 
 
 
8e0cf1c
 
d9d3238
 
8e0cf1c
d9d3238
 
 
 
 
 
 
0469641
 
 
 
 
 
 
 
a0e4d9d
 
d9d3238
 
 
 
0469641
d9d3238
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import json
import os
import uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import urlparse

from agent import run_agent_mode, tool_search_kb


HERE = Path(__file__).resolve().parent
WEB_DIR = HERE / "web"
OUT_DIR = HERE / "out"
KB_DIR = HERE / "kb"


def _read_body_json(handler: BaseHTTPRequestHandler):
    length = int(handler.headers.get("Content-Length") or "0")
    raw = handler.rfile.read(length) if length > 0 else b""
    if not raw:
        return {}
    try:
        return json.loads(raw.decode("utf-8"))
    except Exception:
        return None


class Handler(BaseHTTPRequestHandler):
    server_version = "AgentDemo/1.0"

    def log_message(self, format, *args):
        return

    def _send(self, status: int, content_type: str, body: bytes):
        self.send_response(status)
        self.send_header("Content-Type", content_type)
        self.send_header("Content-Length", str(len(body)))
        self.send_header("Cache-Control", "no-store")
        self.end_headers()
        self.wfile.write(body)

    def _send_json(self, status: int, obj):
        body = json.dumps(obj, ensure_ascii=False).encode("utf-8")
        self._send(status, "application/json; charset=utf-8", body)

    def do_GET(self):
        u = urlparse(self.path)
        path = u.path or "/"

        if path == "/":
            p = WEB_DIR / "index.html"
            self._send(200, "text/html; charset=utf-8", p.read_bytes())
            return

        if path == "/assets/app.js":
            p = WEB_DIR / "app.js"
            self._send(200, "application/javascript; charset=utf-8", p.read_bytes())
            return

        if path == "/assets/app.css":
            p = WEB_DIR / "app.css"
            self._send(200, "text/css; charset=utf-8", p.read_bytes())
            return

        if path == "/api/health":
            self._send_json(200, {"ok": True, "service": "agent-demo", "version": "1.0"})
            return

        if path == "/api/kb/list":
            items = []
            for p in sorted(KB_DIR.glob("**/*.md")):
                try:
                    txt = p.read_text(encoding="utf-8").splitlines()
                    title = ""
                    for ln in txt[:10]:
                        ln = ln.strip()
                        if ln.startswith("#"):
                            title = ln.lstrip("#").strip()
                            break
                    items.append({"path": str(p.relative_to(HERE)), "title": title or p.name})
                except Exception:
                    continue
            self._send_json(200, {"ok": True, "items": items})
            return

        self._send(404, "text/plain; charset=utf-8", "not found".encode("utf-8"))

    def do_POST(self):
        u = urlparse(self.path)
        path = u.path or "/"

        if path == "/api/kb/search":
            payload = _read_body_json(self)
            if payload is None:
                self._send_json(400, {"ok": False, "error": "请求 JSON 解析失败"})
                return
            query = str(payload.get("query") or "").strip()
            if not query:
                self._send_json(400, {"ok": False, "error": "query 不能为空"})
                return
            res = tool_search_kb(query=query, kb_dir=str(KB_DIR), top_k=int(payload.get("top_k") or 6))
            if not res.ok:
                self._send_json(500, {"ok": False, "error": res.output})
                return
            try:
                items = json.loads(res.output)
            except Exception:
                items = []
            self._send_json(200, {"ok": True, "items": items})
            return

        if path != "/api/run":
            self._send(404, "text/plain; charset=utf-8", "not found".encode("utf-8"))
            return

        payload = _read_body_json(self)
        if payload is None:
            self._send_json(400, {"ok": False, "error": "请求 JSON 解析失败"})
            return

        mode = str(payload.get("mode") or "general").strip()
        if mode == "lead_followup":
            lead = payload.get("lead") or {}
            if not isinstance(lead, dict):
                self._send_json(400, {"ok": False, "error": "lead 必须是对象"})
                return
            company = str(lead.get("company") or "").strip()
            pain = str(lead.get("pain_points") or "").strip()
            if not company and not pain:
                self._send_json(400, {"ok": False, "error": "至少填写“公司/组织”或“主要诉求/痛点”"})
                return
        else:
            goal = str(payload.get("goal") or "").strip()
            if not goal:
                self._send_json(400, {"ok": False, "error": "goal 不能为空"})
                return
            if len(goal) > 600:
                self._send_json(400, {"ok": False, "error": "goal 过长(最多 600 字符)"})
                return

        OUT_DIR.mkdir(parents=True, exist_ok=True)
        report_name = f"report_{uuid.uuid4().hex[:12]}.md"
        out_report = str(OUT_DIR / report_name)

        try:
            result = run_agent_mode(mode=mode, payload=payload, kb_dir=str(KB_DIR), out_report=out_report, max_rounds=3)
        except Exception as e:
            self._send_json(500, {"ok": False, "error": f"执行失败: {e}"})
            return

        try:
            report_text = Path(out_report).read_text(encoding="utf-8") if os.path.exists(out_report) else ""
        except Exception:
            report_text = ""

        self._send_json(
            200,
            {
                "ok": True,
                "mode": result.get("mode") or mode,
                "goal": result.get("goal"),
                "final_answer": result.get("final_answer"),
                "round_logs": result.get("round_logs"),
                "structured": result.get("structured"),
                "artifact_path": out_report,
                "report_text": report_text,
            },
        )


def main():
    host = "0.0.0.0"
    if "PORT" in os.environ:
        port = int(os.environ["PORT"])
        httpd = ThreadingHTTPServer((host, port), Handler)
        print(f"本地演示页已启动: http://127.0.0.1:{port}/")
        httpd.serve_forever()
        return

    in_space = any(k.startswith("SPACE_") for k in os.environ.keys())
    base_port = 7860 if in_space else 8000
    last_err = None
    for port in range(base_port, base_port + 20):
        try:
            httpd = ThreadingHTTPServer((host, port), Handler)
            print(f"本地演示页已启动: http://127.0.0.1:{port}/")
            httpd.serve_forever()
            return
        except OSError as e:
            last_err = e
            continue
    raise SystemExit(f"无法绑定端口(从 {base_port} 起尝试 20 个):{last_err}")


if __name__ == "__main__":
    main()