优化: 前端DRY收敛与命名对齐
审批等待格式化与阈值沉 utils/time 正典 相对时间阶梯三套收敛 formatRelative 参数化 灵感状态表沉 constants,透传通道退役 aggText/clearOwn 等命名对齐,IPC 参数名统一 conversation_id
This commit is contained in:
@@ -55,7 +55,7 @@
|
||||
<!-- H1 修复:审批挂起时长提示(对齐 ToolCard: >2min 橙色 / >5min 红色脉冲)。
|
||||
后端 APPROVAL_TIMEOUT_MS 5min 静默拒绝,浮窗原本无计时,用户误以为还有时间。
|
||||
基于审批到达时间(popup 首次见到该 tc.id 的时刻)计时,与 ToolCard 同款语义同款样式。 -->
|
||||
<div v-if="waitSecsFor(tc.id) > 0" class="popup-item-wait" :class="'is-' + waitLevelFor(tc.id)">
|
||||
<div v-if="waitSecsFor(tc.id) > 0" class="popup-item-wait" :class="'is-' + waitLevelOf(waitSecsFor(tc.id))">
|
||||
{{ $t('aiTool.waitTime', { time: formatWaitTime(waitSecsFor(tc.id)) }) }}
|
||||
</div>
|
||||
<!-- path 类:目录提示 -->
|
||||
@@ -153,6 +153,7 @@ import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { toolDisplayName } from '@/composables/ai/useToolCardHeader'
|
||||
import { dispatchApprovalIPC } from '@/composables/ai/useToolApproval'
|
||||
import { ApprovalPopupEvents } from '@/composables/ai/useApprovalPopup'
|
||||
import { formatWaitTime, waitLevelOf } from '@/utils/time'
|
||||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import type { AiToolCallInfo } from '@/api/types'
|
||||
@@ -189,30 +190,18 @@ function answerConfirm(ok: boolean) {
|
||||
// ── H1:审批挂起时长计时(对齐 ToolCard useToolApproval 的 waitSecs/waitLevel)──
|
||||
// 浮窗独立于主窗口,不直接复用 useToolApproval(那个绑定单 tc + 完整状态机 emit/防抖,
|
||||
// 浮窗已用 processingId/runApproval 自管)。这里基于「到达时间」(popup 首次见到 tc.id 的时刻)
|
||||
// 每秒刷新。语义/样式/阈值与 ToolCard 同款:120s long / 300s urgent。
|
||||
// 每秒刷新。格式化(formatWaitTime)与分级阈值(waitLevelOf,120s long / 300s urgent)
|
||||
// 收敛至 @/utils/time,与 ToolCard 同源。
|
||||
/** tc.id → 到达时刻(ms)。审批首次出现在 approvals 即记时;移除即清。 */
|
||||
const arrivalTimes = reactive(new Map<string, number>())
|
||||
/** tc.id → 已等待秒数(每秒 tick 更新)。 */
|
||||
const waitSecsMap = reactive(new Map<string, number>())
|
||||
let _waitTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
/** 格式化秒为 MM:SS(与 useToolApproval.formatWaitTime 同款) */
|
||||
function formatWaitTime(secs: number): string {
|
||||
const m = Math.floor(secs / 60)
|
||||
const s = secs % 60
|
||||
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
|
||||
}
|
||||
/** 取某条审批已等待秒数(无记录返回 0) */
|
||||
function waitSecsFor(id: string): number {
|
||||
return waitSecsMap.get(id) ?? 0
|
||||
}
|
||||
/** 取等待级别(对齐 ToolCard: 300s urgent / 120s long / 其余 normal) */
|
||||
function waitLevelFor(id: string): 'normal' | 'long' | 'urgent' {
|
||||
const s = waitSecsFor(id)
|
||||
if (s >= 300) return 'urgent'
|
||||
if (s >= 120) return 'long'
|
||||
return 'normal'
|
||||
}
|
||||
/** 每秒刷新所有挂起审批的 waitSecs(单 timer 驱动多 tc,避免每条一个 interval) */
|
||||
function _tickWaitSecs() {
|
||||
const now = Date.now()
|
||||
|
||||
+7
-19
@@ -70,7 +70,7 @@
|
||||
</div>
|
||||
<p class="idea-desc-preview">{{ descPreviews.get(idea.id) || '' }}</p>
|
||||
<div class="idea-card-footer">
|
||||
<span class="status-tag" :class="'status-' + idea.status">{{ $t(statusLabelKey(idea.status)) }}</span>
|
||||
<span class="status-tag" :class="'status-' + idea.status">{{ $t(ideaStatusLabelKey(idea.status)) }}</span>
|
||||
<span class="idea-date">{{ formatDate(idea.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,7 +93,6 @@
|
||||
:eval-error="evalError"
|
||||
:promoting="promoting"
|
||||
:deleting="deleting"
|
||||
:status-options="statusOptions"
|
||||
@evaluate="evaluateCurrentIdea"
|
||||
@promote="promoteToProject"
|
||||
@delete="deleteCurrentIdea"
|
||||
@@ -165,6 +164,7 @@ import type { IdeaStatus, IdeaQuery } from '../api/types'
|
||||
import { formatDate } from '../utils/time'
|
||||
import { stripMd } from '../utils/markdown'
|
||||
import { scoreTier } from '../utils/ideaEval'
|
||||
import { ideaStatusLabelKey } from '../constants/project'
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||
import IdeaDetail from '../components/ideas/IdeaDetail.vue'
|
||||
import { useConfirm } from '../composables/useConfirm'
|
||||
@@ -214,19 +214,7 @@ const filters: { key: FilterKey; labelKey: string; icon: string }[] = [
|
||||
{ key: 'promoted', labelKey: 'ideas.filter.promoted', icon: '🚀' },
|
||||
]
|
||||
|
||||
// ── 状态映射(单一数据源,列表/详情/下拉共用) ──
|
||||
const statusOptions: { value: IdeaStatus; labelKey: string }[] = [
|
||||
{ value: 'draft', labelKey: 'ideas.status.draft' },
|
||||
{ value: 'pending_review', labelKey: 'ideas.status.pending_review' },
|
||||
{ value: 'approved', labelKey: 'ideas.status.approved' },
|
||||
{ value: 'promoted', labelKey: 'ideas.status.promoted' },
|
||||
{ value: 'rejected', labelKey: 'ideas.status.rejected' },
|
||||
]
|
||||
|
||||
// 任意 status 字符串 → 对应 i18n key;未知状态回退到原值显示
|
||||
function statusLabelKey(status: IdeaStatus): string {
|
||||
return statusOptions.find(s => s.value === status)?.labelKey ?? status
|
||||
}
|
||||
// ── 状态映射(单一数据源在 @/constants/project,列表/详情/下拉共用) ──
|
||||
|
||||
// ── 后端查询构造status/keyword/order_by 下沉后端 WHERE) ──
|
||||
//
|
||||
@@ -260,11 +248,11 @@ function loadCurrentView() {
|
||||
// 让 store.loading 驱动「加载中」空态显示(对齐 Tasks.vue 三态)。
|
||||
// G5.3:keyword 防抖 timer 用 useTimerOwnership 每实例管理 + onUnmounted 自动清理,
|
||||
// 根治卸载后 300ms 内 timer 触发 → store.loadIdeas 写单例 store(setState-after-unmount)。
|
||||
const { setOwnTimeout, clearOwn } = useTimerOwnership()
|
||||
const { setOwnTimeout, clearOwnTimeout } = useTimerOwnership()
|
||||
let keywordDebounce: ReturnType<typeof setTimeout> | null = null
|
||||
async function reloadIdeas(immediate = false) {
|
||||
if (keywordDebounce) {
|
||||
clearOwn(keywordDebounce)
|
||||
clearOwnTimeout(keywordDebounce)
|
||||
keywordDebounce = null
|
||||
}
|
||||
const run = () => loadCurrentView()
|
||||
@@ -444,8 +432,8 @@ async function onStatusChange(newStatus: IdeaStatus) {
|
||||
if (needsConfirm) {
|
||||
const msg = t('ideas.confirmStatus', {
|
||||
title: currentIdea.value.title,
|
||||
from: t(statusOptions.find(s => s.value === from)?.labelKey ?? from),
|
||||
to: t(statusOptions.find(s => s.value === newStatus)?.labelKey ?? newStatus),
|
||||
from: t(ideaStatusLabelKey(from)),
|
||||
to: t(ideaStatusLabelKey(newStatus)),
|
||||
})
|
||||
if (!await confirmDialog(msg, t('common.confirm'))) return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user