Skip to content

Commit e1f3237

Browse files
committed
🎈 perf(app): ui update & recording
1 parent 46b9fa1 commit e1f3237

File tree

5 files changed

+252
-55
lines changed

5 files changed

+252
-55
lines changed

api-server.js

+98-12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const database = require("./database.js");
99
const Users = database.Users;
1010
const Bots = database.Bots;
1111
const Settings = database.Settings;
12+
const BotRecording = database.BotRecording;
1213
const sequelize = database.sequelize;
1314
const fs = require("fs");
1415
const { promisify } = require("util");
@@ -305,18 +306,16 @@ async function get_api_server(proxy_utils) {
305306
// "switch_config",
306307
// "data_config"
307308

308-
const FIELD_DEFAULT = {
309-
switch_config: BOT_DEFAULT_SWITCH_CONFIG,
310-
data_config: BOT_DEFAULT_DATA_CONFIG,
311-
recording: [],
312-
tabs: [],
313-
bookmarks: [],
314-
history: [],
315-
cookies: [],
316-
downloads: [],
317-
};
318-
319-
309+
const FIELD_DEFAULT = {
310+
switch_config: BOT_DEFAULT_SWITCH_CONFIG,
311+
data_config: BOT_DEFAULT_DATA_CONFIG,
312+
recording: [],
313+
tabs: [],
314+
bookmarks: [],
315+
history: [],
316+
cookies: [],
317+
downloads: [],
318+
};
320319

321320
app.get(API_BASE_PATH + "/fields", async (req, res) => {
322321
const name = req.query.field;
@@ -337,6 +336,93 @@ const FIELD_DEFAULT = {
337336
.end();
338337
});
339338

339+
app.get(API_BASE_PATH + "/recordings", async (req, res) => {
340+
const {
341+
id,
342+
page = 1,
343+
pageSize = 20,
344+
startDate,
345+
endDate,
346+
download,
347+
} = req.query;
348+
const offset = (page - 1) * pageSize;
349+
const needDownload = download === "true";
350+
351+
const whereClause = {
352+
bot: id,
353+
};
354+
355+
if (startDate || endDate) {
356+
const dateConditions = {};
357+
let hasDate = false;
358+
if (startDate) {
359+
const parsedStartDate = new Date(startDate);
360+
if (!isNaN(parsedStartDate.getTime())) {
361+
dateConditions[Op.gte] = parsedStartDate;
362+
hasDate = true;
363+
}
364+
}
365+
if (endDate) {
366+
const parsedEndDate = new Date(endDate);
367+
if (!isNaN(parsedEndDate.getTime())) {
368+
dateConditions[Op.lte] = parsedEndDate;
369+
hasDate = true;
370+
}
371+
}
372+
if (hasDate) {
373+
whereClause.createdAt = dateConditions;
374+
}
375+
}
376+
377+
const recordings = await BotRecording.findAll({
378+
where: whereClause,
379+
attributes: ["createdAt", "recording", "text"],
380+
order: [["createdAt", "DESC"]],
381+
...(needDownload
382+
? {}
383+
: {
384+
offset: offset,
385+
limit: parseInt(pageSize),
386+
}),
387+
});
388+
389+
if (needDownload) {
390+
const filename = `${id}_${Date.now()}.mp3`;
391+
await mergeBase64MP3s(
392+
recordings.map((rec) => rec.recording.data),
393+
filename
394+
);
395+
396+
return res.sendFile(path.join(__dirname, filename), (err) => {
397+
if (err) {
398+
console.error("Error sending file:", err);
399+
}
400+
// Clean up the file after sending
401+
fs.unlink(path.join(__dirname, filename), (unlinkErr) => {
402+
if (unlinkErr) console.error("Error deleting file:", unlinkErr);
403+
});
404+
});
405+
}
406+
407+
const total = await BotRecording.count({ where: whereClause });
408+
409+
res
410+
.status(200)
411+
.json({
412+
success: true,
413+
result: {
414+
recordings,
415+
pagination: {
416+
total,
417+
page: parseInt(page),
418+
pageSize: parseInt(pageSize),
419+
totalPages: Math.ceil(total / pageSize),
420+
},
421+
},
422+
})
423+
.end();
424+
});
425+
340426
app.post(API_BASE_PATH + "/mp3", async (req, res) => {
341427
const bot = await Bots.findOne({
342428
where: {

database.js

+49-1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,52 @@ Bots.init(
196196
}
197197
);
198198

199+
class BotRecording extends Model {}
200+
BotRecording.init(
201+
{
202+
id: {
203+
allowNull: false,
204+
primaryKey: true,
205+
type: Sequelize.UUID,
206+
defaultValue: uuid.v4(),
207+
},
208+
recording: {
209+
type: Sequelize.TEXT,
210+
allowNull: false,
211+
default: "",
212+
},
213+
bot: {
214+
type: Sequelize.UUID,
215+
allowNull: false,
216+
unique: false,
217+
references: {
218+
model: "bots",
219+
key: "id",
220+
},
221+
},
222+
text: {
223+
type: Sequelize.TEXT,
224+
allowNull: true,
225+
default: "",
226+
},
227+
},
228+
{
229+
sequelize,
230+
modelName: "bot_recordings",
231+
indexes: [
232+
{
233+
fields: ["bot"],
234+
method: "BTREE",
235+
},
236+
],
237+
}
238+
);
239+
240+
Bots.hasMany(BotRecording, {
241+
foreignKey: "bot",
242+
sourceKey: "id",
243+
});
244+
199245
/*
200246
Various key/values for settings
201247
*/
@@ -250,7 +296,7 @@ function get_default_user_created_banner(username, password) {
250296
return `
251297
============================================================================
252298
253-
█████╗ ████████╗████████╗███████╗███╗ ██╗████████╗██╗ █████╗ ███╗ ██╗
299+
█████╗ ████████╗████████╗███████╗███╗ ██╗████████╗██╗ ████���█╗ ███╗ ██╗
254300
██╔══██╗╚══██╔══╝╚══██╔══╝██╔════╝████╗ ██║╚══██╔══╝██║██╔═══██╗████╗ ██║
255301
███████║ ██║ ██║ █████╗ ██╔██╗ ██║ ██║ ██║██║ ██║██╔██╗ ██║
256302
██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██║██║╚██╗██║
@@ -357,6 +403,7 @@ async function database_init() {
357403

358404
await Bots.sync({ force: force });
359405
await Settings.sync({ force: force });
406+
await BotRecording.sync({ force: force });
360407

361408
// Set up configs if they're not already set up.
362409
console.log(`Checking for configs...`);
@@ -371,4 +418,5 @@ module.exports.sequelize = sequelize;
371418
module.exports.Users = Users;
372419
module.exports.Bots = Bots;
373420
module.exports.Settings = Settings;
421+
module.exports.BotRecording = BotRecording;
374422
module.exports.database_init = database_init;

gui/src/components/DataRecording.vue

+63-27
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
<template>
22
<div>
3+
<b-row class="mb-3">
4+
<b-col md="6">
5+
<b-form-group label="日期范围">
6+
<b-form-datepicker
7+
v-model="dateRange.startDate"
8+
:max="dateRange.endDate"
9+
placeholder="开始日期"
10+
class="mb-2"
11+
></b-form-datepicker>
12+
<b-form-datepicker
13+
v-model="dateRange.endDate"
14+
:min="dateRange.startDate"
15+
placeholder="结束日期"
16+
></b-form-datepicker>
17+
</b-form-group>
18+
</b-col>
19+
<b-col md="6" class="d-flex align-items-end">
20+
<b-button @click="fetchData" variant="primary">查询</b-button>
21+
<b-button @click="downloadData" variant="primary">下载</b-button>
22+
</b-col>
23+
</b-row>
24+
325
<div style="overflow-x: auto">
426
<b-table
527
id="recording_table"
628
:items="info"
729
:fields="recording_fields"
8-
:current-page="recording_page"
9-
:per-page="recording_page_size"
30+
:total-rows="totalRows"
1031
small
1132
hover
1233
>
@@ -24,13 +45,8 @@
2445
</b-table>
2546
</div>
2647
<b-pagination
27-
striped
28-
hover
29-
fixed
30-
responsive
31-
stacked
3248
v-model="recording_page"
33-
:total-rows="info.length"
49+
:total-rows="totalRows"
3450
:per-page="recording_page_size"
3551
aria-controls="recording_table"
3652
></b-pagination>
@@ -39,7 +55,7 @@
3955

4056
<script>
4157
import { convertToCurrentTimeZone } from "./common.js";
42-
import { get_field } from "./utils.js";
58+
import { get_recordings,api_file_request } from "./utils.js";
4359
export default {
4460
name: "DataRecording",
4561
props: {
@@ -50,14 +66,19 @@ export default {
5066
useMp3: {
5167
type: Boolean,
5268
default: false
53-
}
69+
},
70+
5471
},
5572
data() {
5673
return {
5774
info: [],
58-
// bot recording
75+
totalRows: 0,
5976
recording_page: 1,
6077
recording_page_size: 20,
78+
dateRange: {
79+
startDate: null,
80+
endDate: null
81+
},
6182
recording_fields: [
6283
{
6384
key: "date",
@@ -67,7 +88,6 @@ export default {
6788
key: "data",
6889
label: "数据",
6990
},
70-
7191
],
7292
};
7393
},
@@ -76,6 +96,11 @@ export default {
7696
return this.useMp3 ? 'audio/mp3' : 'audio/mpeg'
7797
}
7898
},
99+
watch: {
100+
recording_page() {
101+
this.fetchData();
102+
}
103+
},
79104
mounted() {
80105
this.fetchData();
81106
},
@@ -95,23 +120,34 @@ export default {
95120
return isMP3 ? 'audio/mp3' : 'audio/mpeg';
96121
},
97122
98-
fetchData() {
99-
get_field(this.id, "recording")
100-
.then((response) => {
101-
this.info = response.map((item) => {
102-
const audioType = this.checkAudioType(item.data);
103-
return {
104-
...item,
105-
audioType, // 存储检测到的类型
106-
data: `data:${audioType};base64,${item.data}`,
107-
date: convertToCurrentTimeZone(new Date(item.date))
108-
};
109-
});
110-
})
111-
.catch((error) => {
112-
console.log(error);
123+
async fetchData() {
124+
try {
125+
const response = await get_recordings({
126+
id: this.id,
127+
page: this.recording_page,
128+
pageSize: this.recording_page_size,
129+
startDate: this.dateRange.startDate,
130+
endDate: this.dateRange.endDate,
113131
});
132+
this.info = response.recordings.map(item => ({
133+
audioType: this.checkAudioType(item.recording),
134+
data: `data:${this.checkAudioType(item.recording)};base64,${item.recording}`,
135+
date: convertToCurrentTimeZone(new Date(item.createdAt))
136+
}));
137+
console.log(response,this.info);
138+
this.totalRows = response.pagination.total;
139+
} catch (error) {
140+
console.error('Error fetching recordings:', error);
141+
}
114142
},
143+
async downloadData() {
144+
await api_file_request("GET", "/recordings", null, {
145+
id: this.id,
146+
download: true,
147+
startDate: this.dateRange.startDate,
148+
endDate: this.dateRange.endDate
149+
})
150+
}
115151
},
116152
};
117153
</script>

gui/src/components/utils.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
const BASE_API_PATH = `${location.origin.toString()}/api/v1`;
22

33
// make a request to the api for a file
4-
export const api_file_request = async (method, path, body) => {
4+
export const api_file_request = async (method, path, body, params) => {
55
var request_options = {
66
method: method,
77
credentials: "include",
8+
params: params,
89
mode: "cors",
910
cache: "no-cache",
1011
responseType: "blob",
@@ -87,6 +88,22 @@ export const get_field = async (id, field) => {
8788
return await api_request("GET", "/fields", { id: id, field: field });
8889
};
8990

91+
export const get_recordings = async ({
92+
id,
93+
page,
94+
pageSize,
95+
startDate,
96+
endDate,
97+
} = {}) => {
98+
return await api_request("GET", "/recordings", {
99+
id,
100+
page,
101+
pageSize,
102+
startDate,
103+
endDate,
104+
});
105+
};
106+
90107
// download the certificate authority
91108
export const download_ca = async () => {
92109
window.location = `${BASE_API_PATH}/download_ca`;

0 commit comments

Comments
 (0)