@hedeqiang/dsh-nl2sql 0.10.0 → 0.11.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/CHANGELOG.md +22 -2
- package/README.en.md +2 -1
- package/README.md +2 -2
- package/channels/dingtalk.js +574 -0
- package/channels/http.js +63 -0
- package/channels/index.js +138 -0
- package/channels/lark.js +1180 -0
- package/channels/message.js +267 -0
- package/channels/telegram.js +663 -0
- package/channels/wecom.js +585 -0
- package/cordis.patch.yml +3 -3
- package/docs/CHANNELS.md +184 -0
- package/im.js +6 -509
- package/index.js +25 -17
- package/lark-bot.js +7 -604
- package/lib/client.js +1097 -185
- package/package.json +14 -9
- package/service/access.js +116 -22
- package/service/admin.js +29 -13
- package/service/bots.js +665 -0
- package/service/channels.js +363 -0
- package/service/datasources.js +31 -8
- package/service/push.js +5 -156
- package/service/shared.js +1 -1
- package/service/lark-actions.js +0 -223
- package/service/lark.js +0 -250
package/index.js
CHANGED
|
@@ -18,12 +18,12 @@ import { methods as m2 } from './service/schema.js'
|
|
|
18
18
|
import { methods as m3 } from './service/query.js'
|
|
19
19
|
import { methods as m4 } from './service/dashboards.js'
|
|
20
20
|
import { methods as m5 } from './service/push.js'
|
|
21
|
-
import { methods as m6 } from './service/
|
|
21
|
+
import { methods as m6 } from './service/bots.js'
|
|
22
|
+
import { methods as mChannels } from './service/channels.js'
|
|
22
23
|
import { methods as m7 } from './service/evaluation.js'
|
|
23
24
|
import { methods as m8 } from './service/alerts.js'
|
|
24
25
|
import { methods as m9 } from './service/uploads.js'
|
|
25
26
|
import { methods as m10 } from './service/admin.js'
|
|
26
|
-
import { methods as mLarkActions } from './service/lark-actions.js'
|
|
27
27
|
import { methods as mSuggest } from './service/suggestions.js'
|
|
28
28
|
const { CHART_TYPES, DEFAULT_SETTINGS, RECORD_KINDS, RPC_PATH, Records, UserError, newId, pick, toUserError } = shared
|
|
29
29
|
|
|
@@ -36,7 +36,7 @@ class Nl2SqlService {
|
|
|
36
36
|
this.config = config ?? {}
|
|
37
37
|
this.dir = this.config.dataDir ?? join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'nl2sql')
|
|
38
38
|
this.file = join(this.dir, 'state.json')
|
|
39
|
-
this.state = { version: 1, datasources: [], settings: structuredClone(DEFAULT_SETTINGS), current: null, sessions: {}, dashboard: [], knowledge: {}, reports: [], dashboardFilters: [], channels: [], dashboards: [], currentDashboard: null, evals: {}, alerts: [], users: [],
|
|
39
|
+
this.state = { version: 1, datasources: [], settings: structuredClone(DEFAULT_SETTINGS), current: null, sessions: {}, dashboard: [], knowledge: {}, reports: [], dashboardFilters: [], channels: [], dashboards: [], currentDashboard: null, evals: {}, alerts: [], users: [], botConversations: {} }
|
|
40
40
|
this.drivers = new Map()
|
|
41
41
|
this.schemas = new Map()
|
|
42
42
|
this.results = new Map()
|
|
@@ -58,6 +58,8 @@ class Nl2SqlService {
|
|
|
58
58
|
this.state.currentDashboard = 'db-default'
|
|
59
59
|
}
|
|
60
60
|
this.syncLegacyDashboard()
|
|
61
|
+
// Channels (adapter registry), bots on channels, member identities, bot settings.
|
|
62
|
+
const migrated = [await this.initChannels(), this.migrateUsers(), this.migrateSettings()].some(Boolean)
|
|
61
63
|
try {
|
|
62
64
|
this.store = new ResultStore(join(this.dir, 'results.sqlite'))
|
|
63
65
|
} catch (error) {
|
|
@@ -82,8 +84,10 @@ class Nl2SqlService {
|
|
|
82
84
|
if (changed) {
|
|
83
85
|
if (Object.keys(moved).length) console.warn('nl2sql: migrated logs to SQLite: %s', JSON.stringify(moved))
|
|
84
86
|
await this.save()
|
|
87
|
+
return
|
|
85
88
|
}
|
|
86
89
|
}
|
|
90
|
+
if (migrated) await this.save()
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
/** Keep the legacy `dashboard` / `dashboardFilters` fields pointing at the current dashboard. */
|
|
@@ -104,8 +108,7 @@ class Nl2SqlService {
|
|
|
104
108
|
if (ids.length > keep) for (const id of ids.slice(0, ids.length - keep)) { delete this.state.sessions[id]; delete this.state.sessionActors?.[id]; delete this.state.evalExclusions?.[id] }
|
|
105
109
|
for (const id of Object.keys(this.state.sessionActors ?? {})) if (!(id in (this.state.sessions ?? {}))) delete this.state.sessionActors[id]
|
|
106
110
|
for (const id of Object.keys(this.state.evalExclusions ?? {})) if (!(id in (this.state.sessions ?? {}))) delete this.state.evalExclusions[id]
|
|
107
|
-
|
|
108
|
-
if (convs.length > 500) for (const c of convs.slice(500)) delete this.state.larkSessions[c.key]
|
|
111
|
+
this.pruneBotConversations()
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
save() {
|
|
@@ -132,9 +135,9 @@ class Nl2SqlService {
|
|
|
132
135
|
await Promise.race([Promise.allSettled(jobs), new Promise(r => { timer = setTimeout(r, 3000) })])
|
|
133
136
|
clearTimeout(timer)
|
|
134
137
|
}
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
if (this.
|
|
138
|
+
await this.stopAllBots()
|
|
139
|
+
// Button analyses still running in the background (service/bots.js).
|
|
140
|
+
if (this.botActionJobs?.size) await Promise.race([Promise.allSettled([...this.botActionJobs]), new Promise(r => setTimeout(r, 3000).unref?.())])
|
|
138
141
|
await Promise.allSettled([...this.drivers.values()].map(p => p.then(d => d.close())))
|
|
139
142
|
this.drivers.clear()
|
|
140
143
|
await this.writing
|
|
@@ -142,7 +145,7 @@ class Nl2SqlService {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
}
|
|
145
|
-
for (const m of [m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, mSuggest
|
|
148
|
+
for (const m of [m0, m1, m2, m3, m4, m5, mChannels, m6, m7, m8, m9, m10, mSuggest]) Object.defineProperties(Nl2SqlService.prototype, Object.getOwnPropertyDescriptors(m))
|
|
146
149
|
const DATASOURCE_PARAM = { type: 'string', description: 'Data source id or name. Omit to use the one bound to this session.' }
|
|
147
150
|
|
|
148
151
|
function textBlock(text) {
|
|
@@ -296,15 +299,15 @@ function registerTools(ctx, service) {
|
|
|
296
299
|
function registerSendTool(ctx, service) {
|
|
297
300
|
ctx.tools.register({
|
|
298
301
|
name: 'nl2sql_send',
|
|
299
|
-
description: 'Send a message to an IM channel (Lark
|
|
302
|
+
description: 'Send a message to an IM channel (such as Lark/Feishu, Telegram, WeCom, DingTalk) configured by the user in the Data Q&A page: a title, a Markdown body, and up to 10 query results by resultId. Each platform renders them as natively as it can (Lark: interactive cards with native charts; others: chart images or Markdown tables — see `capabilities` in action=list). The Markdown supports headings, bold/italic, lists, quotes, code blocks, links, tables, <font color=\'green|red|grey\'>…</font> and <at id=all></at>; platforms with basic Markdown drop the <font>/<at> tags. Only send when the user asks to share/push/send, or when a scheduled report instructs you to. Use action=list to see channels.',
|
|
300
303
|
parameters: {
|
|
301
304
|
type: 'object',
|
|
302
305
|
properties: {
|
|
303
306
|
action: { type: 'string', enum: ['send', 'list'], description: 'send (default) or list channels.' },
|
|
304
307
|
channel: { type: 'string', description: 'Channel id or name; omit for the default channel.' },
|
|
305
|
-
title: { type: 'string', description: '
|
|
308
|
+
title: { type: 'string', description: 'Message title.' },
|
|
306
309
|
text: { type: 'string', description: 'Markdown body: summary, conclusions, tables. Write it for the IM reader; do not paste raw result rows that are already attached via resultIds.' },
|
|
307
|
-
chartMode: { type: 'string', enum: ['native', 'image', 'table'], description: 'How attached results render: native card charts
|
|
310
|
+
chartMode: { type: 'string', enum: ['native', 'image', 'table'], description: 'How attached results render: native card charts, PNG images, or tables. Omit to use the channel default; unsupported modes fall back to it.' },
|
|
308
311
|
resultIds: { type: 'array', items: { type: 'string' }, description: 'resultIds from nl2sql_query / nl2sql_chart / nl2sql_overview to include.' },
|
|
309
312
|
includeSql: { type: 'boolean', description: 'Also include each result\'s SQL.' },
|
|
310
313
|
},
|
|
@@ -314,7 +317,7 @@ function registerSendTool(ctx, service) {
|
|
|
314
317
|
presentCall: (args) => ({ card: 'generic', title: args?.action === 'list' ? '列出推送渠道' : `推送到 ${args?.channel ?? '默认渠道'}:${args?.title ?? ''}`, kind: 'other' }),
|
|
315
318
|
execute: async (args, exec) => {
|
|
316
319
|
try {
|
|
317
|
-
if (args.action === 'list') return { channels: service.listChannels().map(c => pick(c, ['id', 'name', 'type', 'description', 'isDefault'])) }
|
|
320
|
+
if (args.action === 'list') return { channels: service.listChannels().map(c => ({ ...pick(c, ['id', 'name', 'type', 'platform', 'description', 'isDefault']), ...(c.capabilities ? { capabilities: pick(c.capabilities, ['markdown', 'charts', 'tables', 'chartModes']) } : {}) })) }
|
|
318
321
|
return await service.sendToChannel({ channelId: args.channel, title: args.title, text: args.text, resultIds: args.resultIds ?? [], includeSql: args.includeSql, chartMode: args.chartMode, source: 'agent', sessionId: exec.agent?.id })
|
|
319
322
|
} catch (error) {
|
|
320
323
|
throw error instanceof UserError ? new Error(error.message) : error
|
|
@@ -452,11 +455,13 @@ function registerRpc(ctx, service) {
|
|
|
452
455
|
deleteChannel: (p) => service.deleteChannel(p.id),
|
|
453
456
|
testChannel: (p) => service.testChannel(p.id),
|
|
454
457
|
listChannelChats: (p) => service.listChannelChats(p ?? {}),
|
|
458
|
+
channelCatalog: (p) => service.channelCatalog(p ?? {}),
|
|
459
|
+
listChannelTargets: (p) => service.listChannelTargets(p ?? {}),
|
|
455
460
|
sendToChannel: (p) => service.sendToChannel({ ...p, source: p.source === 'dashboard' ? 'dashboard' : 'ui' }),
|
|
456
461
|
sendDashboard: (p) => service.sendDashboard(p),
|
|
457
462
|
previewAssistantMessage: (p) => service.previewAssistantMessage(p),
|
|
458
463
|
sendAssistantMessage: (p) => service.sendAssistantMessage(p),
|
|
459
|
-
listDeliveries: () => service.listDeliveries(),
|
|
464
|
+
listDeliveries: (p) => service.listDeliveries(p?.limit ?? 100),
|
|
460
465
|
listHistory: (p) => service.listHistory(p ?? {}),
|
|
461
466
|
clearHistory: () => service.clearHistory(),
|
|
462
467
|
deleteHistory: (p) => service.deleteHistory(p ?? {}),
|
|
@@ -497,6 +502,9 @@ function registerRpc(ctx, service) {
|
|
|
497
502
|
larkBotStatus: () => service.larkBotStatus(),
|
|
498
503
|
saveLarkBot: (p) => service.saveLarkBot(p),
|
|
499
504
|
restartLarkBot: async () => { await service.restartLarkBot(); return service.larkBotStatus() },
|
|
505
|
+
botStatus: () => service.botStatus(),
|
|
506
|
+
saveBot: (p) => service.saveBot(p ?? {}),
|
|
507
|
+
restartBot: async (p) => { await service.restartBot({ channelId: p?.channelId }); return (await service.botStatus()).find(b => b.channelId === p?.channelId) ?? null },
|
|
500
508
|
queryMetric: (p) => service.queryMetric({ ...p, source: 'ui' }),
|
|
501
509
|
parseUpload: (p) => service.parseUpload(p),
|
|
502
510
|
importUpload: (p) => service.importUpload(p),
|
|
@@ -559,7 +567,7 @@ function registerRpc(ctx, service) {
|
|
|
559
567
|
return Response.json({ ok: true, value: value ?? null }, { headers: { 'cache-control': 'no-store' } })
|
|
560
568
|
} catch (error) {
|
|
561
569
|
const e = toUserError(error)
|
|
562
|
-
return Response.json({ ok: false, error: { code: e.code ?? 'nl2sql/error', message: e.message } }, { headers: { 'cache-control': 'no-store' } })
|
|
570
|
+
return Response.json({ ok: false, error: { code: e.code ?? 'nl2sql/error', message: e.message, ...(e.field ? { field: e.field } : {}) } }, { headers: { 'cache-control': 'no-store' } })
|
|
563
571
|
}
|
|
564
572
|
},
|
|
565
573
|
})
|
|
@@ -575,8 +583,8 @@ export async function apply(ctx, config) {
|
|
|
575
583
|
registerKnowledgeTool(ctx, service)
|
|
576
584
|
registerOverviewTool(ctx, service)
|
|
577
585
|
registerSendTool(ctx, service)
|
|
578
|
-
// Inbound
|
|
579
|
-
service.
|
|
586
|
+
// Inbound IM bots (Lark long connection, Telegram polling, …) for every enabled bot channel.
|
|
587
|
+
service.startBots().catch(error => console.warn('nl2sql bot: start failed: %s', error.message))
|
|
580
588
|
service.scheduleAlerts()
|
|
581
589
|
registerAnalysisTools(ctx, service)
|
|
582
590
|
}
|