Skip to content

Commit 241931f

Browse files
author
linyuan.yang
committed
合并 channel session row
1 parent 60bc8c4 commit 241931f

12 files changed

Lines changed: 117 additions & 52 deletions

File tree

packages/admin/src/views/ChannelsView.vue

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const { isMobile } = useResponsive()
1717
interface PluginInfo {
1818
type: string
1919
label: string
20+
builtin: boolean
2021
configSchema: Record<string, { label: string; type: string; required?: boolean; description?: string; default?: string | boolean | number; options?: Array<{ label: string; value: string }> }>
2122
}
2223
@@ -156,9 +157,6 @@ function formatTokens(n: number): string {
156157
return n.toLocaleString()
157158
}
158159
159-
function threadId(channelId: string, c: any, sessionId: string): string {
160-
return `${c.type}_${channelId}_${sessionId}`
161-
}
162160
163161
const showModal = ref(false)
164162
const editingId = ref<string | null>(null)
@@ -304,7 +302,8 @@ async function waitForQRConfirm(key: string, channelId: string | null, type: str
304302
function openAdd() {
305303
editingId.value = null
306304
clearActionState()
307-
form.value = { name: '', type: plugins.value[0]?.type || '', config: {}, agent: '', saver: '', memories: [], wikis: [], workPath: '', streamVerbose: false, autoApproveAllTools: false, intentModel: '', intentPrompt: '', intentThreshold: 0.7, mergeWindow: 0 }
305+
const defaultType = plugins.value.find(p => !p.builtin)?.type || ''
306+
form.value = { name: '', type: defaultType, config: {}, agent: '', saver: '', memories: [], wikis: [], workPath: '', streamVerbose: false, autoApproveAllTools: false, intentModel: '', intentPrompt: '', intentThreshold: 0.7, mergeWindow: 0 }
308307
showModal.value = true
309308
}
310309
@@ -368,6 +367,12 @@ async function save() {
368367
}
369368
}
370369
370+
function isBuiltin(id: string): boolean {
371+
const c = channels.value[id]
372+
if (!c) return false
373+
return plugins.value.some(p => p.type === c.type && p.builtin)
374+
}
375+
371376
async function remove(id: string) {
372377
const c = channels.value[id]
373378
const label = c?.name || id
@@ -417,7 +422,7 @@ async function refresh() {
417422
<div class="channel-card-right" @click.stop>
418423
<span class="channel-card-agent">{{ agentOptions.find(a => a.id === c.agent)?.label || c.agent || '-' }}</span>
419424
<button class="btn-outline btn-sm" @click="openEdit(id as string)">{{ t('common.edit') }}</button>
420-
<button class="btn-danger btn-sm" @click="remove(id as string)">{{ t('common.delete') }}</button>
425+
<button v-if="!isBuiltin(id as string)" class="btn-danger btn-sm" @click="remove(id as string)">{{ t('common.delete') }}</button>
421426
</div>
422427
</div>
423428
<!-- Card meta -->
@@ -461,7 +466,7 @@ async function refresh() {
461466
<span v-if="s.totalTokens > 0" class="session-item-tokens" :title="`${t('usage.total')}: ${formatTokens(s.totalTokens)} tokens\n ${t('usage.input_tokens')}: ${formatTokens(s.inputTokens)} / ${t('usage.output_tokens')}: ${formatTokens(s.outputTokens)}` + (s.lastTotalTokens > 0 ? `\n${t('usage.last')}: ${formatTokens(s.lastTotalTokens)} tokens` : '')">{{ formatTokens(s.totalTokens) }} tok</span>
462467
</div>
463468
<div class="ops-cell">
464-
<button v-if="s.saver || c.saver" class="btn-outline btn-sm" @click="saverViewModal?.open(s.saver || c.saver, saverOptions.find(o => o.id === (s.saver || c.saver))?.label || (s.saver || c.saver), threadId(id as string, c, s.sessionId))">{{ t('channels.history') }}</button>
469+
<button v-if="s.saver || c.saver" class="btn-outline btn-sm" @click="saverViewModal?.openByDbId(s.id, saverOptions.find(o => o.id === (s.saver || c.saver))?.label || (s.saver || c.saver))">{{ t('channels.history') }}</button>
465470
<button class="btn-outline btn-sm" @click="openEditSession(s)">{{ t('common.edit') }}</button>
466471
<button class="btn-danger btn-sm" @click="removeSession(id as string, s)">{{ t('common.delete') }}</button>
467472
</div>
@@ -523,7 +528,7 @@ async function refresh() {
523528
</div>
524529
<div class="mobile-card-ops">
525530
<button class="btn-outline btn-sm" @click="openEdit(id as string)">{{ t('common.edit') }}</button>
526-
<button class="btn-danger btn-sm" @click="remove(id as string)">{{ t('common.delete') }}</button>
531+
<button v-if="!isBuiltin(id as string)" class="btn-danger btn-sm" @click="remove(id as string)">{{ t('common.delete') }}</button>
527532
</div>
528533
<!-- Expandable detail -->
529534
<div v-if="expandedChannels[id as string]" style="margin-top:10px">
@@ -597,7 +602,7 @@ async function refresh() {
597602
<div class="form-group">
598603
<label>{{ t('channels.channel_type') }} *</label>
599604
<select v-model="form.type" @change="form.config = {}" :disabled="!!editingId">
600-
<option v-for="p in plugins" :key="p.type" :value="p.type">{{ p.label }}</option>
605+
<option v-for="p in plugins.filter(p => !p.builtin || editingId)" :key="p.type" :value="p.type">{{ p.label }}</option>
601606
</select>
602607
</div>
603608
<div class="form-group">

packages/admin/src/views/modals/SaverViewModal.vue

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ const saverId = ref('')
1414
const saverName = ref('')
1515
const threadId = ref('')
1616
const sessionId = ref('')
17+
const dbId = ref<number | null>(null)
1718
const messages = ref<StoredMessage[]>([])
1819
const loading = ref(false)
1920
2021
function historyUrl() {
22+
if (dbId.value) return `/api/channel-sessions/${dbId.value}/history`
2123
if (sessionId.value) return `/api/sessions/${encodeURIComponent(sessionId.value)}/history`
2224
return `/api/savers/${encodeURIComponent(saverId.value)}/threads/${encodeURIComponent(threadId.value)}/history`
2325
}
2426
2527
function thinksUrl() {
28+
if (dbId.value) return `/api/channel-sessions/${dbId.value}/thinks`
2629
if (sessionId.value) return `/api/sessions/${encodeURIComponent(sessionId.value)}/thinks`
2730
return `/api/savers/${encodeURIComponent(saverId.value)}/threads/${encodeURIComponent(threadId.value)}/thinks`
2831
}
@@ -55,6 +58,7 @@ function open(id: string, name: string, thread: string) {
5558
saverName.value = name
5659
threadId.value = thread
5760
sessionId.value = ''
61+
dbId.value = null
5862
messages.value = []
5963
visible.value = true
6064
load()
@@ -65,12 +69,24 @@ function openSession(sid: string, name: string) {
6569
saverName.value = name
6670
threadId.value = ''
6771
sessionId.value = sid
72+
dbId.value = null
6873
messages.value = []
6974
visible.value = true
7075
load()
7176
}
7277
73-
defineExpose({ open, openSession })
78+
function openByDbId(id: number, name: string) {
79+
saverId.value = ''
80+
saverName.value = name
81+
threadId.value = ''
82+
sessionId.value = ''
83+
dbId.value = id
84+
messages.value = []
85+
visible.value = true
86+
load()
87+
}
88+
89+
defineExpose({ open, openSession, openByDbId })
7490
</script>
7591

7692
<template>
@@ -80,7 +96,7 @@ defineExpose({ open, openSession })
8096
<div style="display:flex;align-items:center;gap:10px">
8197
<h3>{{ t('savers.history_title') }}</h3>
8298
<span class="saver-name-badge">{{ saverName }}</span>
83-
<span class="saver-thread-badge">{{ sessionId || threadId }}</span>
99+
<span class="saver-thread-badge">{{ dbId ? `#${dbId}` : sessionId || threadId }}</span>
84100
<span v-if="!loading" class="saver-count-badge">{{ t('savers.count', { count: messages.length }) }}</span>
85101
</div>
86102
<button class="modal-close" @click="visible = false">&times;</button>
0 Bytes
Binary file not shown.

packages/sbot.commons/src/settings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
export const DEFAULT_PORT = 5500;
22

3+
/** Web channel 的固定 channelId 和 type */
4+
export const WEB_CHANNEL_ID = 'web';
5+
export const WEB_CHANNEL_TYPE = 'web';
6+
37
export enum SaverType {
48
File = "file",
59
Sqlite = "sqlite",

packages/sbot/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@qingfeng346/sbot",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"description": "Self-hosted AI agent server. Open-source alternative to OpenAI, Claude, and Gemini interfaces. Run LLM agents locally with MCP tool support, memory, multi-channel integrations (Lark/Feishu), and a built-in web UI.",
55
"releasenoteEn": "### test",
66
"releasenoteZh": "### 测试",

packages/sbot/src/Channel/ChannelManager.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Op } from "sequelize";
55
import { sessionManager } from "../Session/SessionManager";
66
import { LoggerService } from "../Core/LoggerService";
77
import { config } from "../Core/Config";
8-
import { compareSemver, fetchLatestRelease } from "sbot.commons";
8+
import { compareSemver, fetchLatestRelease, WEB_CHANNEL_ID, WEB_CHANNEL_TYPE } from "sbot.commons";
99
import { channelThreadId } from "../Core/Database";
1010
import { PluginLoader } from "./PluginLoader";
1111

@@ -123,6 +123,8 @@ export class ChannelManager {
123123
const channel = config.getChannel(channelId);
124124
if (!channel) return false;
125125

126+
if (channel.type === WEB_CHANNEL_TYPE) return false;
127+
126128
const plugin = this.plugins.get(channel.type);
127129
if (!plugin) {
128130
logger.warn(`Unknown channel type [${channel.type}], skipping channel [${channel.name || channelId}]`);
@@ -177,8 +179,14 @@ export class ChannelManager {
177179
return this.plugins.get(type);
178180
}
179181

180-
getPluginList(): Array<{ type: string; label: string; configSchema: Record<string, any> }> {
181-
return [...this.plugins.values()].map(p => ({ type: p.type, label: p.label, configSchema: p.configSchema }));
182+
getPluginList(): Array<{ type: string; label: string; configSchema: Record<string, any>; builtin: boolean }> {
183+
const list: Array<{ type: string; label: string; configSchema: Record<string, any>; builtin: boolean }> = [
184+
{ type: WEB_CHANNEL_TYPE, label: 'Web', configSchema: {}, builtin: true },
185+
];
186+
for (const p of this.plugins.values()) {
187+
list.push({ type: p.type, label: p.label, configSchema: p.configSchema, builtin: false });
188+
}
189+
return list;
182190
}
183191

184192
async dispose(): Promise<void> {

packages/sbot/src/Core/Database.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export type SchedulerRow = {
2222
id: number;
2323
expr: string; // cron 表达式,如 "0 9 * * *"
2424
message: string; // 消息文本
25-
targetId: string | null; // channel_session.id (string)
25+
targetId: string; // channel_session.id (string)
2626
aiProcess: boolean; // true=交给AI处理后回复, false=直接发送原文不经AI
2727
lastRun: number | null; // 上次执行时间戳
2828
nextRun: number | null; // 下次预计执行时间戳
@@ -33,7 +33,7 @@ export type SchedulerRow = {
3333

3434
export type TodoRow = {
3535
id: number;
36-
targetId: string | null;
36+
targetId: string;
3737
content: string;
3838
status: string;
3939
priority: string;
@@ -437,8 +437,8 @@ class Database {
437437
},
438438
targetId: {
439439
type: DataTypes.TEXT,
440-
allowNull: true,
441-
defaultValue: null,
440+
allowNull: false,
441+
defaultValue: "",
442442
comment: "目标 channel_session.id",
443443
},
444444
aiProcess: {
@@ -496,8 +496,8 @@ class Database {
496496
},
497497
targetId: {
498498
type: DataTypes.TEXT,
499-
allowNull: true,
500-
defaultValue: null,
499+
allowNull: false,
500+
defaultValue: "",
501501
comment: "目标 channel_session.id",
502502
},
503503
content: {

packages/sbot/src/Processing/createProcessAIHandler.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { database, ChannelSessionRow, channelThreadId, parseMemories } from "../
44
import { config } from "../Core/Config";
55
import { buildExecuteTool } from "./buildExecuteTool";
66
import { updateUsageStats } from "./updateUsageStats";
7-
import { WebChatEventType } from "sbot.commons";
7+
import { WebChatEventType, WEB_CHANNEL_ID } from "sbot.commons";
88
import { httpServer } from "../Server/HttpServer";
99
import { AgentRunner } from "../Agent/AgentRunner";
1010

@@ -20,7 +20,7 @@ export function createProcessAIHandler(): ProcessAIHandler {
2020
const channel = config.getChannel(channelId);
2121
if (!channel) throw new Error(`Channel config not found: ${channelId}`);
2222

23-
if (channelId === 'web') {
23+
if (channelId === WEB_CHANNEL_ID) {
2424
httpServer.broadcastToWs(JSON.stringify({ sessionId, type: WebChatEventType.Human, data: { content: query } }));
2525
}
2626

@@ -38,7 +38,6 @@ export function createProcessAIHandler(): ProcessAIHandler {
3838
const autoApproveAllTools = dbSession.autoApproveAllTools ?? channel.autoApproveAllTools ?? false;
3939
const streamVerbose = dbSession.streamVerbose ?? channel.streamVerbose ?? false;
4040
const threadId = channelThreadId(channel.type, channelId, sessionId);
41-
const schedulerId = String(dbSessionId);
4241

4342
const silent: boolean = args?.silent ?? false;
4443
const extraAgentTools = args?.agentTools;
@@ -83,7 +82,7 @@ export function createProcessAIHandler(): ProcessAIHandler {
8382
agentId,
8483
saverId,
8584
threadId,
86-
dbSessionId: schedulerId,
85+
dbSessionId: String(dbSessionId),
8786
extraInfo: args?.extraInfo ?? '',
8887
memories,
8988
wikis,

packages/sbot/src/Processing/updateUsageStats.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type TokenUsage } from "scorpio.ai";
2-
import { WebChatEventType } from "sbot.commons";
2+
import { WebChatEventType, WEB_CHANNEL_ID } from "sbot.commons";
33
import { database, type ChannelSessionRow } from "../Core/Database";
44
import { httpServer } from "../Server/HttpServer";
55

@@ -36,7 +36,7 @@ export async function updateUsageStats(
3636
await database.update(database.channelSession, tokenUpdate, { where: { id: dbSessionId } });
3737

3838
const row = await database.findByPk<ChannelSessionRow>(database.channelSession, dbSessionId);
39-
if (row && row.channelId === 'web') {
39+
if (row && row.channelId === WEB_CHANNEL_ID) {
4040
httpServer.broadcastToWs(JSON.stringify({
4141
sessionId: row.sessionId,
4242
type: WebChatEventType.Usage,

packages/sbot/src/Scheduler/SchedulerService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CronJob } from "cron";
2+
import { WEB_CHANNEL_ID } from "sbot.commons";
23
import { database, SchedulerRow, ChannelSessionRow, channelThreadId } from "../Core/Database";
34
import { sessionManager } from "../Session/SessionManager";
45
import { LoggerService } from "../Core/LoggerService";
@@ -32,7 +33,7 @@ async function executeScheduler(schedulerId: number): Promise<void> {
3233
}
3334

3435
if (scheduler.aiProcess) {
35-
if (channelId === 'web') {
36+
if (channelId === WEB_CHANNEL_ID) {
3637
const threadId = channelThreadId(channelType, channelId, sessionId);
3738
await sessionManager.onReceiveWebMessage(threadId, scheduler.message, sessionId, dbSessionId);
3839
} else {
@@ -45,7 +46,7 @@ async function executeScheduler(schedulerId: number): Promise<void> {
4546
});
4647
}
4748
} else {
48-
if (channelId === 'web') {
49+
if (channelId === WEB_CHANNEL_ID) {
4950
logger.warn(`Scheduler task ${tag} non-aiProcess for web channel is not supported`);
5051
} else {
5152
await channelManager.sendText(channelId, sessionId, scheduler.message);

0 commit comments

Comments
 (0)