iflow-run 1.0.0 → 1.0.1
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/README.md +68 -12
- package/package.json +1 -1
- package/server.js +58 -3
package/README.md
CHANGED
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
一个用于查看 iFlow CLI 会话轨迹和历史会话的 Web 应用程序。
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/iflow-run)
|
|
6
|
+
[](https://github.com/KeWen-Du/iflow-run)
|
|
7
|
+
|
|
5
8
|
## 功能特性
|
|
6
9
|
|
|
7
10
|
- 📁 **项目管理** - 浏览和查看 iFlow CLI 创建的所有项目
|
|
8
11
|
- 💬 **会话浏览** - 查看每个项目下的所有会话历史
|
|
9
12
|
- 🔍 **消息详情** - 查看完整的对话消息,包括用户消息、助手响应、工具调用和工具结果
|
|
10
13
|
- 👁️ **预览功能** - 快速预览会话的第一条消息内容
|
|
14
|
+
- 📊 **会话上下文** - 显示工作目录、Git 分支、版本信息等环境上下文
|
|
15
|
+
- 🔎 **消息筛选** - 支持按类型筛选消息(用户/助手/工具调用)和内容搜索
|
|
16
|
+
- 💰 **Token 统计** - 显示模型名称、Token 消耗、执行时间和预估成本
|
|
17
|
+
- 🔄 **环境追踪** - 检测并显示工作目录和 Git 分支的变更
|
|
18
|
+
- 📥 **导出功能** - 支持导出会话为 Markdown 或 JSON 格式
|
|
19
|
+
- 📋 **消息目录** - 快速导航到用户消息
|
|
11
20
|
- 🎨 **现代 UI** - 采用暗色主题和玻璃拟态设计,提供优雅的用户体验
|
|
12
21
|
- 📱 **响应式设计** - 支持桌面端和移动端访问
|
|
13
22
|
|
|
@@ -85,14 +94,16 @@ npx iflow-run
|
|
|
85
94
|
|
|
86
95
|
```
|
|
87
96
|
iflow-run/
|
|
88
|
-
├── server.js
|
|
89
|
-
├── package.json
|
|
90
|
-
├──
|
|
91
|
-
│
|
|
92
|
-
|
|
93
|
-
│ ├──
|
|
94
|
-
│
|
|
95
|
-
|
|
97
|
+
├── server.js # Express 服务器
|
|
98
|
+
├── package.json # 项目配置
|
|
99
|
+
├── bin/ # 全局可执行文件
|
|
100
|
+
│ └── iflow-run.js # CLI 入口文件
|
|
101
|
+
├── public/ # 前端静态文件
|
|
102
|
+
│ ├── index.html # 主页面
|
|
103
|
+
│ ├── app.js # 前端逻辑
|
|
104
|
+
│ ├── styles.css # 样式文件
|
|
105
|
+
│ └── test.html # 测试页面
|
|
106
|
+
└── test_screenshot.py # 自动化测试脚本
|
|
96
107
|
```
|
|
97
108
|
|
|
98
109
|
## API 接口
|
|
@@ -113,6 +124,41 @@ GET /api/sessions/:projectId/:sessionId
|
|
|
113
124
|
|
|
114
125
|
返回指定会话的完整消息记录。
|
|
115
126
|
|
|
127
|
+
### 搜索会话
|
|
128
|
+
|
|
129
|
+
```http
|
|
130
|
+
GET /api/search?q=关键词&page=1&limit=20&type=all
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
查询参数:
|
|
134
|
+
- `q` (string): 搜索关键词
|
|
135
|
+
- `page` (number): 页码,默认 1
|
|
136
|
+
- `limit` (number): 每页结果数,默认 20
|
|
137
|
+
- `type` (string): 消息类型筛选,可选值:`all`、`user`、`assistant`,默认 `all`
|
|
138
|
+
- `startDate` (number): 开始时间戳(可选)
|
|
139
|
+
- `endDate` (number): 结束时间戳(可选)
|
|
140
|
+
|
|
141
|
+
响应示例:
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"results": [
|
|
145
|
+
{
|
|
146
|
+
"projectId": "project-id",
|
|
147
|
+
"projectName": "项目名称",
|
|
148
|
+
"sessionId": "session-1234567890",
|
|
149
|
+
"content": "消息内容预览...",
|
|
150
|
+
"type": "user",
|
|
151
|
+
"timestamp": 1704110400000,
|
|
152
|
+
"uuid": "message-uuid"
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
"total": 100,
|
|
156
|
+
"page": 1,
|
|
157
|
+
"limit": 20,
|
|
158
|
+
"totalPages": 5
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
116
162
|
## 配置
|
|
117
163
|
|
|
118
164
|
### 通过命令行参数配置
|
|
@@ -164,11 +210,7 @@ python test_screenshot.py
|
|
|
164
210
|
3. 点击第一个会话并截图会话详情
|
|
165
211
|
4. 测试返回按钮功能
|
|
166
212
|
|
|
167
|
-
## 截图
|
|
168
213
|
|
|
169
|
-

|
|
170
|
-

|
|
171
|
-

|
|
172
214
|
|
|
173
215
|
## 开发
|
|
174
216
|
|
|
@@ -198,6 +240,7 @@ python test_screenshot.py
|
|
|
198
240
|
1. `.iflow` 目录路径是否正确
|
|
199
241
|
2. 目录下是否有 `projects` 子目录
|
|
200
242
|
3. 项目目录中是否有 `session-*.jsonl` 文件
|
|
243
|
+
4. 尝试使用 `--dir` 参数指定正确的 iflow 目录
|
|
201
244
|
|
|
202
245
|
### 消息显示为空?
|
|
203
246
|
|
|
@@ -205,6 +248,19 @@ python test_screenshot.py
|
|
|
205
248
|
- 会话文件格式不正确
|
|
206
249
|
- 消息内容不包含可显示的文本
|
|
207
250
|
- 消息格式不符合预期
|
|
251
|
+
- 使用了消息筛选功能,当前筛选条件下没有匹配的消息
|
|
252
|
+
|
|
253
|
+
### 工具结果显示不完整?
|
|
254
|
+
|
|
255
|
+
工具结果默认折叠显示,点击工具结果的标题栏可以展开查看完整内容。长结果会自动截断,可以通过复制按钮获取完整内容。
|
|
256
|
+
|
|
257
|
+
### Token 统计不准确?
|
|
258
|
+
|
|
259
|
+
Token 统计依赖于会话消息中的 `usage` 字段。如果模型未返回该信息,则无法显示 Token 统计。
|
|
260
|
+
|
|
261
|
+
### 环境变更提示没有显示?
|
|
262
|
+
|
|
263
|
+
环境变更提示需要消息中包含 `cwd` 或 `gitBranch` 字段。只有当这些字段发生变化时才会显示提示。
|
|
208
264
|
|
|
209
265
|
## 贡献
|
|
210
266
|
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name": "iflow-run", "version": "1.0.
|
|
1
|
+
{"name": "iflow-run", "version": "1.0.1", "description": "查看 iflow-cli 会话轨迹和历史会话的 Web 应用", "main": "server.js", "bin": {"iflow-run": "./bin/iflow-run.js"}, "scripts": {"start": "node server.js", "dev": "node server.js"}, "keywords": ["iflow", "session", "history", "cli", "viewer", "dashboard"], "author": "dukewen <dukewen666@gmail.com>", "license": "MIT", "files": ["bin/", "public/", "server.js", "package.json", "README.md"], "dependencies": {"express": "^4.18.2", "cors": "^2.8.5"}, "engines": {"node": ">=14.0.0"}}
|
package/server.js
CHANGED
|
@@ -247,6 +247,61 @@ function extractContent(msg) {
|
|
|
247
247
|
return JSON.stringify(content);
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
// 检测端口是否可用
|
|
251
|
+
function isPortAvailable(port) {
|
|
252
|
+
return new Promise((resolve) => {
|
|
253
|
+
const net = require('net');
|
|
254
|
+
const server = net.createServer();
|
|
255
|
+
|
|
256
|
+
server.once('error', (err) => {
|
|
257
|
+
if (err.code === 'EADDRINUSE') {
|
|
258
|
+
resolve(false);
|
|
259
|
+
} else {
|
|
260
|
+
resolve(true);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
server.once('listening', () => {
|
|
265
|
+
server.close();
|
|
266
|
+
resolve(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
server.listen(port);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 自动查找可用端口
|
|
274
|
+
async function findAvailablePort(startPort) {
|
|
275
|
+
let port = startPort;
|
|
276
|
+
let attempts = 0;
|
|
277
|
+
const maxAttempts = 100;
|
|
278
|
+
|
|
279
|
+
while (attempts < maxAttempts) {
|
|
280
|
+
const available = await isPortAvailable(port);
|
|
281
|
+
if (available) {
|
|
282
|
+
return port;
|
|
283
|
+
}
|
|
284
|
+
port++;
|
|
285
|
+
attempts++;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
throw new Error(`Unable to find available port after ${maxAttempts} attempts`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 启动服务器
|
|
292
|
+
(async () => {
|
|
293
|
+
try {
|
|
294
|
+
const availablePort = await findAvailablePort(PORT);
|
|
295
|
+
|
|
296
|
+
if (availablePort !== PORT) {
|
|
297
|
+
console.log(`Port ${PORT} is occupied, using port ${availablePort} instead`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
app.listen(availablePort, () => {
|
|
301
|
+
console.log(`iflow-run server running at http://localhost:${availablePort}`);
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error('Failed to start server:', error.message);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
})();
|