新增: 挂机模式 P1 总装: 设置面板+顶栏徽章+边界卡精简+监控告警推送
- 新增 UnattendedPanel.vue 挂机配置面板(引擎自动认领看板任务,Settings KV 热读即改即生效) - Settings.vue 接入挂机面板;searchIndex.ts 补挂机搜索词条 - TopBar.vue 新增挂机进行中徽章(脉冲动效+透明度过渡) - TaskBoundaryCard.vue 精简边界状态判定(wallState 合并比对,删冗余 watch) - i18n zh-CN/en: settings/ai/aiChat 六文件补挂机词条双语 - unattended.rs 新增 push_monitor_alert 监控告警推送(异常快照+告警头直发 monitor_chat) - feishu_notify.rs push/push_card 出口重构收敛 - lib.rs 注册 unattended_status/unattended_set_enabled 两 IPC - aiShared.ts/useAiStreamingEvents.ts 挂机状态流接入
This commit is contained in:
@@ -20,6 +20,25 @@
|
||||
>
|
||||
⏳ {{ $t('aiChat.pendingApprovalCount', { n: pendingApprovalCount }) }}
|
||||
</button>
|
||||
<!-- 挂机引擎态徽章:开启=「挂机中」(在途附「执行中」),熔断=红徽点击复位;关闭态不显示 -->
|
||||
<button
|
||||
v-if="unattendedStatus?.breaker_tripped"
|
||||
type="button"
|
||||
class="ai-unattended-badge ai-unattended-badge--tripped"
|
||||
:title="$t('aiChat.unattendedResetHint')"
|
||||
@click="resetUnattendedBreaker"
|
||||
>
|
||||
{{ $t('aiChat.unattendedTripped') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="unattendedStatus?.enabled"
|
||||
type="button"
|
||||
class="ai-unattended-badge"
|
||||
:title="$t('aiChat.unattendedBadgeHint')"
|
||||
@click="jumpToUnattendedSettings"
|
||||
>
|
||||
🛌 {{ $t('aiChat.unattendedOn') }}<template v-if="unattendedStatus.in_flight_conv"> · {{ $t('aiChat.unattendedExecuting') }}</template>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 提供商状态 + 模型选择器 -->
|
||||
<div class="ai-header-center">
|
||||
@@ -281,7 +300,10 @@
|
||||
// 父持有 clear/compress/confirm 逻辑 + 单一 toast 源(provider-switched 回父弹 toast)。
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { useAiStore } from '../../stores/ai'
|
||||
import { useAppSettingsStore } from '../../stores/appSettings'
|
||||
import { aiApi } from '../../api/ai'
|
||||
import { pendingTaskProgress } from '../../composables/ai/useAiPendingState'
|
||||
import { convRounds } from '../../composables/ai/aiShared'
|
||||
@@ -335,6 +357,54 @@ const emit = defineEmits<{
|
||||
const store = useAiStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
// ── 挂机引擎态徽章(P1 总装):消费 unattended_status,开启/熔断时 30s 级轮询刷新 ──
|
||||
// 关闭态不显示徽章,轮询降频到 2 分钟(仍能感知设置页里的开启动作;单次 KV 级纯读,开销可忽略)
|
||||
interface UnattendedEngineStatus {
|
||||
enabled: boolean
|
||||
claim_scope: string
|
||||
in_flight_conv: string | null
|
||||
breaker_tripped: boolean
|
||||
poll_secs: number
|
||||
}
|
||||
const UNATTENDED_ACTIVE_MS = 30_000
|
||||
const UNATTENDED_IDLE_MS = 120_000
|
||||
const router = useRouter()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const unattendedStatus = ref<UnattendedEngineStatus | null>(null)
|
||||
let unattendedTimer: ReturnType<typeof setTimeout> | null = null
|
||||
// 卸载守卫:onBeforeUnmount 清定时器后,在途 invoke 的续延还会回来排下一轮,须拦住防卸载后空转
|
||||
let unattendedDisposed = false
|
||||
async function refreshUnattended() {
|
||||
if (unattendedDisposed) return
|
||||
try {
|
||||
unattendedStatus.value = await invoke<UnattendedEngineStatus>('unattended_status')
|
||||
} catch { /* IPC 失败保留上次状态,下轮重试 */ }
|
||||
if (unattendedDisposed) return
|
||||
const s = unattendedStatus.value
|
||||
const delay = s?.enabled || s?.breaker_tripped ? UNATTENDED_ACTIVE_MS : UNATTENDED_IDLE_MS
|
||||
unattendedTimer = setTimeout(() => void refreshUnattended(), delay)
|
||||
}
|
||||
/** 点击徽章跳设置页挂机区块(集成类首面板;activeCategory 由 appSettings 持久化驱动) */
|
||||
function jumpToUnattendedSettings() {
|
||||
void appSettings.set('df-settings-active-category', 'integrations')
|
||||
void router.push('/settings')
|
||||
}
|
||||
/** 熔断复位:开关 false→true(引擎在关态轮询里清熔断与失败计数,再开即恢复领单) */
|
||||
async function resetUnattendedBreaker() {
|
||||
try {
|
||||
await invoke('unattended_set_enabled', { enabled: false })
|
||||
await invoke('unattended_set_enabled', { enabled: true })
|
||||
} catch (e) {
|
||||
console.warn('[topbar] 挂机熔断复位失败:', e)
|
||||
}
|
||||
void refreshUnattended()
|
||||
}
|
||||
onMounted(() => { void refreshUnattended() })
|
||||
onBeforeUnmount(() => {
|
||||
unattendedDisposed = true
|
||||
if (unattendedTimer) { clearTimeout(unattendedTimer); unattendedTimer = null }
|
||||
})
|
||||
|
||||
// ══ 更多菜单 popout 状态(上下文管理操作收纳)══
|
||||
// 顶部按钮过多时,低频的清空/压缩上下文收入 ... popout;点击按钮切换,点击外部收起。
|
||||
const moreMenuOpen = ref(false)
|
||||
@@ -800,6 +870,31 @@ onBeforeUnmount(() => {
|
||||
outline: 1.5px solid var(--df-accent, #4a9eff);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
/* 挂机引擎态徽章(开启=accent 弱底;熔断=红底红字,点击复位) */
|
||||
.ai-unattended-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
background: var(--df-accent-bg);
|
||||
color: var(--df-accent);
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--df-radius-sm);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
font-family: inherit;
|
||||
appearance: none;
|
||||
border: none;
|
||||
transition: opacity 0.15s var(--df-ease);
|
||||
}
|
||||
.ai-unattended-badge:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
.ai-unattended-badge--tripped {
|
||||
background: var(--df-danger-bg);
|
||||
color: var(--df-danger);
|
||||
border: 0.5px solid var(--df-danger);
|
||||
}
|
||||
|
||||
/* ═══ 模型选择器(对话透明化 L1:紧凑下拉) ═══ */
|
||||
.ai-model-picker {
|
||||
|
||||
Reference in New Issue
Block a user