Commit Graph

9 Commits

Author SHA1 Message Date
lambert
a92f76ff87
fix(ui): clipboard copy fails in non-HTTPS context
Use textarea + execCommand fallback when navigator.clipboard is unavailable. Consolidates 5 call sites into a single helper.
2026-05-08 10:23:58 +08:00
matevip
8ead7b4254 feat(channels): card description fallback + tighter card layout 2026-05-02 07:39:33 +08:00
matevip
8ce7e7d26d feat(ui): agent integration of SkillIcon + SkillIconPicker 2026-05-01 10:58:15 +08:00
matevip
e759ad4a1b sync: settings UI polish, channel reliability fixes, DeepSeek cross-turn fix
- Settings → Models: inline API key, frosted drawer, dark-mode polish, provider icons, i18n sweep
- WeChat Work channel: rebuild HttpClient on reconnect, dedup failure signals, route auth_succeed errcode!=0 through failure handler
- Channel framework: per-adapter error isolation, QR auth SPI, health indicators
- Agent: patch cross-turn assistants for DeepSeek thinking-mode
- GitHub: bilingual issue templates with required fields
2026-04-29 11:22:47 +08:00
matevip
b4aef56c89 fix(channels): close QR loading dead-window for feishu and dingtalk register flows
Reported issue: click 'scan to create' -> button momentarily flickers
loading -> button re-enables but no QR shows up -> blank for 1-2 seconds
-> QR suddenly appears. Looks broken even though it works.

Root cause: loading.value flipped back to false the moment the begin HTTP
call returned (sessionId in hand), but the actual QR image only arrives on
the first status poll, which the existing code waited a full 2 seconds
for. Between begin completing and the first poll firing the UI was a
disabled button + nothing.

Three coordinated changes:
- useFeishuAppRegister and useDingTalkAppRegister: keep loading.value true
  through begin AND across the polls, only flip false when the QR image
  is actually populated (or a terminal failure status arrives). Also run
  an immediate first poll right after begin instead of waiting for the
  setInterval tick — usually the first poll already has the rendered QR
  for dingtalk, and pushes the feishu user roughly 2 seconds closer.
- ChannelEditModal: same-sized loading placeholder (min-height 240px,
  matching the QR card) that renders when loading is true and no QR is
  in hand. CSS spinner ring tinted with the channel brand color (feishu
  indigo, dingtalk blue) and a new
  channels.{feishu,dingtalk}Register.qrcodeLoading hint. The placeholder
  swaps to the real image with no layout shift.
- i18n: new qrcodeLoading key in zh-CN and en-US for both flows.

Net effect: click to spinner-visible is ~50ms; the user is never staring
at a frozen button-without-content again.
2026-04-28 11:14:14 +08:00
matevip
acf6eccb3a feat(dingtalk): one-click bot creation via OAuth device flow
Mirrors the feishu one-click flow: scan a QR with the DingTalk app,
approve, and the bot's client_id / client_secret get auto-filled instead
of forcing the user through the open-dev console. Saves about seven
manual steps per channel setup.

Backend
- Bump dingtalk-stream from 1.3.5 to 1.3.12. Diff against the classes we
  depend on (OpenDingTalkStreamClient, ChatbotMessage, MessageContent,
  GenericEventListener) is empty — pure point-release bumps, no API churn.
- New DingTalkAppRegistrationService: synchronously runs init + begin
  against /app/registration/{init,begin} on oapi.dingtalk.com to obtain
  the device_code and verification URL, then spawns a daemon worker that
  polls /app/registration/poll every 5s until SUCCESS / FAIL / EXPIRED is
  returned. Sessions evict after 7 minutes, worker has a 6-minute hard
  runtime cap, transient HTTP errors do not terminate the loop. Same
  shape as the feishu service, but written from scratch because the
  dingtalk-stream SDK doesn't wrap this OAuth device flow.
- Two new endpoints under /api/v1/channels/webhook:
  POST /dingtalk/register/begin returns session_id;
  GET  /dingtalk/register/status returns status + qrcode_img (data URI
  PNG, ZXing-encoded from the verification URL, matching the feishu and
  weixin flows). Status surface: waiting / confirmed / expired / denied.

Frontend
- channelApi.dingtalkRegisterBegin / dingtalkRegisterStatus.
- New useDingTalkAppRegister composable, structurally identical to
  useFeishuAppRegister minus the domain argument. Stops polling on
  terminal status, fires onConfirmed with {clientId, clientSecret}.
- ChannelEditModal: dingtalk-register-card rendered when channelType is
  dingtalk, scoped DingTalk blue (#1f79ff) to differentiate from feishu's
  indigo. onConfirmed writes channelConfig.client_id / client_secret so
  the existing form fields update reactively.
- i18n: channels.dingtalkRegister.* keys for title / hint / button states
  / scan / confirmed / expired / denied / startFailed.
2026-04-28 11:12:22 +08:00
matevip
a27898507c feat(feishu): one-click app creation via official SDK device-flow registration
Saves the user the entire 'go to the open platform -> create an enterprise
app -> copy App ID and Secret' detour. Click a button in the channel form,
scan the QR code, confirm authorization, credentials are auto-filled.

Backend
- Bump com.larksuite.oapi:oapi-sdk from 2.5.3 to 2.6.1, which adds the
  scene/registration package wrapping the device-code flow.
- New FeishuAppRegistrationService: each begin() creates a sessionId,
  spawns a worker thread, runs the SDK's blocking RegisterApp.register
  with onQRCode and onStatusChange wired into a per-session state machine
  (PENDING -> WAITING -> CONFIRMED / EXPIRED / DENIED / ERROR). The
  session caches the QR data URI so ZXing only encodes once per attempt.
  Sessions evict after 5 minutes so closed browsers don't leak the map.
- Two new webhook endpoints under /api/v1/channels/webhook/feishu:
  POST /register/begin returns session_id, GET /register/status returns
  status + qrcode_img (data URI base64 PNG, ZXing-encoded from the SDK's
  verification URL — the raw URL would render as a broken image, so the
  encoding step matches the WeCom flow).
- SDK detail caught the hard way: don't pass .domain() or .larkDomain().
  The SDK defaults are accounts.feishu.cn / accounts.larksuite.com (the
  registration endpoints). open.feishu.cn is the open-API endpoint, a
  completely different service. Passing the wrong one makes the SDK parse
  HTML as JSON and emit invalid_response.

Frontend
- channelApi: feishuRegisterBegin / feishuRegisterStatus.
- New useFeishuAppRegister composable: state machine that begins the
  session, polls status every 2s, prefers qrcode_img over qrcode_url for
  the <img> src, stops on terminal status, fires onConfirmed with
  {appId, appSecret}.
- ChannelEditModal: a new feishu-register-card above the wecom one. The
  composable's onConfirmed writes channelConfig.app_id / app_secret, so
  the existing form fields update reactively.
- i18n: channels.feishuRegister.* keys for title / hint / button states /
  scan / confirmed / expired / denied / error.
2026-04-28 11:11:36 +08:00
matevip
b982d4a2d0 feat(feishu): default connection to WebSocket and hide webhook UI when unused
Backend (FeishuChannelAdapter):
- Default connection_mode flips webhook -> websocket on doStart and doReconnect.
- Stale event filter: drop events whose message.create_time is older than
  stale_event_threshold_seconds (default 30s) so SDK reconnect replays do not
  re-trigger the agent.
- Silent disconnect watchdog runs every 60s; if no events arrive for
  silent_disconnect_threshold_seconds (default 1800s) after the first event,
  call onDisconnected to force a reconnect cycle. Setting the threshold to 0
  disables the watchdog. The watchdog is scheduled before wsClient.start() on
  the bring-up path because that call blocks indefinitely.
- Quoted message context: when a reply has parent_id set, fetch the parent
  via GET /open-apis/im/v1/messages/{id}, summarize per msg_type (text / post
  first paragraph / [Image]/[File]/[Audio]/[Video] placeholders, capped at
  200 chars), and prepend [Quoted: ...] to both content text and the first
  content part. LRU-cached (200) per message_id.
- AbstractChannelAdapter gains getConfigLong helper for numeric config keys.

Frontend:
- types/index.ts feishu fields: default connection_mode is websocket; the
  recommended option moves to the top; verification_token and encrypt_key
  get showIf so they only render in webhook mode; new enable_quoted_context
  switch (default on) exposes the quoted-message feature.
- ChannelEditModal builds a feishu-specific WEBHOOK_GUIDES path that picks
  webhookStep vs websocketStep based on connection_mode, so users only see
  steps for the mode they're using.
- i18n: split feishu.step3/step4 into webhookStep/websocketStep, rename
  step5 to permissionStep. Channel type labels in zh-CN drop bilingual
  prefix (e.g. 'Feishu / Lark (飞书)' -> '飞书').

Migrations:
- V52 was a no-op the first time it ran (matched compact JSON only) and
  Flyway refused to re-run after the SQL was fixed. V52 is documented as a
  no-op; V53 carries the actual UPDATE with REPLACE covering both compact
  and pretty-printed JSON, and an idempotent WHERE for rows already on
  websocket. h2 and mysql variants stay in lockstep.
2026-04-28 11:09:17 +08:00
matevip
22894ac4b1 perf(channels): split Channels.vue, lazy-load modal, async locales, keep-alive route
- Extract create/edit modal into ChannelEditModal.vue (defineAsyncComponent),
  shrinking Channels.vue from 1438 to 370 lines and dropping ~30KB from the
  initial route chunk.
- Move side-effect logic into composables: useWeixinQrcodePoll (QR + 2s status
  poll, auto-cleanup) and useWecomBotAuth (lazy SDK script with module-level
  promise dedupe). Pure config-JSON helpers move to utils/channelConfigJson.ts.
- Switch i18n locales from static imports to dynamic import keyed by current
  locale; applyLocale becomes async to avoid first-render flicker.
- /channels route opts into keep-alive (meta.keepAlive=true). Channels.vue
  pauses status polling in onDeactivated and resumes in onActivated, with an
  isActive guard to prevent late-resolving timers from leaking after navigation.
- Initial load goes from serial 3-RTT to Promise.all + 4-card el-skeleton.
2026-04-28 11:08:36 +08:00