import { computed, ref, watch } from 'vue'
import { locale, t } from '@/i18n/index.js'
/**
* The Backgrounds tab: running/finished tasks and terminals in one list, the
* row that is selected in the detail pane, and the labels/ages it shows.
*/
const TASK_ICONS = {
terminal: 'ph ph-terminal-window',
ssh_exec: 'ph ph-plugs-connected',
spawn_agent: 'ph ph-user-circle-gear',
code_exec: 'ph ph-code',
peer: 'ph ph-chats-circle',
}
export function useBackgrounds(chat) {
// { kind: 'task' | 'terminal', key }
const selectedBg = ref(null)
const backgroundsList = computed(() => {
const tasks = Object.values(chat.backgroundTasks).map(t => ({
kind: 'task',
key: t.taskId,
running: t.status === 'running',
startedAt: t.startedAt ?? '',
task: t,
}))
const terminals = Object.values(chat.terminals)
.filter(t => !t.closed || t.output)
.map(t => ({
kind: 'terminal',
key: t.name,
running: t.status === 'busy' && !t.closed,
startedAt: '',
terminal: t,
}))
const all = [...tasks, ...terminals]
return all.sort((a, b) => {
// running first, then newest started on top
if (a.running !== b.running) return a.running ? -1 : 1
return String(b.startedAt ?? '').localeCompare(String(a.startedAt ?? ''))
})
})
const activeBackgroundCount = computed(() =>
backgroundsList.value.filter(i => i.running).length,
)
const selectedTask = computed(() =>
selectedBg.value?.kind === 'task' ? (chat.backgroundTasks[selectedBg.value.key] || null) : null,
)
const selectedTerminal = computed(() =>
selectedBg.value?.kind === 'terminal' ? (chat.terminals[selectedBg.value.key] || null) : null,
)
// A task can finish (TTL, cancel) while selected — drop a dangling selection.
watch(() => [selectedTask.value, selectedTerminal.value], ([task, term]) => {
if (selectedBg.value && !task && !term) selectedBg.value = null
})
// Selection refers to per-session ids — reset on session switch.
watch(() => chat.currentId, () => { selectedBg.value = null })
function selectBackground(item) {
const sel = { kind: item.kind, key: item.key }
selectedBg.value = selectedBg.value?.kind === sel.kind && selectedBg.value?.key === sel.key ? null : sel
}
function isBgSelected(item) {
return Boolean(
selectedBg.value &&
selectedBg.value.kind === item.kind &&
selectedBg.value.key === item.key,
)
}
function backgroundIcon(item) {
return item.kind === 'terminal'
? 'ph ph-terminal-window'
: (TASK_ICONS[item.task.tool] || 'ph ph-arrows-out')
}
function backgroundTitle(item) {
return item.kind === 'terminal'
? item.terminal.name
: (item.task.argsSummary || item.task.tool)
}
function backgroundMeta(item) {
return item.kind === 'terminal'
? (item.terminal.description || item.terminal.command || t('artifacts.terminal'))
: t('artifacts.toolAndAge', { tool: item.task.tool, age: taskAge(item.task) })
}
function backgroundBadgeClass(item) {
if (item.kind === 'terminal') {
if (item.terminal.closed) return 'badge-error'
return item.terminal.status === 'busy' ? 'badge-success' : 'badge-secondary'
}
return taskBadgeBgClass(item.task)
}
function backgroundBadgeLabel(item) {
if (item.kind === 'terminal') {
if (item.terminal.closed) return t('artifacts.statusClosed')
return t(item.terminal.status === 'busy' ? 'artifacts.statusBusy' : 'artifacts.statusIdle')
}
return taskBadgeLabel(item.task)
}
function taskAge(task) {
const ts = task.finishedAt ?? task.startedAt
if (!ts) return ''
let seconds = (Date.now() - new Date(ts).getTime()) / 1000
if (!Number.isFinite(seconds) || seconds < 0) return t('common.justNow')
if (seconds < 60) return `${Math.round(seconds)}s`
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`
return `${Math.floor(seconds / 86400)}d`
}
function taskBadgeClass(task) {
if (task.status === 'running') return 'status-busy'
if (task.status === 'completed') return 'status-idle'
return 'status-closed' // failed / cancelled
}
// Kit badge variant for list badges (filled chip look).
function taskBadgeBgClass(task) {
if (task.status === 'running') return 'badge-success'
if (task.status === 'completed') return 'badge-secondary'
return 'badge-error' // failed / cancelled
}
function taskBadgeLabel(task) {
const map = {
running: t('artifacts.statusRunning'),
completed: t('artifacts.statusDone'),
failed: t('artifacts.statusFailed'),
cancelled: t('artifacts.statusStopped'),
}
return map[task.status] || task.status
}
function formatTime(value) {
if (!value) return t('common.notAvailable')
const date = new Date(value)
if (Number.isNaN(date.getTime())) return value
return date.toLocaleTimeString(locale.value, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
}
function formatUptime(seconds) {
if (seconds < 60) return `${Math.round(seconds)}s`
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
return `${h}h ${m}m`
}
return {
selectedBg,
backgroundsList,
activeBackgroundCount,
selectedTask,
selectedTerminal,
selectBackground,
isBgSelected,
backgroundIcon,
backgroundTitle,
backgroundMeta,
backgroundBadgeClass,
backgroundBadgeLabel,
taskBadgeClass,
taskBadgeLabel,
formatTime,
formatUptime,
}
}