feat: v0.8.2 — 15 fixes + 4 features + 3 improvements

Fixes:
- Stop force-appending /v1 to API URLs (breaks Volcengine /v3 etc)
- SSH upgrade: --unset-all + --add for 4 git insteadOf rules
- Feishu: builtin detection, overlay→modal fix, select field, plugin version persistence
- Docker: HTML response detection, Web mode guidance
- Chat: runId dedup prevents duplicate messages
- Cron: RPC params name→id
- Channels: Gateway reload async (instant UI response), toggle cache invalidation
- Linux: auto sudo for non-root npm installs (libc geteuid)
- Control UI: dynamic hostname + auth token for remote access
- npm: mirror fallback (npmmirror→npmjs.org)
- QQBot: native binding friendly error message
- Error diagnosis: SSH vs Git-not-installed, native binding detection

Features:
- About page: company info (武汉晴辰天下网络科技有限公司)
- model-presets.js: shared module for models.js + assistant.js
- Feishu: dual plugin support (builtin vs official @larksuiteoapi)
- Assistant: provider preset quick-fill buttons

Improvements:
- Website: dynamic download links from latest.json + claw.qt.cool proxy
- Linux deploy docs: upgrade guide, Gitee mirror, sudo notes
- linux-deploy.sh: Gitee fallback + sudo npm + mirror retry
This commit is contained in:
晴天
2026-03-13 00:03:09 +08:00
parent f707b2301c
commit db30f29abf
25 changed files with 587 additions and 196 deletions

View File

@@ -49,6 +49,7 @@ let _currentAiBubble = null, _currentAiText = '', _currentAiImages = [], _curren
let _isStreaming = false, _isSending = false, _messageQueue = [], _streamStartTime = 0
let _lastRenderTime = 0, _renderPending = false, _lastHistoryHash = ''
let _streamSafetyTimer = null, _unsubEvent = null, _unsubReady = null, _unsubStatus = null
let _seenRunIds = new Set()
let _pageActive = false
let _errorTimer = null, _lastErrorMsg = null
let _attachments = []
@@ -886,6 +887,17 @@ function handleChatEvent(payload) {
if (payload.sessionKey && payload.sessionKey !== _sessionKey && _sessionKey) return
const { state } = payload
const runId = payload.runId
// 重复 run 过滤:跳过已完成的 runId 的后续事件Gateway 可能对同一消息触发多个 run
if (runId && state === 'final' && _seenRunIds.has(runId)) {
console.log('[chat] 跳过重复 final, runId:', runId)
return
}
if (runId && state === 'delta' && _seenRunIds.has(runId) && !_isStreaming) {
console.log('[chat] 跳过已完成 run 的 delta, runId:', runId)
return
}
if (state === 'delta') {
const c = extractChatContent(payload.message)
@@ -935,6 +947,14 @@ function handleChatEvent(payload) {
const hasContent = finalText || _currentAiImages.length || _currentAiVideos.length || _currentAiAudios.length || _currentAiFiles.length
// 忽略空 finalGateway 会为一条消息触发多个 run部分是空 final
if (!_currentAiBubble && !hasContent) return
// 标记 runId 为已处理,防止重复
if (runId) {
_seenRunIds.add(runId)
if (_seenRunIds.size > 200) {
const first = _seenRunIds.values().next().value
_seenRunIds.delete(first)
}
}
showTyping(false)
// 如果流式阶段没有创建 bubble从 final message 中提取
if (!_currentAiBubble && hasContent) {