@hedeqiang/dsh-nl2sql 0.0.0-stage → 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 +52 -0
- package/LICENSE +21 -0
- package/README.en.md +63 -0
- package/README.md +88 -2
- package/analysis.js +888 -0
- 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 +68 -0
- package/docs/CHANNELS.md +184 -0
- package/drivers.js +694 -0
- package/filters.js +237 -0
- package/icon.svg +13 -0
- package/im.js +8 -0
- package/index.js +658 -0
- package/lark-bot.js +9 -0
- package/lib/client.echarts.js +57 -0
- package/lib/client.js +5757 -0
- package/locale/en.json +1 -0
- package/locale/zh.json +1 -0
- package/package.json +95 -4
- package/policy.js +362 -0
- package/service/access.js +239 -0
- package/service/admin.js +555 -0
- package/service/alerts.js +233 -0
- package/service/bots.js +665 -0
- package/service/channels.js +363 -0
- package/service/dashboards.js +359 -0
- package/service/datasources.js +415 -0
- package/service/evaluation.js +121 -0
- package/service/push.js +163 -0
- package/service/query.js +411 -0
- package/service/schema.js +507 -0
- package/service/shared.js +424 -0
- package/service/suggestions.js +772 -0
- package/service/uploads.js +258 -0
- package/sql-guard.js +222 -0
- package/sqlite-driver.js +677 -0
- package/store.js +225 -0
- package/xlsx.js +1001 -0
package/docs/CHANNELS.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Channel architecture
|
|
2
|
+
|
|
3
|
+
A **channel** is one configured connection to an IM platform. Every platform is an
|
|
4
|
+
**adapter** module under `channels/`; the service core (`service/channels.js`,
|
|
5
|
+
`service/bots.js`) only talks to the adapter interface, never to a platform API.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
channels/
|
|
9
|
+
index.js registry: list/get adapters, shared types and helpers
|
|
10
|
+
message.js platform-neutral message model + Markdown utilities + chart PNG
|
|
11
|
+
http.js fetch with timeout, JSON helpers, ChannelError
|
|
12
|
+
lark.js Lark / Feishu (webhook bot, app bot; long connection; card buttons)
|
|
13
|
+
telegram.js Telegram (Bot API; long polling; inline keyboard)
|
|
14
|
+
wecom.js WeCom 企业微信 (group webhook bot; AI bot long connection)
|
|
15
|
+
dingtalk.js DingTalk 钉钉 (custom robot webhook; enterprise app robot over Stream)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Message model (platform neutral)
|
|
19
|
+
|
|
20
|
+
Producers (agent `nl2sql_send`, dashboards, reports, alerts, schema notices,
|
|
21
|
+
bot answers) build one `OutboundMessage`; each adapter renders it natively.
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
OutboundMessage = {
|
|
25
|
+
title: string,
|
|
26
|
+
text?: string, // Markdown (CommonMark subset + <font color='green|red|grey'>, <at id=all></at>)
|
|
27
|
+
results?: ResultBlock[], // ≤ 10
|
|
28
|
+
footer?: string,
|
|
29
|
+
includeSql?: boolean,
|
|
30
|
+
chartMode?: 'native' | 'image' | 'table',
|
|
31
|
+
mentionAll?: boolean,
|
|
32
|
+
template?: 'blue' | 'red' | 'green' | 'orange' | 'grey', // accent; adapters may ignore
|
|
33
|
+
}
|
|
34
|
+
ResultBlock = { title, columns:[{name,kind}], rows, rowCount, truncated, chart, sql?, filters?, error?,
|
|
35
|
+
actions?: ActionSpec } // interactive controls, only when the target supports them
|
|
36
|
+
ActionSpec = { buttons?: [{ text, value }], select?: { placeholder, value, options:[{ text, value }] } }
|
|
37
|
+
// value objects are opaque tokens: { a, t } — never trusted on receipt
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Adapter interface
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
export default {
|
|
44
|
+
id: 'telegram', // platform id
|
|
45
|
+
name: { zh: 'Telegram', en: 'Telegram' },
|
|
46
|
+
kinds: [ChannelKind, ...], // what a user can configure for this platform
|
|
47
|
+
// Optional inbound bot (one per configured channel of a bot-capable kind):
|
|
48
|
+
createBot?(channel, secrets, handlers, opts) -> Bot,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ChannelKind = {
|
|
52
|
+
id: 'telegram_bot', // stored as channel.type (legacy: lark_webhook, lark_app)
|
|
53
|
+
platform: 'telegram',
|
|
54
|
+
label: { zh, en }, description: { zh, en },
|
|
55
|
+
// Form schema rendered generically by the UI:
|
|
56
|
+
fields: [{ key, label:{zh,en}, type:'text'|'secret'|'select'|'url', required?, placeholder?, help?:{zh,en},
|
|
57
|
+
options?:[{value,label:{zh,en}}], pattern?: string, secret?: boolean, showIf?: { key, equals },
|
|
58
|
+
target?: boolean }], // target: the field the 「选择群聊」 picker (listTargets) fills, e.g. Lark receiveId
|
|
59
|
+
capabilities: {
|
|
60
|
+
push: true,
|
|
61
|
+
markdown: 'full' | 'basic' | 'none',
|
|
62
|
+
charts: 'native' | 'image' | 'none', // native card charts vs PNG upload vs tables only
|
|
63
|
+
chartImages?: boolean, // also supports PNG images when charts is 'native' (Lark app)
|
|
64
|
+
chartModes?: ('native'|'image'|'table')[], // selectable channel.chartMode values; first = default
|
|
65
|
+
// (derived from charts/chartImages when omitted)
|
|
66
|
+
tables: 'native' | 'markdown' | 'text',
|
|
67
|
+
buttons: boolean, // interactive buttons on pushed/bot messages
|
|
68
|
+
bot: boolean, // inbound questions (no public URL needed)
|
|
69
|
+
listTargets: boolean, // can list chats/groups the bot is in
|
|
70
|
+
mentionAll: boolean,
|
|
71
|
+
maxMessageBytes?: number,
|
|
72
|
+
},
|
|
73
|
+
setupSteps: { zh: string[], en: string[] }, // shown in the UI; console paths, permissions
|
|
74
|
+
validate(input, existing) -> { config, secrets } // throws ChannelError with a user message
|
|
75
|
+
send(channel, secrets, message, ctx) -> { messageId?, images } // ctx: { renderChartPng, logger, fetch }
|
|
76
|
+
test?(channel, secrets) -> { ok, detail } // cheap auth check (token fetch / getMe)
|
|
77
|
+
listTargets?(channel, secrets) -> [{ id, name, type }]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
Bot = {
|
|
81
|
+
start(), stop(), status() -> { state:'idle'|'connecting'|'connected'|'error'|'stopped', error?, since? },
|
|
82
|
+
info() -> { name?, id? } | null,
|
|
83
|
+
}
|
|
84
|
+
handlers = {
|
|
85
|
+
onMessage(InboundMessage) -> Promise<void>,
|
|
86
|
+
onAction?(InboundAction) -> Promise<{ toast?: { type:'info'|'success'|'error', content } }>,
|
|
87
|
+
}
|
|
88
|
+
InboundMessage = {
|
|
89
|
+
platform, channelId, messageId, chatId, chatType: 'p2p' | 'group', text, // mention markup stripped
|
|
90
|
+
mentionedBot: boolean, sender: { id, name?, email?, unionId? }, raw?,
|
|
91
|
+
reply(OutboundMessage) -> Promise<{ messageId? }>, // threaded reply when supported
|
|
92
|
+
replyText(text) -> Promise<void>,
|
|
93
|
+
react?(kind: 'working'|'done'|'failed') -> Promise<void>,
|
|
94
|
+
}
|
|
95
|
+
InboundAction = { platform, channelId, operator: { id }, chatId, messageId, value, option?, raw?,
|
|
96
|
+
reply(OutboundMessage) -> Promise<{ messageId? }> }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Errors: adapters throw `ChannelError(code, message, { field? })` with a Chinese
|
|
100
|
+
user-facing message and a stable code such as `channel/auth`, `channel/config`,
|
|
101
|
+
`channel/network`, `channel/rate-limit`, `channel/rejected` (Lark keeps its
|
|
102
|
+
legacy `im/lark-<code>` / `im/config` codes). `field` names the form field key
|
|
103
|
+
the error belongs to; page RPC errors pass it through as `error.field`
|
|
104
|
+
(`{ ok:false, error:{ code, message, field? } }`).
|
|
105
|
+
|
|
106
|
+
Adapters must not import `channels/index.js` (the registry imports them); use
|
|
107
|
+
`channels/http.js` and `channels/message.js`. The `channel` object passed to
|
|
108
|
+
`send` / `test` / `listTargets` / `createBot` is the stored record with its
|
|
109
|
+
`config` fields also spread at the top level; `secrets` is a plain object keyed by
|
|
110
|
+
the kind's secret field keys. `validate(input, existing)` receives
|
|
111
|
+
`input = { name, config, secrets }` where `secrets` already merges stored values
|
|
112
|
+
(omitted / empty secrets keep their stored value, `null` clears one), and returns
|
|
113
|
+
the normalized `{ config, secrets }`. `test` may return `{ ok, detail, send:false }`
|
|
114
|
+
to skip the follow-up test message (`send` defaults to true: a test message is
|
|
115
|
+
sent after a successful check).
|
|
116
|
+
|
|
117
|
+
## Identities (members)
|
|
118
|
+
|
|
119
|
+
Members carry platform-scoped identities instead of Lark-only fields:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
user = { id, kind: 'person' | 'chat', name, role, datasourceIds, rowRules, disabled, note,
|
|
123
|
+
identities: [{ platform: 'lark'|'telegram'|'wecom'|'dingtalk', id, email? }] }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Legacy fields migrate on load: `larkOpenId → { platform:'lark', id }`,
|
|
127
|
+
`larkChatId → { platform:'lark', id }`, `email` stays and also matches any
|
|
128
|
+
platform that reports an email. `resolveActor({ platform, userId, email, name,
|
|
129
|
+
chatId, chatType })` replaces `resolveLarkActor` (kept as a thin alias).
|
|
130
|
+
|
|
131
|
+
Settings: `botDefaultRole` / `botDefaultDatasourceIds` replace
|
|
132
|
+
`larkDefaultRole` / `larkDefaultDatasourceIds` (read both; write the new keys).
|
|
133
|
+
|
|
134
|
+
## Bots
|
|
135
|
+
|
|
136
|
+
Bot configuration lives on the channel: `channel.bot = { enabled, allowP2p,
|
|
137
|
+
sessionScope: 'chat'|'user' }`. Every enabled bot channel runs its adapter's
|
|
138
|
+
`createBot`; `service/bots.js` owns lifecycle, the per-conversation queue, the
|
|
139
|
+
agent turn (`runAgentTurn`), actor resolution, card actions (opaque tokens,
|
|
140
|
+
rate limit, re-authorization), delivery log and audit. Conversations are keyed
|
|
141
|
+
`<channelId>:<chatId>[:<userId>]`.
|
|
142
|
+
|
|
143
|
+
Legacy `state.larkBot = { enabled, channelId, allowP2p, sessionScope }`
|
|
144
|
+
migrates to `channel.bot` on the referenced channel.
|
|
145
|
+
|
|
146
|
+
## Platform notes
|
|
147
|
+
|
|
148
|
+
| Platform | Push | Bot without public URL | Buttons | Charts |
|
|
149
|
+
|---|---|---|---|---|
|
|
150
|
+
| Lark / Feishu | webhook bot; app bot (chat / user / email) | long connection (`im.message.receive_v1`, `card.action.trigger`) | yes | native card charts / PNG |
|
|
151
|
+
| Telegram | Bot API `sendMessage` / `sendPhoto` to chat ids | long polling `getUpdates` | inline keyboard (`callback_query`) | PNG (`sendPhoto`) |
|
|
152
|
+
| WeCom 企业微信 | group robot webhook (markdown, image base64+md5) | AI bot long connection (`wss://openws.work.weixin.qq.com`, bot id + secret) | template card buttons where supported, else none | PNG via webhook image |
|
|
153
|
+
| DingTalk 钉钉 | custom robot webhook (+ HMAC sign) | enterprise app robot over Stream (`/v1.0/im/bot/messages/get`), reply via `sessionWebhook` | ActionCard links only | PNG not uploadable via webhook → tables |
|
|
154
|
+
|
|
155
|
+
Only verified behaviour should be claimed in the UI; anything tested against
|
|
156
|
+
fakes only is labelled in `CHANGELOG.md`.
|
|
157
|
+
|
|
158
|
+
## Page RPC contract (UI ↔ host)
|
|
159
|
+
|
|
160
|
+
Existing RPC names keep working (`listChannels`, `saveChannel`, `deleteChannel`,
|
|
161
|
+
`testChannel`, `listChannelChats`, `sendToChannel`, `sendDashboard`,
|
|
162
|
+
`larkBotStatus`, `saveLarkBot`, `restartLarkBot`, `listDeliveries`). New:
|
|
163
|
+
|
|
164
|
+
- `channelCatalog({ locale? })` → `[{ platform, name:{zh,en}, kinds:[{ id, label, description, fields, capabilities, setupSteps }] }]`
|
|
165
|
+
(secrets fields marked `secret: true`; never any value; with `locale`, `*Text` strings are added).
|
|
166
|
+
Adapters that fail to load are simply absent.
|
|
167
|
+
- `listChannels()` → `[{ id, name, type, platform, description, isDefault, chartMode, config: {non-secret fields},
|
|
168
|
+
secretsSet: { <secretKey>: true }, hasSecret, needsSecret?, unsupported?, lastSentAt?, lastError?,
|
|
169
|
+
capabilities, bot?: { enabled, allowP2p, sessionScope, status:{state,error?}, info? } }]`
|
|
170
|
+
(Lark channels also keep the legacy top-level `domain, appId, receiveIdType, receiveId, hasSign`;
|
|
171
|
+
`unsupported: true` when the channel's adapter is not loaded.)
|
|
172
|
+
- `saveChannel({ id?, name, type, description?, isDefault?, chartMode?, config: {...}, secrets: {...only changed...}, bot?: {...} })`
|
|
173
|
+
— legacy flat payloads (`webhookUrl`, `appId`, `appSecret`, `receiveId`…) are still accepted for Lark.
|
|
174
|
+
- `listChannelTargets({ id? , type?, config?, secrets? })` → `[{ id, name, type }]` (unsaved credentials allowed; nothing stored).
|
|
175
|
+
- `testChannel({ id })` → `{ ok, detail, … }` (runs `kind.test` when present, then sends a test message
|
|
176
|
+
unless the check returned `send: false`).
|
|
177
|
+
- `botStatus()` → `[{ channelId, channelName, platform, type, enabled, allowP2p, sessionScope, status, info, conversations: [...] }]`
|
|
178
|
+
(one entry per channel whose kind has `capabilities.bot`).
|
|
179
|
+
- `saveBot({ channelId, enabled?, allowP2p?, sessionScope? })` → bot status for that channel; `restartBot({ channelId })`.
|
|
180
|
+
- `listUsers` / `saveUser` use `identities: [{ platform, id, email? }]`; legacy `larkOpenId` / `larkChatId` / `email` accepted.
|
|
181
|
+
- `getSettings` exposes `botDefaultRole` / `botDefaultDatasourceIds`; `larkDefaultRole` / `larkDefaultDatasourceIds`
|
|
182
|
+
are still returned as aliases and accepted by `saveSettings` / `resetSettings`.
|
|
183
|
+
- `listDeliveries({ limit? })`: entries carry `platform` for channel deliveries; bot answers use `source: '<platform>bot'`
|
|
184
|
+
(`larkbot`, `telegrambot`, …).
|