zrocclaw 0.0.3 → 0.0.6
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.
- package/.turbo/turbo-build.log +14 -0
- package/bin/zrocclaw.js +68 -1
- package/dist/server.js +223 -0
- package/nodemon.json +6 -0
- package/package.json +28 -14
- package/src/app.ts +35 -0
- package/src/models/session.ts +78 -0
- package/src/routes/chat.ts +67 -0
- package/src/routes/config.ts +67 -0
- package/src/server.ts +9 -0
- package/src/static/assets/Chat-9YnlTybN.js +15 -0
- package/src/static/assets/Chat-DUc2YNA1.css +1 -0
- package/src/static/assets/Config-iHT4yGKm.js +1 -0
- package/src/static/assets/Welcome-ChEkTj3S.js +1 -0
- package/src/static/assets/Welcome-x1XGuNl0.css +1 -0
- package/src/static/assets/_plugin-vue_export-helper-S3RvzygF.js +1 -0
- package/src/static/assets/index-CPxNZVgF.js +2 -0
- package/src/static/assets/index-Dh0OZbcj.css +2 -0
- package/src/static/assets/request-CPZhIesa.js +6 -0
- package/src/static/assets/runtime-core.esm-bundler-CWHhnmHT.js +1 -0
- package/src/static/favicon.svg +1 -0
- package/src/static/icons.svg +24 -0
- package/src/static/index.html +15 -0
- package/tsconfig.json +17 -0
- package/tsup.config.ts +12 -0
- package/README.md +0 -6
- package/dist/index.js +0 -79
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> zrocclaw@0.0.6 build /Users/zroc/Desktop/Zroc/AI/zrocclaw/apps/gateway
|
|
4
|
+
> tsup
|
|
5
|
+
|
|
6
|
+
[34mCLI[39m Building entry: src/server.ts
|
|
7
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
8
|
+
[34mCLI[39m tsup v8.5.1
|
|
9
|
+
[34mCLI[39m Using tsup config: /Users/zroc/Desktop/Zroc/AI/zrocclaw/apps/gateway/tsup.config.ts
|
|
10
|
+
[34mCLI[39m Target: node18
|
|
11
|
+
[34mCLI[39m Cleaning output folder
|
|
12
|
+
[34mCJS[39m Build start
|
|
13
|
+
[32mCJS[39m [1mdist/server.js [22m[32m7.68 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 7ms
|
package/bin/zrocclaw.js
CHANGED
|
@@ -1,2 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
// Set PM2_HOME to a local directory to avoid permission issues in sandboxed environments
|
|
5
|
+
if (!process.env.PM2_HOME) {
|
|
6
|
+
process.env.PM2_HOME = path.resolve(__dirname, '../../.pm2');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { program } = require('commander');
|
|
10
|
+
const pm2 = require('pm2');
|
|
11
|
+
const pkg = require('../package.json');
|
|
12
|
+
|
|
13
|
+
const APP_NAME = 'zrocclaw';
|
|
14
|
+
const SCRIPT_PATH = path.resolve(__dirname, '../dist/server.js');
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('zrocclaw')
|
|
18
|
+
.description('ZrocClaw Gateway CLI')
|
|
19
|
+
.version(pkg.version);
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command('start')
|
|
23
|
+
.description('启动网关服务')
|
|
24
|
+
.action(() => {
|
|
25
|
+
pm2.connect((err) => {
|
|
26
|
+
if (err) {
|
|
27
|
+
console.error('连接 PM2 失败:', err);
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pm2.start({
|
|
32
|
+
name: APP_NAME,
|
|
33
|
+
script: SCRIPT_PATH,
|
|
34
|
+
env: {
|
|
35
|
+
NODE_ENV: 'production'
|
|
36
|
+
}
|
|
37
|
+
}, (err, apps) => {
|
|
38
|
+
pm2.disconnect();
|
|
39
|
+
if (err) {
|
|
40
|
+
console.error('网关服务启动失败:', err.message || err);
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}
|
|
43
|
+
console.log(`✅ 网关服务 [${APP_NAME}] 已成功启动!`);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
program
|
|
49
|
+
.command('stop')
|
|
50
|
+
.description('关闭网关服务')
|
|
51
|
+
.action(() => {
|
|
52
|
+
pm2.connect((err) => {
|
|
53
|
+
if (err) {
|
|
54
|
+
console.error('连接 PM2 失败:', err);
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pm2.stop(APP_NAME, (err, apps) => {
|
|
59
|
+
pm2.disconnect();
|
|
60
|
+
if (err) {
|
|
61
|
+
console.error('网关服务关闭失败 (可能服务并未运行):', err.message || err);
|
|
62
|
+
process.exit(2);
|
|
63
|
+
}
|
|
64
|
+
console.log(`⛔ 网关服务 [${APP_NAME}] 已成功关闭!`);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
program.parse(process.argv);
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/app.ts
|
|
26
|
+
var import_express3 = __toESM(require("express"));
|
|
27
|
+
var import_cors = __toESM(require("cors"));
|
|
28
|
+
var import_helmet = __toESM(require("helmet"));
|
|
29
|
+
|
|
30
|
+
// src/routes/config.ts
|
|
31
|
+
var import_express = require("express");
|
|
32
|
+
var import_fileManager = require("@zrocclaw/core/fileManager");
|
|
33
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
34
|
+
var import_path = __toESM(require("path"));
|
|
35
|
+
var router = (0, import_express.Router)();
|
|
36
|
+
router.get("/", (req, res) => {
|
|
37
|
+
res.json({ configPath: (0, import_fileManager.getConfigPath)() });
|
|
38
|
+
});
|
|
39
|
+
var configDir = (0, import_fileManager.getConfigPath)();
|
|
40
|
+
var configFilePath = import_path.default.join(configDir, "model.json");
|
|
41
|
+
router.get("/model", async (req, res) => {
|
|
42
|
+
try {
|
|
43
|
+
try {
|
|
44
|
+
await import_promises.default.access(configFilePath);
|
|
45
|
+
const data = await import_promises.default.readFile(configFilePath, "utf-8");
|
|
46
|
+
res.json(JSON.parse(data));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error.code === "ENOENT") {
|
|
49
|
+
await import_promises.default.mkdir(configDir, { recursive: true });
|
|
50
|
+
await import_promises.default.writeFile(configFilePath, "{}", "utf-8");
|
|
51
|
+
res.json({});
|
|
52
|
+
} else {
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error("\u83B7\u53D6\u6A21\u578B\u5931\u8D25:", error);
|
|
58
|
+
res.status(500).json({ error: "\u83B7\u53D6\u6A21\u578B\u5931\u8D25" });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
router.post("/model", async (req, res) => {
|
|
62
|
+
try {
|
|
63
|
+
await import_promises.default.mkdir(configDir, { recursive: true });
|
|
64
|
+
let currentConfig = {};
|
|
65
|
+
try {
|
|
66
|
+
const data = await import_promises.default.readFile(configFilePath, "utf-8");
|
|
67
|
+
currentConfig = JSON.parse(data);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error.code !== "ENOENT") {
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const newConfig = { ...currentConfig, ...req.body };
|
|
74
|
+
await import_promises.default.writeFile(configFilePath, JSON.stringify(newConfig, null, 2), "utf-8");
|
|
75
|
+
res.json(newConfig);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error("\u4FEE\u6539\u6A21\u578B\u5931\u8D25:", error);
|
|
78
|
+
res.status(500).json({ error: "\u4FEE\u6539\u6A21\u578B\u5931\u8D25" });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
var config_default = router;
|
|
82
|
+
|
|
83
|
+
// src/routes/chat.ts
|
|
84
|
+
var import_express2 = require("express");
|
|
85
|
+
var import_agents = require("@zrocclaw/core/agents");
|
|
86
|
+
var import_fileManager3 = require("@zrocclaw/core/fileManager");
|
|
87
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
88
|
+
var import_path3 = __toESM(require("path"));
|
|
89
|
+
var import_crypto = __toESM(require("crypto"));
|
|
90
|
+
|
|
91
|
+
// src/models/session.ts
|
|
92
|
+
var import_fileManager2 = require("@zrocclaw/core/fileManager");
|
|
93
|
+
var import_fs = __toESM(require("fs"));
|
|
94
|
+
var import_path2 = __toESM(require("path"));
|
|
95
|
+
var SessionModel = class _SessionModel {
|
|
96
|
+
constructor() {
|
|
97
|
+
this.filePath = import_path2.default.join((0, import_fileManager2.getConfigPath)(), "session.json");
|
|
98
|
+
this.data = {
|
|
99
|
+
sessionId: null,
|
|
100
|
+
sessionData: {}
|
|
101
|
+
};
|
|
102
|
+
this.load();
|
|
103
|
+
}
|
|
104
|
+
static getInstance() {
|
|
105
|
+
if (!_SessionModel.instance) {
|
|
106
|
+
_SessionModel.instance = new _SessionModel();
|
|
107
|
+
}
|
|
108
|
+
return _SessionModel.instance;
|
|
109
|
+
}
|
|
110
|
+
load() {
|
|
111
|
+
if (import_fs.default.existsSync(this.filePath)) {
|
|
112
|
+
try {
|
|
113
|
+
const fileContent = import_fs.default.readFileSync(this.filePath, "utf-8");
|
|
114
|
+
const parsed = JSON.parse(fileContent);
|
|
115
|
+
this.data = { ...this.data, ...parsed };
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("Failed to parse session.json:", error);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
this.save();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
save() {
|
|
124
|
+
try {
|
|
125
|
+
const dir = import_path2.default.dirname(this.filePath);
|
|
126
|
+
if (!import_fs.default.existsSync(dir)) {
|
|
127
|
+
import_fs.default.mkdirSync(dir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
import_fs.default.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error("Failed to save session.json:", error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
getSessionId() {
|
|
135
|
+
return this.data.sessionId;
|
|
136
|
+
}
|
|
137
|
+
setSessionId(id) {
|
|
138
|
+
this.data.sessionId = id;
|
|
139
|
+
this.save();
|
|
140
|
+
}
|
|
141
|
+
getSessionData() {
|
|
142
|
+
return this.data.sessionData;
|
|
143
|
+
}
|
|
144
|
+
setSessionData(data) {
|
|
145
|
+
this.data.sessionData = data;
|
|
146
|
+
this.save();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var sessionModel = SessionModel.getInstance();
|
|
150
|
+
var session_default = sessionModel;
|
|
151
|
+
|
|
152
|
+
// src/routes/chat.ts
|
|
153
|
+
var configDir2 = (0, import_fileManager3.getConfigPath)();
|
|
154
|
+
var configFilePath2 = import_path3.default.join(configDir2, "model.json");
|
|
155
|
+
var router2 = (0, import_express2.Router)();
|
|
156
|
+
router2.get("/", (req, res) => {
|
|
157
|
+
res.json({ status: "OK" });
|
|
158
|
+
});
|
|
159
|
+
router2.post("/stream", async (req, res) => {
|
|
160
|
+
try {
|
|
161
|
+
const query = req.body.query;
|
|
162
|
+
const thread_id = req.body.thread_id;
|
|
163
|
+
if (!query || !thread_id) {
|
|
164
|
+
return res.status(400).json({ error: "Missing query or thread_id in request body" });
|
|
165
|
+
}
|
|
166
|
+
const configContent = await import_promises2.default.readFile(configFilePath2, "utf-8");
|
|
167
|
+
const config = JSON.parse(configContent);
|
|
168
|
+
const modelConfig = config.defaultModel;
|
|
169
|
+
if (!modelConfig) {
|
|
170
|
+
return res.status(500).json({ error: "defaultModel not found in config" });
|
|
171
|
+
}
|
|
172
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
173
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
174
|
+
res.setHeader("Connection", "keep-alive");
|
|
175
|
+
const stream = (0, import_agents.browser_stream_invoke)(query, thread_id, modelConfig);
|
|
176
|
+
for await (const chunk of stream) {
|
|
177
|
+
res.write(chunk);
|
|
178
|
+
}
|
|
179
|
+
res.end();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error("Stream error:", error);
|
|
182
|
+
if (!res.headersSent) {
|
|
183
|
+
res.status(500).json({ error: "Internal server error" });
|
|
184
|
+
} else {
|
|
185
|
+
res.end();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
router2.get("/session", (req, res) => {
|
|
190
|
+
const isNew = req.query.new === "true";
|
|
191
|
+
let sessionId = session_default.getSessionId();
|
|
192
|
+
if (!sessionId || isNew) {
|
|
193
|
+
sessionId = import_crypto.default.randomUUID();
|
|
194
|
+
session_default.setSessionId(sessionId);
|
|
195
|
+
}
|
|
196
|
+
res.json({ sessionId });
|
|
197
|
+
});
|
|
198
|
+
var chat_default = router2;
|
|
199
|
+
|
|
200
|
+
// src/app.ts
|
|
201
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
202
|
+
var import_path4 = __toESM(require("path"));
|
|
203
|
+
import_dotenv.default.config();
|
|
204
|
+
var app = (0, import_express3.default)();
|
|
205
|
+
app.use((0, import_helmet.default)());
|
|
206
|
+
app.use((0, import_cors.default)());
|
|
207
|
+
app.use(import_express3.default.json());
|
|
208
|
+
app.get("/health", (req, res) => {
|
|
209
|
+
res.json({ status: "OK", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
210
|
+
});
|
|
211
|
+
app.use("/config", config_default);
|
|
212
|
+
app.use("/chat", chat_default);
|
|
213
|
+
if (true) {
|
|
214
|
+
app.use("/web", import_express3.default.static(import_path4.default.join(__dirname, "../src/static")));
|
|
215
|
+
}
|
|
216
|
+
var app_default = app;
|
|
217
|
+
|
|
218
|
+
// src/server.ts
|
|
219
|
+
var PORT = 18302;
|
|
220
|
+
app_default.listen(PORT, () => {
|
|
221
|
+
console.log(`Gateway is running at http://localhost:${PORT}`);
|
|
222
|
+
console.log(`Environment: ${"production"}`);
|
|
223
|
+
});
|
package/nodemon.json
ADDED
package/package.json
CHANGED
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zrocclaw",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"description": "ZrocClaw Gateway Service",
|
|
5
|
+
"main": "src/server.ts",
|
|
5
6
|
"bin": {
|
|
6
|
-
"zrocclaw": "bin/zrocclaw.js"
|
|
7
|
+
"zrocclaw": "./bin/zrocclaw.js"
|
|
7
8
|
},
|
|
8
|
-
"files": [
|
|
9
|
-
"dist",
|
|
10
|
-
"bin",
|
|
11
|
-
"README.md"
|
|
12
|
-
],
|
|
13
9
|
"scripts": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"dev": "nodemon src/server.ts",
|
|
12
|
+
"start": "node dist/server.js",
|
|
13
|
+
"build": "tsup"
|
|
16
14
|
},
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"author": "ZrocLee",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"packageManager": "pnpm@10.30.3",
|
|
17
19
|
"dependencies": {
|
|
18
|
-
"@zrocclaw/
|
|
19
|
-
"commander": "^
|
|
20
|
+
"@zrocclaw/core": "workspace:*",
|
|
21
|
+
"commander": "^14.0.3",
|
|
22
|
+
"cors": "^2.8.6",
|
|
23
|
+
"dotenv": "^17.3.1",
|
|
24
|
+
"express": "^5.2.1",
|
|
25
|
+
"helmet": "^8.1.0",
|
|
26
|
+
"http-proxy-middleware": "^3.0.5",
|
|
27
|
+
"pm2": "^6.0.14"
|
|
20
28
|
},
|
|
21
29
|
"devDependencies": {
|
|
22
|
-
"@types/
|
|
30
|
+
"@types/cors": "^2.8.19",
|
|
31
|
+
"@types/express": "^5.0.6",
|
|
32
|
+
"@types/http-proxy-middleware": "^1.0.0",
|
|
33
|
+
"@types/node": "^25.5.0",
|
|
34
|
+
"nodemon": "^3.1.14",
|
|
35
|
+
"ts-node": "^10.9.2",
|
|
36
|
+
"tsup": "^8.5.1",
|
|
23
37
|
"typescript": "^5.9.3"
|
|
24
38
|
}
|
|
25
|
-
}
|
|
39
|
+
}
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/app.ts
|
|
2
|
+
import express from "express";
|
|
3
|
+
import cors from "cors"; // 可选,如果需要跨域
|
|
4
|
+
import helmet from "helmet"; // 可选,安全头
|
|
5
|
+
import configRouter from "./routes/config";
|
|
6
|
+
import chatRouter from "./routes/chat";
|
|
7
|
+
import dotenv from "dotenv";
|
|
8
|
+
import path from "path";
|
|
9
|
+
|
|
10
|
+
dotenv.config(); // 加载 .env 文件
|
|
11
|
+
|
|
12
|
+
const app = express();
|
|
13
|
+
|
|
14
|
+
// 基础中间件
|
|
15
|
+
app.use(helmet()); // 安全相关
|
|
16
|
+
app.use(cors()); // 允许跨域
|
|
17
|
+
app.use(express.json()); // 解析 JSON 请求体
|
|
18
|
+
|
|
19
|
+
// 健康检查端点
|
|
20
|
+
app.get("/health", (req, res) => {
|
|
21
|
+
res.json({ status: "OK", timestamp: new Date().toISOString() });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// 配置路由
|
|
25
|
+
app.use("/config", configRouter);
|
|
26
|
+
app.use("/chat", chatRouter);
|
|
27
|
+
|
|
28
|
+
// 静态文件服务:将 Vue 打包后的 dist 目录挂载到根路径
|
|
29
|
+
// 注意:生产环境才使用静态文件服务,开发时通常由 Vite 自己处理
|
|
30
|
+
if (process.env.NODE_ENV === "production") {
|
|
31
|
+
// 静态资源存放在 src/static(Web 构建后复制过来)
|
|
32
|
+
app.use("/web", express.static(path.join(__dirname, "../src/static")));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default app;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getConfigPath } from '@zrocclaw/core/fileManager';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
interface SessionData {
|
|
6
|
+
sessionId: string | null;
|
|
7
|
+
sessionData: Record<string, any>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class SessionModel {
|
|
11
|
+
private static instance: SessionModel;
|
|
12
|
+
private filePath: string;
|
|
13
|
+
private data: SessionData;
|
|
14
|
+
|
|
15
|
+
private constructor() {
|
|
16
|
+
this.filePath = path.join(getConfigPath(), 'session.json');
|
|
17
|
+
this.data = {
|
|
18
|
+
sessionId: null,
|
|
19
|
+
sessionData: {},
|
|
20
|
+
};
|
|
21
|
+
this.load();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static getInstance(): SessionModel {
|
|
25
|
+
if (!SessionModel.instance) {
|
|
26
|
+
SessionModel.instance = new SessionModel();
|
|
27
|
+
}
|
|
28
|
+
return SessionModel.instance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private load(): void {
|
|
32
|
+
if (fs.existsSync(this.filePath)) {
|
|
33
|
+
try {
|
|
34
|
+
const fileContent = fs.readFileSync(this.filePath, 'utf-8');
|
|
35
|
+
const parsed = JSON.parse(fileContent);
|
|
36
|
+
this.data = { ...this.data, ...parsed };
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Failed to parse session.json:', error);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// 文件不存在就创建一个
|
|
42
|
+
this.save();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private save(): void {
|
|
47
|
+
try {
|
|
48
|
+
const dir = path.dirname(this.filePath);
|
|
49
|
+
if (!fs.existsSync(dir)) {
|
|
50
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), 'utf-8');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Failed to save session.json:', error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public getSessionId(): string | null {
|
|
59
|
+
return this.data.sessionId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public setSessionId(id: string | null): void {
|
|
63
|
+
this.data.sessionId = id;
|
|
64
|
+
this.save();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public getSessionData(): Record<string, any> {
|
|
68
|
+
return this.data.sessionData;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public setSessionData(data: Record<string, any>): void {
|
|
72
|
+
this.data.sessionData = data;
|
|
73
|
+
this.save();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const sessionModel = SessionModel.getInstance();
|
|
78
|
+
export default sessionModel;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { browser_stream_invoke } from '@zrocclaw/core/agents'
|
|
3
|
+
import { getConfigPath } from '@zrocclaw/core/fileManager';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
import sessionModel from '../models/session';
|
|
8
|
+
|
|
9
|
+
const configDir = getConfigPath();
|
|
10
|
+
const configFilePath = path.join(configDir, 'model.json');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const router = Router();
|
|
15
|
+
router.get('/', (req, res) => {
|
|
16
|
+
res.json({ status: "OK" });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
router.post('/stream', async (req, res) => {
|
|
20
|
+
try {
|
|
21
|
+
const query = req.body.query as string;
|
|
22
|
+
const thread_id = req.body.thread_id as string;
|
|
23
|
+
|
|
24
|
+
if (!query || !thread_id) {
|
|
25
|
+
return res.status(400).json({ error: "Missing query or thread_id in request body" });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const configContent = await fs.readFile(configFilePath, 'utf-8');
|
|
29
|
+
const config = JSON.parse(configContent);
|
|
30
|
+
const modelConfig = config.defaultModel;
|
|
31
|
+
|
|
32
|
+
if (!modelConfig) {
|
|
33
|
+
return res.status(500).json({ error: "defaultModel not found in config" });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
37
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
38
|
+
res.setHeader('Connection', 'keep-alive');
|
|
39
|
+
|
|
40
|
+
const stream = browser_stream_invoke(query, thread_id, modelConfig);
|
|
41
|
+
|
|
42
|
+
for await (const chunk of stream) {
|
|
43
|
+
res.write(chunk);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
res.end();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("Stream error:", error);
|
|
49
|
+
if (!res.headersSent) {
|
|
50
|
+
res.status(500).json({ error: "Internal server error" });
|
|
51
|
+
} else {
|
|
52
|
+
res.end();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
router.get('/session', (req, res) => {
|
|
58
|
+
const isNew = req.query.new === 'true';
|
|
59
|
+
let sessionId = sessionModel.getSessionId();
|
|
60
|
+
if (!sessionId || isNew) {
|
|
61
|
+
sessionId = crypto.randomUUID();
|
|
62
|
+
sessionModel.setSessionId(sessionId);
|
|
63
|
+
}
|
|
64
|
+
res.json({ sessionId });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export default router;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { getConfigPath } from '@zrocclaw/core/fileManager';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const router = Router();
|
|
7
|
+
|
|
8
|
+
router.get('/', (req, res) => {
|
|
9
|
+
res.json({ configPath: getConfigPath()});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const configDir = getConfigPath();
|
|
14
|
+
const configFilePath = path.join(configDir, 'model.json');
|
|
15
|
+
|
|
16
|
+
// 新增:获取模型信息
|
|
17
|
+
router.get('/model', async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(configFilePath);
|
|
22
|
+
const data = await fs.readFile(configFilePath, 'utf-8');
|
|
23
|
+
res.json(JSON.parse(data));
|
|
24
|
+
} catch (error: any) {
|
|
25
|
+
if (error.code === 'ENOENT') {
|
|
26
|
+
// 文件不存在,创建并返回 {}
|
|
27
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
28
|
+
await fs.writeFile(configFilePath, '{}', 'utf-8');
|
|
29
|
+
res.json({});
|
|
30
|
+
} else {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('获取模型失败:', error);
|
|
36
|
+
res.status(500).json({ error: '获取模型失败' });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 新增:修改模型信息
|
|
41
|
+
router.post('/model', async (req, res) => {
|
|
42
|
+
try {
|
|
43
|
+
// 确保目录存在
|
|
44
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
45
|
+
|
|
46
|
+
// 读取现有配置
|
|
47
|
+
let currentConfig = {};
|
|
48
|
+
try {
|
|
49
|
+
const data = await fs.readFile(configFilePath, 'utf-8');
|
|
50
|
+
currentConfig = JSON.parse(data);
|
|
51
|
+
} catch (error: any) {
|
|
52
|
+
if (error.code !== 'ENOENT') {
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 合并并写入新配置
|
|
58
|
+
const newConfig = { ...currentConfig, ...req.body };
|
|
59
|
+
await fs.writeFile(configFilePath, JSON.stringify(newConfig, null, 2), 'utf-8');
|
|
60
|
+
res.json(newConfig);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('修改模型失败:', error);
|
|
63
|
+
res.status(500).json({ error: '修改模型失败' });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export default router;
|