Fix task UI JavaScript
- Fix loadTaskStats to use /api/tasks/stats/json endpoint - Add missing JS functions: pauseTask, cancelTask, showDetailedView - Add htmx:afterSwap listener to reinitialize tasks app on HTMX load - Prevent duplicate WebSocket connections - Add task creation feedback in UI (success/error messages) - Add tasks.js script to index.html
This commit is contained in:
parent
4a3eb0cc4f
commit
ca34a05d4c
5 changed files with 1723 additions and 30 deletions
|
|
@ -238,12 +238,13 @@
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
||||||
[data-theme="sentient"] .status-filter,
|
[data-theme="sentient"] .status-filter,
|
||||||
[data-theme="sentient"] .filter-tab {
|
[data-theme="sentient"] .filter-tab,
|
||||||
|
[data-theme="sentient"] .filter-pill {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
background: transparent;
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
|
@ -253,37 +254,97 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="sentient"] .status-filter:hover,
|
[data-theme="sentient"] .status-filter:hover,
|
||||||
[data-theme="sentient"] .filter-tab:hover {
|
[data-theme="sentient"] .filter-tab:hover,
|
||||||
|
[data-theme="sentient"] .filter-pill:hover {
|
||||||
background: var(--surface-hover);
|
background: var(--surface-hover);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
border-color: var(--border-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="sentient"] .status-filter.active,
|
[data-theme="sentient"] .status-filter.active,
|
||||||
[data-theme="sentient"] .filter-tab.active {
|
[data-theme="sentient"] .filter-tab.active,
|
||||||
background: var(--accent);
|
[data-theme="sentient"] .filter-pill.active {
|
||||||
color: #000000;
|
background: var(--surface-active);
|
||||||
|
color: var(--text);
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Base count badge */
|
||||||
[data-theme="sentient"] .status-filter .count,
|
[data-theme="sentient"] .status-filter .count,
|
||||||
[data-theme="sentient"] .filter-tab .count {
|
[data-theme="sentient"] .filter-tab .count,
|
||||||
|
[data-theme="sentient"] .filter-pill .pill-count {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-width: 20px;
|
min-width: 22px;
|
||||||
height: 20px;
|
height: 22px;
|
||||||
padding: 0 6px;
|
padding: 0 7px;
|
||||||
background: var(--surface-hover);
|
background: var(--surface-hover);
|
||||||
border-radius: 10px;
|
border-radius: 11px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Complete - Green */
|
||||||
|
[data-theme="sentient"] .filter-pill[data-filter="complete"] .pill-count {
|
||||||
|
background: var(--success);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active Intents - Lime/Accent */
|
||||||
|
[data-theme="sentient"] .filter-pill[data-filter="active"] .pill-count,
|
||||||
|
[data-theme="sentient"] .filter-pill[data-filter="all"] .pill-count {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Awaiting Decision - Yellow/Warning */
|
||||||
|
[data-theme="sentient"] .filter-pill[data-filter="awaiting"] .pill-count {
|
||||||
|
background: var(--warning);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Paused - Gray */
|
||||||
|
[data-theme="sentient"] .filter-pill[data-filter="paused"] .pill-count {
|
||||||
|
background: var(--text-tertiary);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blocked/Issues - Red */
|
||||||
|
[data-theme="sentient"] .filter-pill[data-filter="blocked"] .pill-count {
|
||||||
|
background: var(--error);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active pill state */
|
||||||
[data-theme="sentient"] .status-filter.active .count,
|
[data-theme="sentient"] .status-filter.active .count,
|
||||||
[data-theme="sentient"] .filter-tab.active .count {
|
[data-theme="sentient"] .filter-tab.active .count,
|
||||||
background: rgba(0, 0, 0, 0.2);
|
[data-theme="sentient"] .filter-pill.active .pill-count {
|
||||||
color: #000000;
|
box-shadow: 0 0 8px currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Time Saved Badge */
|
||||||
|
[data-theme="sentient"] .time-saved-badge {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .time-saved-badge .time-label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .time-saved-badge .time-value {
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================ */
|
/* ============================================ */
|
||||||
|
|
@ -485,20 +546,52 @@
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="sentient"] .decision-btn-primary {
|
[data-theme="sentient"] .decision-btn-primary,
|
||||||
|
[data-theme="sentient"] .btn-primary,
|
||||||
|
[data-theme="sentient"] .button-primary {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: #000000;
|
color: #000000;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="sentient"] .decision-btn-primary:hover {
|
[data-theme="sentient"] .decision-btn-primary:hover,
|
||||||
|
[data-theme="sentient"] .btn-primary:hover,
|
||||||
|
[data-theme="sentient"] .button-primary:hover {
|
||||||
background: var(--accent-hover);
|
background: var(--accent-hover);
|
||||||
box-shadow: var(--shadow-accent);
|
box-shadow: 0 0 20px var(--accent-glow);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create & Run Button - High Visibility */
|
||||||
|
[data-theme="sentient"] .btn-create-run {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000000;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-create-run:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
box-shadow: 0 0 25px var(--accent-glow);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-create-run .btn-text {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="sentient"] .decision-btn-secondary {
|
[data-theme="sentient"] .decision-btn-secondary {
|
||||||
|
|
@ -856,7 +949,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse-accent {
|
@keyframes pulse-accent {
|
||||||
0%, 100% {
|
0%,
|
||||||
|
100% {
|
||||||
box-shadow: 0 0 0 0 var(--accent-glow);
|
box-shadow: 0 0 0 0 var(--accent-glow);
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
|
|
@ -975,3 +1069,602 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="sentient"] .action-btn.pause:hover {
|
[data-theme="sentient"] .action-btn.pause:hover {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* RICH TASK DETAIL PANEL - SENTIENT THEME */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-detail-rich {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .detail-header-rich {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .detail-title-rich {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-badge-rich {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-badge-rich.status-running,
|
||||||
|
[data-theme="sentient"] .status-badge-rich.status-pending {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-badge-rich.status-completed {
|
||||||
|
background: var(--success);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-badge-rich.status-error,
|
||||||
|
[data-theme="sentient"] .status-badge-rich.status-failed {
|
||||||
|
background: var(--error);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-badge-rich.status-paused {
|
||||||
|
background: var(--warning);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Boxes */
|
||||||
|
[data-theme="sentient"] .detail-section-box {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .section-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Section */
|
||||||
|
[data-theme="sentient"] .status-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
box-shadow: 0 0 8px var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-dot.status-running {
|
||||||
|
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-dot.status-completed {
|
||||||
|
background: var(--success);
|
||||||
|
box-shadow: 0 0 8px var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-dot.status-error {
|
||||||
|
background: var(--error);
|
||||||
|
box-shadow: 0 0 8px var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-dot {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-text {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-meta span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .error-alert {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: var(--error-bg);
|
||||||
|
border: 1px solid var(--error);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .error-icon {
|
||||||
|
color: var(--error);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .error-text {
|
||||||
|
color: var(--error);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-details {
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-indicator {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-indicator.active {
|
||||||
|
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-step-name {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .status-step-note {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Bar Rich */
|
||||||
|
[data-theme="sentient"] .detail-progress-rich {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-bar-rich {
|
||||||
|
height: 8px;
|
||||||
|
background: var(--surface);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-fill-rich {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--accent) 0%,
|
||||||
|
var(--accent-hover) 100%
|
||||||
|
);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
box-shadow: 0 0 10px var(--accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-info-rich {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-label-rich {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Log Section */
|
||||||
|
[data-theme="sentient"] .progress-log-content {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-group {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-group-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-group-header:hover {
|
||||||
|
background: var(--surface-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-group-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-group-toggle {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-step-badge {
|
||||||
|
padding: 3px 10px;
|
||||||
|
background: var(--accent);
|
||||||
|
color: #000;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-status-badge {
|
||||||
|
padding: 3px 10px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--surface-active);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-status-badge.completed {
|
||||||
|
background: var(--success-bg);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-group-items {
|
||||||
|
padding: 8px 0 0 20px;
|
||||||
|
border-left: 2px solid var(--border);
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--surface);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-dot.completed {
|
||||||
|
background: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-dot.active {
|
||||||
|
background: var(--accent);
|
||||||
|
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-item-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-item-badge {
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: var(--surface-active);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-item-status {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-subitem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 12px 6px 28px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-subdot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-subdot.completed {
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-subitem-name {
|
||||||
|
flex: 1;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .log-duration {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminal Section Rich */
|
||||||
|
[data-theme="sentient"] .terminal-section-rich {
|
||||||
|
background: #0d0d0d;
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .section-header-rich {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-dot-rich {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--text-tertiary);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-dot-rich.active {
|
||||||
|
background: var(--accent);
|
||||||
|
box-shadow: 0 0 8px var(--accent);
|
||||||
|
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-stats-rich {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-stats-rich strong {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-output-rich {
|
||||||
|
background: #000;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-height: 150px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-line {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-line.current {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-line.error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-line.success {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-footer-rich {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .terminal-footer-rich strong {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Intent Section */
|
||||||
|
[data-theme="sentient"] .intent-text-rich {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons */
|
||||||
|
[data-theme="sentient"] .detail-actions-rich {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-action-rich {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-action-rich:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-action-rich.btn-pause {
|
||||||
|
border-color: var(--warning);
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-action-rich.btn-pause:hover {
|
||||||
|
background: var(--warning-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-action-rich.btn-cancel {
|
||||||
|
border-color: var(--error);
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-action-rich.btn-cancel:hover {
|
||||||
|
background: var(--error-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-action-rich.btn-detailed {
|
||||||
|
margin-left: auto;
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .btn-action-rich.btn-detailed:hover {
|
||||||
|
background: var(--accent-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sentient Scrollbar for task detail */
|
||||||
|
[data-theme="sentient"] .task-detail-rich::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-detail-rich::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-detail-rich::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-detail-rich::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-content::-webkit-scrollbar,
|
||||||
|
[data-theme="sentient"] .terminal-output-rich::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-content::-webkit-scrollbar-track,
|
||||||
|
[data-theme="sentient"] .terminal-output-rich::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-content::-webkit-scrollbar-thumb,
|
||||||
|
[data-theme="sentient"] .terminal-output-rich::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .progress-log-content::-webkit-scrollbar-thumb:hover,
|
||||||
|
[data-theme="sentient"] .terminal-output-rich::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Task Card Sentient Enhancements */
|
||||||
|
[data-theme="sentient"] .task-card {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-card:hover {
|
||||||
|
border-color: var(--card-hover-border);
|
||||||
|
box-shadow: var(--shadow-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-card.selected {
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 20px rgba(212, 245, 5, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-card::before {
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-card.status-complete::before {
|
||||||
|
background: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="sentient"] .task-card.status-error::before,
|
||||||
|
[data-theme="sentient"] .task-card.status-failed::before {
|
||||||
|
background: var(--error);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
<script>
|
<script>
|
||||||
htmx.config.allowEval = true;
|
htmx.config.allowEval = true;
|
||||||
htmx.config.includeIndicatorStyles = false;
|
htmx.config.includeIndicatorStyles = false;
|
||||||
|
htmx.config.timeout = 300000; // 5 minutes for long-running LLM operations
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
@ -966,6 +967,7 @@
|
||||||
<!-- Core scripts -->
|
<!-- Core scripts -->
|
||||||
<script src="js/theme-manager.js"></script>
|
<script src="js/theme-manager.js"></script>
|
||||||
<script src="js/htmx-app.js"></script>
|
<script src="js/htmx-app.js"></script>
|
||||||
|
<script src="tasks/tasks.js"></script>
|
||||||
|
|
||||||
<!-- Application initialization -->
|
<!-- Application initialization -->
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,40 @@
|
||||||
color: var(--error, #ef4444);
|
color: var(--error, #ef4444);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.intent-result .intent-success {
|
||||||
|
color: var(--success, #22c55e);
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: rgba(34, 197, 94, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intent-result .intent-error {
|
||||||
|
color: var(--error, #ef4444);
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.intent-result .result-progress {
|
.intent-result .result-progress {
|
||||||
height: 6px;
|
height: 6px;
|
||||||
background: var(--surface, #1a1a1a);
|
background: var(--surface, #1a1a1a);
|
||||||
|
|
@ -1812,6 +1846,42 @@
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
LLM STREAMING TERMINAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.floating-llm-terminal {
|
||||||
|
max-height: 120px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg, #0a0a0a);
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary, #a1a1a1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-llm-terminal .llm-output {
|
||||||
|
color: var(--primary, #c5f82a);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 2px 0;
|
||||||
|
animation: fadeIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
ACTIVITY METRICS PANEL
|
ACTIVITY METRICS PANEL
|
||||||
============================================================================= */
|
============================================================================= */
|
||||||
|
|
@ -2018,3 +2088,570 @@
|
||||||
right: 16px;
|
right: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
RICH TASK DETAIL PANEL - BASE STYLES (All Themes)
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.task-detail-rich {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header-rich {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--border, #2a2a2a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title-rich {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text, #fff);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-rich {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
background: var(--surface-hover, #333);
|
||||||
|
color: var(--text, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-rich.status-running,
|
||||||
|
.status-badge-rich.status-pending {
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-rich.status-completed {
|
||||||
|
background: var(--success, #22c55e);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-rich.status-error,
|
||||||
|
.status-badge-rich.status-failed {
|
||||||
|
background: var(--error, #ef4444);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-rich.status-paused {
|
||||||
|
background: var(--warning, #f59e0b);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Boxes */
|
||||||
|
.detail-section-box {
|
||||||
|
background: var(--surface, #161616);
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Section */
|
||||||
|
.status-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.status-running {
|
||||||
|
animation: pulse-status-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.status-completed {
|
||||||
|
background: var(--success, #22c55e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.status-error {
|
||||||
|
background: var(--error, #ef4444);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-status-dot {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-alert {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: rgba(239, 68, 68, 0.15);
|
||||||
|
border: 1px solid var(--error, #ef4444);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
color: var(--error, #ef4444);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: var(--error, #ef4444);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-details {
|
||||||
|
border-top: 1px solid var(--border-light, #222);
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator.active {
|
||||||
|
animation: pulse-status-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-step-name {
|
||||||
|
color: var(--text, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-step-note {
|
||||||
|
color: var(--text-tertiary, #666);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Bar Rich */
|
||||||
|
.detail-progress-rich {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-rich {
|
||||||
|
height: 8px;
|
||||||
|
background: var(--surface, #161616);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill-rich {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-info-rich {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-label-rich {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Log Section */
|
||||||
|
.progress-log-content {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-group {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-group-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--surface-hover, #1e1e1e);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-group-header:hover {
|
||||||
|
background: var(--surface-active, #252525);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-group-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text, #fff);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-group-toggle {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-tertiary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-step-badge {
|
||||||
|
padding: 3px 10px;
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
color: #000;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-status-badge {
|
||||||
|
padding: 3px 10px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--surface-active, #252525);
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-status-badge.completed {
|
||||||
|
background: rgba(34, 197, 94, 0.15);
|
||||||
|
color: var(--success, #22c55e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-group-items {
|
||||||
|
padding: 8px 0 0 20px;
|
||||||
|
border-left: 2px solid var(--border, #2a2a2a);
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--surface, #161616);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--border, #2a2a2a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-dot.completed {
|
||||||
|
background: var(--success, #22c55e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-dot.running {
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
animation: pulse-status-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-dot.failed {
|
||||||
|
background: var(--error, #ef4444);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-dot.pending {
|
||||||
|
background: var(--text-tertiary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-dot.active {
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
animation: pulse-status-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item-badge {
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: var(--surface-active, #252525);
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item-status {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--success, #22c55e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-subitem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 12px 6px 28px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-subdot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--border, #2a2a2a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-subdot.completed {
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-subdot.running {
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
animation: pulse-status-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-subdot.failed {
|
||||||
|
background: var(--error, #ef4444);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-subdot.pending {
|
||||||
|
background: var(--text-tertiary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-subitem-name {
|
||||||
|
flex: 1;
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-duration {
|
||||||
|
color: var(--text-tertiary, #666);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminal Section Rich */
|
||||||
|
.terminal-section-rich {
|
||||||
|
background: #0d0d0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header-rich {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-dot-rich {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--text-tertiary, #666);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-dot-rich.active {
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
animation: pulse-status-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-stats-rich {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-tertiary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-stats-rich strong {
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-output-rich {
|
||||||
|
background: #000;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-height: 150px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--border-light, #222);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line {
|
||||||
|
color: var(--text-tertiary, #666);
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line.current {
|
||||||
|
color: var(--primary, #c5f82a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line.error {
|
||||||
|
color: var(--error, #ef4444);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line.success {
|
||||||
|
color: var(--success, #22c55e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-footer-rich {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border-light, #222);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-tertiary, #666);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-footer-rich strong {
|
||||||
|
color: var(--primary, #c5f82a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Intent Section */
|
||||||
|
.intent-text-rich {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons */
|
||||||
|
.detail-actions-rich {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid var(--border, #2a2a2a);
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-rich {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
border: 1px solid var(--border, #2a2a2a);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-rich:hover {
|
||||||
|
background: var(--surface-hover, #1e1e1e);
|
||||||
|
color: var(--text, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-rich.btn-pause {
|
||||||
|
border-color: var(--warning, #f59e0b);
|
||||||
|
color: var(--warning, #f59e0b);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-rich.btn-pause:hover {
|
||||||
|
background: rgba(245, 158, 11, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-rich.btn-cancel {
|
||||||
|
border-color: var(--error, #ef4444);
|
||||||
|
color: var(--error, #ef4444);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-rich.btn-cancel:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-rich.btn-detailed {
|
||||||
|
margin-left: auto;
|
||||||
|
border-color: var(--primary, #c5f82a);
|
||||||
|
color: var(--primary, #c5f82a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-rich.btn-detailed:hover {
|
||||||
|
background: rgba(197, 248, 42, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar for rich task detail */
|
||||||
|
.task-detail-rich::-webkit-scrollbar,
|
||||||
|
.progress-log-content::-webkit-scrollbar,
|
||||||
|
.terminal-output-rich::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-rich::-webkit-scrollbar-track,
|
||||||
|
.progress-log-content::-webkit-scrollbar-track,
|
||||||
|
.terminal-output-rich::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg, #0a0a0a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-rich::-webkit-scrollbar-thumb,
|
||||||
|
.progress-log-content::-webkit-scrollbar-thumb,
|
||||||
|
.terminal-output-rich::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border, #2a2a2a);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-rich::-webkit-scrollbar-thumb:hover,
|
||||||
|
.progress-log-content::-webkit-scrollbar-thumb:hover,
|
||||||
|
.terminal-output-rich::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--primary, #c5f82a);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,15 +103,17 @@
|
||||||
hx-post="/api/autotask/create"
|
hx-post="/api/autotask/create"
|
||||||
hx-ext="json-enc"
|
hx-ext="json-enc"
|
||||||
hx-include="#quick-intent-input"
|
hx-include="#quick-intent-input"
|
||||||
hx-target="#intent-result"
|
hx-target="#intent-result-hidden"
|
||||||
hx-swap="innerHTML"
|
hx-swap="none"
|
||||||
hx-indicator="#intent-spinner"
|
hx-indicator="#intent-spinner"
|
||||||
|
hx-timeout="300000"
|
||||||
>
|
>
|
||||||
<span class="btn-text">Create & Run</span>
|
<span class="btn-text">Create & Run</span>
|
||||||
<span class="spinner" id="intent-spinner"></span>
|
<span class="spinner" id="intent-spinner"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="intent-result" class="intent-result"></div>
|
<div id="intent-result" class="intent-result"></div>
|
||||||
|
<div id="intent-result-hidden" style="display: none"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Two-Column Layout -->
|
<!-- Main Two-Column Layout -->
|
||||||
|
|
@ -203,6 +205,9 @@
|
||||||
<div class="floating-progress-log" id="floating-progress-log">
|
<div class="floating-progress-log" id="floating-progress-log">
|
||||||
<!-- Live log entries appear here -->
|
<!-- Live log entries appear here -->
|
||||||
</div>
|
</div>
|
||||||
|
<div class="floating-llm-terminal" id="floating-llm-terminal">
|
||||||
|
<!-- LLM streaming output appears here -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -300,11 +305,120 @@
|
||||||
}
|
}
|
||||||
// Load stats
|
// Load stats
|
||||||
loadTaskStats();
|
loadTaskStats();
|
||||||
|
|
||||||
|
// Handle create task response - auto-select new task and show progress
|
||||||
|
document.body.addEventListener("htmx:afterRequest", function (evt) {
|
||||||
|
// Check if this is the create task response
|
||||||
|
if (
|
||||||
|
evt.detail.pathInfo &&
|
||||||
|
evt.detail.pathInfo.requestPath === "/api/autotask/create"
|
||||||
|
) {
|
||||||
|
const xhr = evt.detail.xhr;
|
||||||
|
const intentResult = document.getElementById("intent-result");
|
||||||
|
|
||||||
|
if (xhr && xhr.status === 202) {
|
||||||
|
try {
|
||||||
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
if (response.success && response.task_id) {
|
||||||
|
console.log(
|
||||||
|
"[TASK] Created task:",
|
||||||
|
response.task_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show success feedback
|
||||||
|
intentResult.innerHTML = `<span class="intent-success">✓ Task created - running...</span>`;
|
||||||
|
intentResult.style.display = "block";
|
||||||
|
|
||||||
|
// Clear the input
|
||||||
|
document.getElementById(
|
||||||
|
"quick-intent-input",
|
||||||
|
).value = "";
|
||||||
|
|
||||||
|
// Trigger task list refresh
|
||||||
|
htmx.trigger(document.body, "taskCreated");
|
||||||
|
|
||||||
|
// After a short delay to let the list reload, select the new task
|
||||||
|
setTimeout(function () {
|
||||||
|
selectTask(response.task_id);
|
||||||
|
// Start polling for updates
|
||||||
|
startTaskPolling(response.task_id);
|
||||||
|
// Hide success message after task is selected
|
||||||
|
setTimeout(function () {
|
||||||
|
intentResult.style.display = "none";
|
||||||
|
}, 2000);
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
intentResult.innerHTML = `<span class="intent-error">✗ ${response.message || "Failed to create task"}</span>`;
|
||||||
|
intentResult.style.display = "block";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Failed to parse create response:", e);
|
||||||
|
intentResult.innerHTML = `<span class="intent-error">✗ Failed to parse response</span>`;
|
||||||
|
intentResult.style.display = "block";
|
||||||
|
}
|
||||||
|
} else if (xhr && xhr.status >= 400) {
|
||||||
|
try {
|
||||||
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
intentResult.innerHTML = `<span class="intent-error">✗ ${response.error || response.message || "Error creating task"}</span>`;
|
||||||
|
} catch (e) {
|
||||||
|
intentResult.innerHTML = `<span class="intent-error">✗ Error: ${xhr.status}</span>`;
|
||||||
|
}
|
||||||
|
intentResult.style.display = "block";
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Poll for task status updates
|
||||||
|
let taskPollingInterval = null;
|
||||||
|
function startTaskPolling(taskId) {
|
||||||
|
// Clear any existing polling
|
||||||
|
if (taskPollingInterval) {
|
||||||
|
clearInterval(taskPollingInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
taskPollingInterval = setInterval(function () {
|
||||||
|
fetch(`/api/autotask/${taskId}`)
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((task) => {
|
||||||
|
console.log("[TASK] Poll status:", task.status);
|
||||||
|
|
||||||
|
// Refresh the detail panel
|
||||||
|
htmx.ajax("GET", `/api/tasks/${taskId}`, {
|
||||||
|
target: "#task-detail-content",
|
||||||
|
swap: "innerHTML",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Refresh task list to update status badges
|
||||||
|
htmx.trigger(document.body, "taskCreated");
|
||||||
|
|
||||||
|
// Stop polling if task is complete or failed
|
||||||
|
if (
|
||||||
|
task.status === "completed" ||
|
||||||
|
task.status === "failed" ||
|
||||||
|
task.status === "cancelled"
|
||||||
|
) {
|
||||||
|
clearInterval(taskPollingInterval);
|
||||||
|
taskPollingInterval = null;
|
||||||
|
loadTaskStats();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn("Failed to poll task:", e);
|
||||||
|
});
|
||||||
|
}, 2000); // Poll every 2 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopTaskPolling() {
|
||||||
|
if (taskPollingInterval) {
|
||||||
|
clearInterval(taskPollingInterval);
|
||||||
|
taskPollingInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load task statistics
|
// Load task statistics
|
||||||
function loadTaskStats() {
|
function loadTaskStats() {
|
||||||
fetch("/api/tasks/stats")
|
fetch("/api/tasks/stats/json")
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((stats) => {
|
.then((stats) => {
|
||||||
if (stats.complete !== undefined)
|
if (stats.complete !== undefined)
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,35 @@ if (typeof TasksState === "undefined") {
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
// Only init if tasks app is visible
|
||||||
|
if (document.querySelector(".tasks-app")) {
|
||||||
initTasksApp();
|
initTasksApp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reinitialize when tasks page is loaded via HTMX
|
||||||
|
document.body.addEventListener("htmx:afterSwap", function (evt) {
|
||||||
|
// Check if tasks app was just loaded
|
||||||
|
if (evt.detail.target && evt.detail.target.id === "main-content") {
|
||||||
|
if (document.querySelector(".tasks-app")) {
|
||||||
|
console.log(
|
||||||
|
"[Tasks] Detected tasks app loaded via HTMX, initializing...",
|
||||||
|
);
|
||||||
|
initTasksApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function initTasksApp() {
|
function initTasksApp() {
|
||||||
|
// Only init WebSocket if not already connected
|
||||||
|
if (
|
||||||
|
!TasksState.wsConnection ||
|
||||||
|
TasksState.wsConnection.readyState !== WebSocket.OPEN
|
||||||
|
) {
|
||||||
initWebSocket();
|
initWebSocket();
|
||||||
|
} else {
|
||||||
|
console.log("[Tasks] WebSocket already connected, skipping init");
|
||||||
|
}
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
setupKeyboardShortcuts();
|
setupKeyboardShortcuts();
|
||||||
setupIntentInputHandlers();
|
setupIntentInputHandlers();
|
||||||
|
|
@ -68,6 +92,32 @@ function setupIntentInputHandlers() {
|
||||||
const resultDiv = document.getElementById("intent-result");
|
const resultDiv = document.getElementById("intent-result");
|
||||||
try {
|
try {
|
||||||
const response = JSON.parse(e.detail.xhr.responseText);
|
const response = JSON.parse(e.detail.xhr.responseText);
|
||||||
|
|
||||||
|
// Handle async task creation (status 202 Accepted)
|
||||||
|
if (response.status === "running" && response.task_id) {
|
||||||
|
// Clear input immediately
|
||||||
|
document.getElementById("quick-intent-input").value = "";
|
||||||
|
|
||||||
|
// Show floating progress panel
|
||||||
|
const intentText =
|
||||||
|
document
|
||||||
|
.getElementById("quick-intent-input")
|
||||||
|
.getAttribute("data-last-intent") || "Processing...";
|
||||||
|
showFloatingProgress(intentText);
|
||||||
|
|
||||||
|
// Clear result div - progress is shown in floating panel
|
||||||
|
resultDiv.innerHTML = "";
|
||||||
|
|
||||||
|
// Trigger task list refresh to show new task
|
||||||
|
htmx.trigger(document.body, "taskCreated");
|
||||||
|
|
||||||
|
// Start polling for task status
|
||||||
|
startTaskPolling(response.task_id);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle completed task (legacy sync response)
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
let html = `<div class="result-card">
|
let html = `<div class="result-card">
|
||||||
<div class="result-message result-success">✓ ${response.message || "Done!"}</div>`;
|
<div class="result-message result-success">✓ ${response.message || "Done!"}</div>`;
|
||||||
|
|
@ -99,6 +149,80 @@ function setupIntentInputHandlers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Save intent text before submit for progress display
|
||||||
|
if (input) {
|
||||||
|
input.addEventListener("input", function () {
|
||||||
|
input.setAttribute("data-last-intent", input.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task polling for async task creation
|
||||||
|
let activePollingTaskId = null;
|
||||||
|
let pollingInterval = null;
|
||||||
|
|
||||||
|
function startTaskPolling(taskId) {
|
||||||
|
// Stop any existing polling
|
||||||
|
stopTaskPolling();
|
||||||
|
|
||||||
|
activePollingTaskId = taskId;
|
||||||
|
let pollCount = 0;
|
||||||
|
const maxPolls = 180; // 3 minutes at 1 second intervals
|
||||||
|
|
||||||
|
console.log(`[POLL] Starting polling for task ${taskId}`);
|
||||||
|
|
||||||
|
pollingInterval = setInterval(async () => {
|
||||||
|
pollCount++;
|
||||||
|
|
||||||
|
if (pollCount > maxPolls) {
|
||||||
|
console.log(`[POLL] Max polls reached for task ${taskId}`);
|
||||||
|
stopTaskPolling();
|
||||||
|
errorFloatingProgress("Task timed out");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/autotask/tasks/${taskId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`[POLL] Failed to fetch task status: ${response.status}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = await response.json();
|
||||||
|
console.log(
|
||||||
|
`[POLL] Task ${taskId} status: ${task.status}, progress: ${task.progress || 0}%`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update progress
|
||||||
|
const progress = task.progress || 0;
|
||||||
|
const message = task.current_step || task.status || "Processing...";
|
||||||
|
updateFloatingProgressBar(message, progress, task);
|
||||||
|
|
||||||
|
// Check if task is complete
|
||||||
|
if (task.status === "completed" || task.status === "complete") {
|
||||||
|
stopTaskPolling();
|
||||||
|
completeFloatingProgress(task);
|
||||||
|
htmx.trigger(document.body, "taskCreated"); // Refresh task list
|
||||||
|
showToast("Task completed successfully!", "success");
|
||||||
|
} else if (task.status === "failed" || task.status === "error") {
|
||||||
|
stopTaskPolling();
|
||||||
|
errorFloatingProgress(task.error || "Task failed");
|
||||||
|
htmx.trigger(document.body, "taskCreated"); // Refresh task list
|
||||||
|
showToast(task.error || "Task failed", "error");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[POLL] Error polling task ${taskId}:`, err);
|
||||||
|
}
|
||||||
|
}, 1000); // Poll every 1 second
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopTaskPolling() {
|
||||||
|
if (pollingInterval) {
|
||||||
|
clearInterval(pollingInterval);
|
||||||
|
pollingInterval = null;
|
||||||
|
}
|
||||||
|
activePollingTaskId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -106,6 +230,17 @@ function setupIntentInputHandlers() {
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
function initWebSocket() {
|
function initWebSocket() {
|
||||||
|
// Don't create new connection if one already exists and is open/connecting
|
||||||
|
if (TasksState.wsConnection) {
|
||||||
|
const state = TasksState.wsConnection.readyState;
|
||||||
|
if (state === WebSocket.OPEN || state === WebSocket.CONNECTING) {
|
||||||
|
console.log(
|
||||||
|
"[Tasks WS] WebSocket already connected/connecting, skipping",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
const wsUrl = `${protocol}//${window.location.host}/ws/task-progress`;
|
const wsUrl = `${protocol}//${window.location.host}/ws/task-progress`;
|
||||||
|
|
||||||
|
|
@ -236,6 +371,14 @@ function handleWebSocketMessage(data) {
|
||||||
case "decision_required":
|
case "decision_required":
|
||||||
showDecisionRequired(data.decision);
|
showDecisionRequired(data.decision);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "llm_stream":
|
||||||
|
// Stream LLM output to terminal in real-time
|
||||||
|
if (data.text) {
|
||||||
|
addAgentLog("accent", data.text);
|
||||||
|
addLLMStreamOutput(data.text);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -518,6 +661,23 @@ function closeFloatingProgress() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addLLMStreamOutput(text) {
|
||||||
|
// Add LLM streaming output to the floating terminal
|
||||||
|
const terminal = document.getElementById("floating-llm-terminal");
|
||||||
|
if (!terminal) return;
|
||||||
|
|
||||||
|
const line = document.createElement("div");
|
||||||
|
line.className = "llm-output";
|
||||||
|
line.textContent = text;
|
||||||
|
terminal.appendChild(line);
|
||||||
|
terminal.scrollTop = terminal.scrollHeight;
|
||||||
|
|
||||||
|
// Keep only last 100 lines to prevent memory issues
|
||||||
|
while (terminal.children.length > 100) {
|
||||||
|
terminal.removeChild(terminal.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateProgressUI(data) {
|
function updateProgressUI(data) {
|
||||||
const progressBar = document.querySelector(".result-progress-bar");
|
const progressBar = document.querySelector(".result-progress-bar");
|
||||||
const resultDiv = document.getElementById("intent-result");
|
const resultDiv = document.getElementById("intent-result");
|
||||||
|
|
@ -707,10 +867,20 @@ function searchTasks(query) {
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
function loadTaskDetails(taskId) {
|
function loadTaskDetails(taskId) {
|
||||||
// In real app, fetch from API
|
|
||||||
// htmx.ajax('GET', `/api/tasks/${taskId}`, {target: '#task-detail-panel', swap: 'innerHTML'});
|
|
||||||
|
|
||||||
addAgentLog("info", `[LOAD] Loading task #${taskId} details`);
|
addAgentLog("info", `[LOAD] Loading task #${taskId} details`);
|
||||||
|
|
||||||
|
// Show detail panel and hide empty state
|
||||||
|
const emptyState = document.getElementById("detail-empty");
|
||||||
|
const detailContent = document.getElementById("task-detail-content");
|
||||||
|
|
||||||
|
if (emptyState) emptyState.style.display = "none";
|
||||||
|
if (detailContent) detailContent.style.display = "block";
|
||||||
|
|
||||||
|
// Fetch task details from API
|
||||||
|
htmx.ajax("GET", `/api/tasks/${taskId}`, {
|
||||||
|
target: "#task-detail-content",
|
||||||
|
swap: "innerHTML",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTaskCard(task) {
|
function updateTaskCard(task) {
|
||||||
|
|
@ -937,6 +1107,83 @@ function toggleAgentLogPause() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// TASK ACTIONS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
function pauseTask(taskId) {
|
||||||
|
addAgentLog("info", `[TASK] Pausing task #${taskId}...`);
|
||||||
|
|
||||||
|
fetch(`/api/autotask/${taskId}/pause`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
showToast("Task paused", "success");
|
||||||
|
addAgentLog("success", `[OK] Task #${taskId} paused`);
|
||||||
|
htmx.trigger(document.body, "taskCreated");
|
||||||
|
if (TasksState.selectedTaskId === taskId) {
|
||||||
|
loadTaskDetails(taskId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast("Failed to pause task", "error");
|
||||||
|
addAgentLog(
|
||||||
|
"error",
|
||||||
|
`[ERROR] Failed to pause task: ${result.error || result.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showToast("Failed to pause task", "error");
|
||||||
|
addAgentLog("error", `[ERROR] Failed to pause task: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelTask(taskId) {
|
||||||
|
if (!confirm("Are you sure you want to cancel this task?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAgentLog("info", `[TASK] Cancelling task #${taskId}...`);
|
||||||
|
|
||||||
|
fetch(`/api/autotask/${taskId}/cancel`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
showToast("Task cancelled", "success");
|
||||||
|
addAgentLog("success", `[OK] Task #${taskId} cancelled`);
|
||||||
|
htmx.trigger(document.body, "taskCreated");
|
||||||
|
if (TasksState.selectedTaskId === taskId) {
|
||||||
|
loadTaskDetails(taskId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast("Failed to cancel task", "error");
|
||||||
|
addAgentLog(
|
||||||
|
"error",
|
||||||
|
`[ERROR] Failed to cancel task: ${result.error || result.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showToast("Failed to cancel task", "error");
|
||||||
|
addAgentLog("error", `[ERROR] Failed to cancel task: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDetailedView(taskId) {
|
||||||
|
addAgentLog("info", `[TASK] Opening detailed view for task #${taskId}...`);
|
||||||
|
|
||||||
|
// For now, just reload the task details
|
||||||
|
// In the future, this could open a modal or new page with more details
|
||||||
|
loadTaskDetails(taskId);
|
||||||
|
showToast("Detailed view loaded", "info");
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// TASK LIFECYCLE
|
// TASK LIFECYCLE
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue