@for (item of awaitingGuidance(); track item.id) {
用户指导 · {{ guidanceStatusLabel(item) }}
+
接收 {{ (item.accepted_at || item.timestamp) * 1000 | date:'HH:mm:ss' }}@if (item.consumed_at) { · 执行器读取 {{ item.consumed_at * 1000 | date:'HH:mm:ss' }} }
{{ item.instruction }}
}
diff --git a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss
index 4c23d24..1538009 100644
--- a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss
+++ b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss
@@ -15,10 +15,10 @@
*/
$border-color: rgba(0, 0, 0, 0.08);
-$text-primary: #1f1f1f;
-$text-secondary: #1f1f1f;
-$text-muted: #1f1f1f;
-$bg-main: #f9fafb;
+$text-primary: var(--app-text-strong, #1f1f1f);
+$text-secondary: var(--app-text-strong, #1f1f1f);
+$text-muted: var(--app-text-strong, #1f1f1f);
+$bg-main: var(--app-surface-soft, #f9fafb);
$uniform-gap: 8px;
$stream-row-height: 25px;
@@ -34,7 +34,7 @@ $stream-row-height: 25px;
display: flex;
flex-direction: column;
height: 100%;
- background: #f8f9fa;
+ background: var(--app-surface-soft, #f8f9fa);
box-sizing: border-box;
position: relative;
container-type: inline-size;
@@ -44,8 +44,8 @@ $stream-row-height: 25px;
position: relative;
height: 60px;
flex-shrink: 0;
- background: #ffffff;
- border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+ background: var(--app-surface, #ffffff);
+ border-bottom: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.08);
display: flex;
align-items: center;
justify-content: flex-end;
@@ -63,16 +63,16 @@ $stream-row-height: 25px;
display: flex;
height: 36px;
box-sizing: border-box;
- background: rgba(241, 245, 249, 0.72);
+ background: rgb(var(--app-surface-soft-rgb, 241 245 249) / 0.72);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
- border: 1px solid rgba(226, 232, 240, 0.85);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.85);
border-radius: 18px;
padding: 3px;
gap: 3px;
align-items: center;
flex-shrink: 0;
- box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgba(255, 255, 255, 0.8);
+ box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.8);
}
.vscode-tab-btn {
@@ -88,7 +88,7 @@ $stream-row-height: 25px;
border-radius: 15px;
font-size: 13px;
font-weight: 600;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
font-family: 'Google Sans', 'Google Sans Text', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
text-decoration: none;
cursor: pointer;
@@ -116,28 +116,28 @@ $stream-row-height: 25px;
.dropdown-chevron {
font-size: 16px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
margin-left: 2px;
transition: transform 0.2s ease, color 0.15s ease;
&.open {
transform: rotate(180deg);
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
}
}
&:hover:not(.active) {
- color: #0f172a;
- background: rgba(255, 255, 255, 0.6);
+ color: var(--app-text-strong, #0f172a);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.6);
}
&.active {
- background: #ffffff;
- color: #2563eb;
+ background: var(--app-surface, #ffffff);
+ color: var(--app-primary-text, #2563eb);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.02);
.tab-icon {
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
}
}
@@ -154,7 +154,7 @@ $stream-row-height: 25px;
&.has-video {
.tab-icon {
- color: #0284c7;
+ color: var(--app-info-text, #0284c7);
}
}
}
@@ -233,10 +233,10 @@ $stream-row-height: 25px;
padding: 0 11px;
margin: 0;
box-sizing: border-box;
- background: rgba(255, 255, 255, 0.72);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.72);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
- border: 1px solid rgba(226, 232, 240, 0.85);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.85);
border-radius: 15px;
cursor: pointer;
user-select: none;
@@ -244,25 +244,25 @@ $stream-row-height: 25px;
-webkit-appearance: none;
&:hover {
- background: rgba(255, 255, 255, 0.92);
- border-color: rgba(203, 213, 225, 0.95);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.92);
+ border-color: rgb(var(--app-border-control-rgb, 203 213 225) / 0.95);
}
&.active {
- background: #eff6ff;
- border-color: #bfdbfe;
+ background: var(--app-primary-soft, #eff6ff);
+ border-color: var(--app-primary-border, #bfdbfe);
.model-icon,
.model-name {
- color: #1d4ed8;
+ color: var(--app-primary-text, #1d4ed8);
}
}
&:focus-visible {
- outline: 2px solid #93c5fd;
+ outline: 2px solid var(--app-primary-border, #93c5fd);
outline-offset: 1px;
}
- box-shadow: 0 2px 6px -1px rgba(15, 23, 42, 0.05), inset 0 1px 1px rgba(255, 255, 255, 0.9);
+ box-shadow: 0 2px 6px -1px rgba(15, 23, 42, 0.05), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
font-family: 'Google Sans', 'Google Sans Text', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
@@ -273,20 +273,20 @@ $stream-row-height: 25px;
display: inline-flex;
align-items: center;
justify-content: center;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
}
.model-label {
font-size: 12px;
font-weight: 500;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
line-height: 1;
}
.model-name {
font-size: 12px;
font-weight: 700;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
letter-spacing: 0.1px;
line-height: 1;
}
@@ -300,12 +300,12 @@ $stream-row-height: 25px;
height: 30px;
padding: 0 12px;
box-sizing: border-box;
- background: rgba(255, 255, 255, 0.72);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.72);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
- border: 1px solid rgba(226, 232, 240, 0.85);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.85);
border-radius: 15px;
- box-shadow: 0 2px 6px -1px rgba(15, 23, 42, 0.05), inset 0 1px 1px rgba(255, 255, 255, 0.9);
+ box-shadow: 0 2px 6px -1px rgba(15, 23, 42, 0.05), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
cursor: pointer;
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
user-select: none;
@@ -319,14 +319,14 @@ $stream-row-height: 25px;
display: inline-flex;
align-items: center;
justify-content: center;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
transition: transform 0.2s ease, color 0.2s ease;
}
.btn-text {
font-size: 12px;
font-weight: 600;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
letter-spacing: 0.1px;
line-height: 1;
transition: color 0.15s ease;
@@ -342,39 +342,39 @@ $stream-row-height: 25px;
}
&.recording {
- background: rgba(254, 242, 242, 0.9);
- border-color: rgba(248, 113, 113, 0.6);
- box-shadow: 0 2px 6px -1px rgba(220, 38, 38, 0.15), inset 0 1px 1px rgba(255, 255, 255, 0.9);
+ background: rgb(var(--app-error-soft-rgb, 254 242 242) / 0.9);
+ border-color: rgb(var(--app-error-border-rgb, 248 113 113) / 0.6);
+ box-shadow: 0 2px 6px -1px rgba(220, 38, 38, 0.15), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
.video-icon {
- color: #dc2626;
+ color: var(--app-error-text, #dc2626);
}
.btn-text {
- color: #b91c1c;
+ color: var(--app-error-text, #b91c1c);
}
}
&.processing {
.video-icon {
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
animation: spin 1s linear infinite;
}
}
&:hover:not(.active) {
- background: rgba(255, 255, 255, 0.88);
- border-color: rgba(203, 213, 225, 0.95);
- box-shadow: 0 4px 12px -2px rgba(15, 23, 42, 0.08), inset 0 1px 1px rgba(255, 255, 255, 1);
- color: #0f172a;
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.88);
+ border-color: rgb(var(--app-border-control-rgb, 203 213 225) / 0.95);
+ box-shadow: 0 4px 12px -2px rgba(15, 23, 42, 0.08), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 1);
+ color: var(--app-text-strong, #0f172a);
.video-icon {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
transform: scale(1.08);
}
.btn-text {
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
}
}
@@ -384,38 +384,38 @@ $stream-row-height: 25px;
}
&.has-steps {
- background: rgba(240, 253, 250, 0.9);
+ background: rgb(var(--app-success-soft-rgb, 240 253 250) / 0.9);
border-color: rgba(94, 234, 212, 0.7);
- box-shadow: 0 2px 6px -1px rgba(13, 148, 136, 0.12), inset 0 1px 1px rgba(255, 255, 255, 0.9);
+ box-shadow: 0 2px 6px -1px rgba(13, 148, 136, 0.12), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
.steps-icon {
- color: #0d9488;
+ color: var(--app-info-text, #0d9488);
}
.btn-text {
- color: #0f766e;
+ color: var(--app-info-text, #0f766e);
}
&:hover:not(.active) {
- background: rgba(204, 251, 241, 0.95);
+ background: rgb(var(--app-success-soft-rgb, 204 251 241) / 0.95);
border-color: rgba(45, 212, 191, 0.85);
.steps-icon {
- color: #0f766e;
+ color: var(--app-info-text, #0f766e);
}
}
}
&.failed {
- background: rgba(254, 242, 242, 0.8);
- border-color: rgba(254, 202, 202, 0.9);
+ background: rgb(var(--app-error-soft-rgb, 254 242 242) / 0.8);
+ border-color: rgb(var(--app-error-border-rgb, 254 202 202) / 0.9);
.error-icon {
- color: #ef4444;
+ color: var(--app-error-text, #ef4444);
}
.btn-text {
- color: #dc2626;
+ color: var(--app-error-text, #dc2626);
}
}
@@ -443,13 +443,13 @@ $stream-row-height: 25px;
}
.video-pulse-dot {
- background: #ffffff;
- box-shadow: 0 0 6px rgba(255, 255, 255, 0.9);
+ background: var(--app-surface, #ffffff);
+ box-shadow: 0 0 6px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
}
.steps-count-badge {
- background: #ffffff;
- color: #1a73e8;
+ background: var(--app-surface, #ffffff);
+ color: var(--app-primary-text, #1a73e8);
}
}
}
@@ -467,7 +467,7 @@ $stream-row-height: 25px;
align-items: center;
justify-content: center;
gap: 9px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
font-size: 13px;
.material-symbols-outlined {
@@ -487,7 +487,7 @@ $stream-row-height: 25px;
align-items: center;
gap: 4px;
padding: 3px 9px;
- background: rgba(255, 255, 255, 0.92);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.92);
backdrop-filter: blur(12px);
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 12px;
@@ -498,27 +498,27 @@ $stream-row-height: 25px;
user-select: none;
&:hover {
- background: rgba(255, 255, 255, 1);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 1);
border-color: rgba(99, 102, 241, 0.4);
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.15);
transform: translateY(-1px);
.model-icon {
- color: #4f46e5;
+ color: var(--app-exploration-text, #4f46e5);
transform: rotate(15deg) scale(1.05);
}
}
.model-icon {
font-size: 13px;
- color: #6366f1;
+ color: var(--app-exploration-text, #6366f1);
transition: all 0.3s ease;
}
.model-name {
font-size: 11px;
font-weight: 600;
- color: #334155;
+ color: var(--app-text-control, #334155);
letter-spacing: normal;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
@@ -557,7 +557,7 @@ $stream-row-height: 25px;
.material-symbols-outlined {
font-size: 40px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
}
@@ -592,7 +592,7 @@ $stream-row-height: 25px;
display: inline-flex;
align-items: center;
gap: 8px;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
font-size: 13px;
font-weight: 500;
user-select: none;
@@ -605,12 +605,12 @@ $stream-row-height: 25px;
align-items: center;
justify-content: center;
animation: spin-loader 1s linear infinite;
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
flex-shrink: 0;
}
.loader-text {
- color: #475569;
+ color: var(--app-text-secondary, #475569);
font-size: 13px;
font-weight: 500;
line-height: 1.4;
@@ -630,7 +630,7 @@ $stream-row-height: 25px;
line-height: 1.5;
min-height: 20px;
box-sizing: border-box;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
margin-top: $uniform-gap * 2;
margin-bottom: 8px;
user-select: none;
@@ -643,7 +643,7 @@ $stream-row-height: 25px;
}
.phase-worked-time {
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
font-weight: normal;
}
@@ -651,16 +651,16 @@ $stream-row-height: 25px;
display: inline-flex;
align-items: center;
gap: 4px;
- color: #8e8e93;
+ color: var(--app-text-muted, #8e8e93);
cursor: default;
.token-dot {
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
font-weight: bold;
}
.token-text {
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
font-variant-numeric: tabular-nums;
}
}
@@ -671,7 +671,7 @@ $stream-row-height: 25px;
flex-direction: column;
gap: $uniform-gap;
padding-left: 12px;
- border-left: 1px solid rgba(0, 0, 0, 0.04);
+ border-left: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.04);
margin-left: 6px;
position: relative;
z-index: 1;
@@ -715,14 +715,14 @@ $stream-row-height: 25px;
}
.details-summary {
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
&::after {
color: #d4d4d8;
}
&:hover {
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
}
}
@@ -757,7 +757,7 @@ $stream-row-height: 25px;
-webkit-background-clip: border-box !important;
background-clip: border-box !important;
-webkit-text-fill-color: #71717a !important;
- color: #71717a !important;
+ color: var(--app-text-muted, #71717a) !important;
animation: none !important;
font-weight: normal !important;
@@ -770,7 +770,7 @@ $stream-row-height: 25px;
}
.stream-status-dot {
- background: #d4d4d8;
+ background: var(--app-surface-raised, #d4d4d8);
&.streaming {
background: #3b82f6;
@@ -786,32 +786,32 @@ $stream-row-height: 25px;
}
.stream-text {
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
}
}
&.raw-thinking-details {
- border-color: #dbeafe;
- background: #f8fafc;
+ border-color: var(--app-primary-border, #dbeafe);
+ background: var(--app-surface-soft, #f8fafc);
&:not(.collapsed) {
- background: #f8fafc;
+ background: var(--app-surface-soft, #f8fafc);
}
.details-summary {
- color: #1e40af;
+ color: var(--app-primary-text, #1e40af);
&::after {
- color: #93c5fd;
+ color: var(--app-primary-text, #93c5fd);
}
&:hover {
- color: #1e3a8a;
+ color: var(--app-primary-text, #1e3a8a);
}
}
.stream-status-dot {
- background: #93c5fd;
+ background: var(--app-primary-soft, #93c5fd);
&.streaming {
background: #2563eb;
@@ -870,7 +870,7 @@ $stream-row-height: 25px;
&::after {
transform: translateX(3px);
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
}
}
}
@@ -909,26 +909,26 @@ $stream-row-height: 25px;
font-family: inherit;
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
white-space: normal;
word-break: break-word;
strong {
font-weight: 600;
- color: #111827;
+ color: var(--app-text-strong, #111827);
}
div, p {
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin: 4px 0;
}
h1, h2, h3, h4, h5, h6 {
font-size: 12.5px;
font-weight: 600;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin-top: 8px;
margin-bottom: 4px;
}
@@ -936,8 +936,8 @@ $stream-row-height: 25px;
.inline-code {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 11px;
- background: #f4f4f5;
- color: #b91c1c;
+ background: var(--app-surface-raised, #f4f4f5);
+ color: var(--app-error-text, #b91c1c);
padding: 2px 6px;
border-radius: 4px;
}
@@ -948,7 +948,7 @@ $stream-row-height: 25px;
li {
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin: 4px 0;
padding-left: 2px;
}
@@ -987,7 +987,7 @@ $stream-row-height: 25px;
min-height: 20px;
box-sizing: border-box;
letter-spacing: 0.5px;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
margin-bottom: $uniform-gap;
user-select: none;
display: flex;
@@ -999,7 +999,7 @@ $stream-row-height: 25px;
height: 8px;
border-radius: 50%;
display: inline-block;
- background: #d4d4d8;
+ background: var(--app-surface-raised, #d4d4d8);
&.streaming {
background: #71717a;
@@ -1011,7 +1011,7 @@ $stream-row-height: 25px;
.report-content {
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
}
.stream-box {
@@ -1040,26 +1040,26 @@ $stream-row-height: 25px;
font-family: inherit;
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
white-space: normal;
word-break: break-word;
strong {
font-weight: 600;
- color: #111827;
+ color: var(--app-text-strong, #111827);
}
div, p {
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin: 4px 0;
}
h1 {
font-size: 14px;
font-weight: 700;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
margin-top: 12px;
margin-bottom: 6px;
}
@@ -1067,7 +1067,7 @@ $stream-row-height: 25px;
h2 {
font-size: 13.5px;
font-weight: 700;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
margin-top: 10px;
margin-bottom: 5px;
}
@@ -1075,7 +1075,7 @@ $stream-row-height: 25px;
h3 {
font-size: 13px;
font-weight: 600;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
margin-top: 8px;
margin-bottom: 4px;
}
@@ -1083,7 +1083,7 @@ $stream-row-height: 25px;
h4, h5, h6 {
font-size: 12.5px;
font-weight: 600;
- color: #334155;
+ color: var(--app-text-control, #334155);
margin-top: 6px;
margin-bottom: 3px;
}
@@ -1096,36 +1096,36 @@ $stream-row-height: 25px;
blockquote {
margin: 6px 0;
padding: 4px 10px;
- border-left: 3px solid #cbd5e1;
- color: #475569;
- background: rgba(241, 245, 249, 0.5);
+ border-left: 3px solid var(--app-border-control, #cbd5e1);
+ color: var(--app-text-secondary, #475569);
+ background: rgb(var(--app-surface-soft-rgb, 241 245 249) / 0.5);
border-radius: 0 4px 4px 0;
}
.code-block {
margin: 6px 0;
padding: 8px 12px;
- background: #f8fafc;
- border: 1px solid #e2e8f0;
+ background: var(--app-surface-soft, #f8fafc);
+ border: 1px solid var(--app-border, #e2e8f0);
border-radius: 6px;
overflow-x: auto;
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 11.5px;
line-height: 1.5;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
}
.md-hr {
border: none;
- border-top: 1px solid #e2e8f0;
+ border-top: 1px solid var(--app-border, #e2e8f0);
margin: 10px 0;
}
.inline-code {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 11px;
- background: #f4f4f5;
- color: #b91c1c;
+ background: var(--app-surface-raised, #f4f4f5);
+ color: var(--app-error-text, #b91c1c);
padding: 2px 6px;
border-radius: 4px;
}
@@ -1136,16 +1136,16 @@ $stream-row-height: 25px;
li {
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin: 4px 0;
padding-left: 2px;
&.md-verify-item {
- color: #334155;
+ color: var(--app-text-control, #334155);
}
&.md-finding-item {
- color: #92400e;
+ color: var(--app-warning-text, #92400e);
}
}
}
@@ -1157,8 +1157,8 @@ $stream-row-height: 25px;
}
.checker-stream-flat-card {
- background: #f5f3ff;
- border: 1px solid #e9d5ff;
+ background: var(--app-exploration-soft, #f5f3ff);
+ border: 1px solid var(--app-exploration-border, #e9d5ff);
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 8px;
@@ -1170,7 +1170,7 @@ $stream-row-height: 25px;
font-size: 12px;
line-height: 1.5;
letter-spacing: 0.5px;
- color: #6b21a8;
+ color: var(--app-exploration-text, #6b21a8);
margin-bottom: 8px;
user-select: none;
display: flex;
@@ -1194,15 +1194,15 @@ $stream-row-height: 25px;
.report-content {
font-size: 12px;
line-height: 1.6;
- color: #27272a;
+ color: var(--app-text-strong, #27272a);
.stream-text {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 11.5px;
white-space: pre-wrap;
word-break: break-all;
- color: #5b21b6;
- background: rgba(255, 255, 255, 0.5);
+ color: var(--app-exploration-text, #5b21b6);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.5);
padding: 8px;
border-radius: 6px;
border: 1px dashed rgba(168, 85, 247, 0.2);
@@ -1221,22 +1221,22 @@ $stream-row-height: 25px;
border: 1px solid transparent;
&.success {
- background: #ecfdf5;
- color: #065f46;
- border-color: #a7f3d0;
+ background: var(--app-success-soft, #ecfdf5);
+ color: var(--app-success-text, #065f46);
+ border-color: var(--app-success-border, #a7f3d0);
}
&.failed {
- background: #fef2f2;
- color: #991b1b;
- border-color: #fca5a5;
+ background: var(--app-error-soft, #fef2f2);
+ color: var(--app-error-text, #991b1b);
+ border-color: var(--app-error-border, #fca5a5);
}
}
.checker-reason {
font-size: 12px;
line-height: 1.6;
- color: #3f3f46;
+ color: var(--app-text-control, #3f3f46);
font-weight: 500;
}
}
@@ -1286,7 +1286,7 @@ $stream-row-height: 25px;
-webkit-background-clip: border-box !important;
background-clip: border-box !important;
-webkit-text-fill-color: #71717a !important;
- color: #71717a !important;
+ color: var(--app-text-muted, #71717a) !important;
animation: none !important;
font-weight: normal !important;
margin-bottom: 0;
@@ -1307,7 +1307,7 @@ $stream-row-height: 25px;
min-height: 20px;
box-sizing: border-box;
letter-spacing: 0.5px;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
margin-bottom: $uniform-gap;
user-select: none;
display: flex;
@@ -1324,7 +1324,7 @@ $stream-row-height: 25px;
height: 8px;
border-radius: 50%;
display: inline-block;
- background: #d4d4d8;
+ background: var(--app-surface-raised, #d4d4d8);
&.streaming {
background: #3b82f6;
@@ -1343,24 +1343,24 @@ $stream-row-height: 25px;
.report-content {
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
strong {
font-weight: 600;
- color: #111827;
+ color: var(--app-text-strong, #111827);
}
div, p {
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin: 4px 0;
}
h1, h2, h3, h4, h5, h6 {
font-size: 12.5px;
font-weight: 600;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin-top: 8px;
margin-bottom: 4px;
}
@@ -1368,8 +1368,8 @@ $stream-row-height: 25px;
.inline-code {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 11px;
- background: #f4f4f5;
- color: #b91c1c;
+ background: var(--app-surface-raised, #f4f4f5);
+ color: var(--app-error-text, #b91c1c);
padding: 2px 6px;
border-radius: 4px;
}
@@ -1380,7 +1380,7 @@ $stream-row-height: 25px;
li {
font-size: 12.5px;
line-height: 1.65;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin: 4px 0;
padding-left: 2px;
}
@@ -1393,8 +1393,8 @@ $stream-row-height: 25px;
}
.step-action-summary-card {
- background: #f4fbf7;
- border: 1px solid #d1fae5;
+ background: var(--app-success-soft, #f4fbf7);
+ border: 1px solid var(--app-success-border, #d1fae5);
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 8px;
@@ -1406,7 +1406,7 @@ $stream-row-height: 25px;
font-size: 12px;
line-height: 1.5;
letter-spacing: 0.5px;
- color: #065f46;
+ color: var(--app-success-text, #065f46);
margin-bottom: 8px;
user-select: none;
}
@@ -1414,18 +1414,18 @@ $stream-row-height: 25px;
.report-content {
font-size: 12px;
line-height: 1.6;
- color: #27272a;
+ color: var(--app-text-strong, #27272a);
strong {
font-weight: 600;
- color: #09090b;
+ color: var(--app-text-strong, #09090b);
}
.inline-code {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 11px;
- background: #e6f4ea;
- color: #137333;
+ background: var(--app-success-soft, #e6f4ea);
+ color: var(--app-success-text, #137333);
padding: 2px 6px;
border-radius: 4px;
}
@@ -1463,7 +1463,7 @@ $stream-row-height: 25px;
display: flex;
align-items: center;
gap: 8px;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
font-family: 'Google Sans', 'Google Sans Text', 'Inter', Arial, sans-serif;
font-size: 12px;
line-height: 1.5;
@@ -1473,7 +1473,7 @@ $stream-row-height: 25px;
width: 8px;
height: 8px;
border-radius: 50%;
- background: #d4d4d8;
+ background: var(--app-surface-raised, #d4d4d8);
flex: 0 0 auto;
&.streaming {
@@ -1490,7 +1490,7 @@ $stream-row-height: 25px;
}
.llm-retry-note-text {
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
display: flex;
flex-direction: column;
min-width: 0;
@@ -1501,7 +1501,7 @@ $stream-row-height: 25px;
}
.llm-retry-guidance {
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
}
}
}
@@ -1522,13 +1522,13 @@ $stream-row-height: 25px;
.tool-call-label {
display: flex;
align-items: flex-start;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
line-height: 1.5;
user-select: text;
.error-icon {
font-size: 14px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
margin-top: 1px;
margin-left: -3px;
margin-right: -3px;
@@ -1542,13 +1542,13 @@ $stream-row-height: 25px;
.error-title-text {
font-weight: 500;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
margin-right: 4px;
flex-shrink: 0;
}
.error-message-text {
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
word-break: break-word;
}
}
@@ -1584,7 +1584,7 @@ $stream-row-height: 25px;
.tool-call-label {
font-size: 12px;
font-weight: normal;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
font-family: 'Google Sans', 'Google Sans Text', 'Inter', Arial, sans-serif;
letter-spacing: 0.5px;
line-height: 1.5;
@@ -1604,7 +1604,7 @@ $stream-row-height: 25px;
width: 8px;
height: 8px;
border-radius: 50%;
- background: #d4d4d8;
+ background: var(--app-surface-raised, #d4d4d8);
display: inline-block;
flex-shrink: 0;
@@ -1617,7 +1617,7 @@ $stream-row-height: 25px;
.is-failed-dot {
font-size: 13px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
display: inline-flex;
align-items: center;
justify-content: center;
@@ -1643,7 +1643,7 @@ $stream-row-height: 25px;
background: transparent;
font-size: 12px;
font-weight: 500;
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;
@@ -1656,7 +1656,7 @@ $stream-row-height: 25px;
}
&:hover {
- color: #1557b0;
+ color: var(--app-primary-text, #1557b0);
background: rgba(26, 115, 232, 0.08);
}
}
@@ -1669,8 +1669,8 @@ $stream-row-height: 25px;
}
.step-llm-error-card {
- background: #fef2f2;
- border: 1px solid #fecaca;
+ background: var(--app-error-soft, #fef2f2);
+ border: 1px solid var(--app-error-border, #fecaca);
border-radius: 10px;
padding: 10px 12px;
margin-top: 0;
@@ -1687,7 +1687,7 @@ $stream-row-height: 25px;
position: relative;
&:hover {
- border-color: #fca5a5;
+ border-color: var(--app-error-border, #fca5a5);
box-shadow: 0 4px 16px rgba(239, 68, 68, 0.08);
}
@@ -1695,19 +1695,19 @@ $stream-row-height: 25px;
display: flex;
align-items: center;
gap: 8px;
- border-bottom: 1px solid #fee2e2;
+ border-bottom: 1px solid var(--app-error-border, #fee2e2);
padding-bottom: 6px;
.error-icon {
font-size: 18px;
- color: #ef4444;
+ color: var(--app-error-text, #ef4444);
}
.error-title {
font-family: 'Google Sans', 'Google Sans Text', 'Inter', Arial, sans-serif;
font-weight: 700;
font-size: 12px;
- color: #991b1b;
+ color: var(--app-error-text, #991b1b);
}
}
@@ -1721,7 +1721,7 @@ $stream-row-height: 25px;
font-size: 12px;
.detail-value {
- color: #b91c1c;
+ color: var(--app-error-text, #b91c1c);
font-weight: 500;
word-break: break-word;
line-height: 1.4;
@@ -1780,7 +1780,7 @@ $stream-row-height: 25px;
justify-content: center;
margin: -2px 0 -2px -2px;
border-radius: 50%;
- background: rgba(255, 255, 255, 0.16);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.16);
font-size: 16px;
}
@@ -1814,7 +1814,7 @@ $stream-row-height: 25px;
}
.llm-resume-hint {
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
font-size: 11px;
line-height: 1.35;
white-space: nowrap;
@@ -1836,8 +1836,8 @@ $stream-row-height: 25px;
}
.step-android-action-card {
- background: #f8fafc;
- border: 1px solid #e2e8f0;
+ background: var(--app-surface-soft, #f8fafc);
+ border: 1px solid var(--app-border, #e2e8f0);
border-radius: 10px;
padding: 10px 12px;
margin-top: 0;
@@ -1854,13 +1854,17 @@ $stream-row-height: 25px;
cursor: pointer;
user-select: none;
+ &.plugin-tool-card.is-expanded {
+ width: min(100%, 760px);
+ }
+
&.is-running {
border-color: rgba(59, 130, 246, 0.35);
box-shadow: 0 2px 10px rgba(59, 130, 246, 0.08);
animation: subtle-card-glow 2.5s ease-in-out infinite alternate;
.action-icon {
- color: #3b82f6;
+ color: var(--app-primary-text, #3b82f6);
}
.action-title {
@@ -1881,97 +1885,97 @@ $stream-row-height: 25px;
}
&:hover {
- border-color: #cbd5e1;
+ border-color: var(--app-border-control, #cbd5e1);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
transform: translateY(-1px);
.action-expand-icon {
- color: #1e293b;
- background: #e2e8f0;
+ color: var(--app-text-control, #1e293b);
+ background: var(--app-surface-raised, #e2e8f0);
}
}
&.is-expanded {
- border-color: #94a3b8;
- background: #ffffff;
+ border-color: var(--app-border-control, #94a3b8);
+ background: var(--app-surface, #ffffff);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
.action-expand-icon {
- color: #0f172a;
- background: #e2e8f0;
+ color: var(--app-text-strong, #0f172a);
+ background: var(--app-surface-raised, #e2e8f0);
}
}
&.is-failed {
- background: #f8fafc;
- border-color: #fca5a5;
+ background: var(--app-surface-soft, #f8fafc);
+ border-color: var(--app-error-border, #fca5a5);
&.has-details {
cursor: pointer;
}
&:hover {
- border-color: #f87171;
+ border-color: var(--app-error-border, #f87171);
}
&.is-expanded {
- background: #ffffff;
- border-color: #f87171;
+ background: var(--app-surface, #ffffff);
+ border-color: var(--app-error-border, #f87171);
}
}
&.task-report-card {
width: min(680px, 100%);
box-sizing: border-box;
- background: #f0fdf4;
- border: 1px solid #bbf7d0;
+ background: var(--app-success-soft, #f0fdf4);
+ border: 1px solid var(--app-success-border, #bbf7d0);
&.is-completed {
- background: #f0fdf4;
- border-color: #86efac;
+ background: var(--app-success-soft, #f0fdf4);
+ border-color: var(--app-success-border, #86efac);
.report-icon {
- color: #16a34a;
+ color: var(--app-success-text, #16a34a);
font-variation-settings: 'FILL' 1;
}
.action-title {
- color: #14532d;
+ color: var(--app-success-text, #14532d);
font-weight: 700;
}
.action-status-badge.is-completed {
- background: #dcfce7;
- color: #15803d;
- border: 1px solid #bbf7d0;
+ background: var(--app-success-soft, #dcfce7);
+ color: var(--app-success-text, #15803d);
+ border: 1px solid var(--app-success-border, #bbf7d0);
}
}
&.is-failed {
- background: #fef2f2;
- border-color: #fecaca;
+ background: var(--app-error-soft, #fef2f2);
+ border-color: var(--app-error-border, #fecaca);
.report-icon {
- color: #dc2626;
+ color: var(--app-error-text, #dc2626);
font-variation-settings: 'FILL' 1;
}
.action-title {
- color: #7f1d1d;
+ color: var(--app-error-text, #7f1d1d);
font-weight: 700;
}
.action-status-badge.is-failed {
- background: #fee2e2;
- color: #b91c1c;
- border: 1px solid #fca5a5;
+ background: var(--app-error-soft, #fee2e2);
+ color: var(--app-error-text, #b91c1c);
+ border: 1px solid var(--app-error-border, #fca5a5);
}
}
.report-markdown-body {
font-size: 14px;
line-height: 1.7;
- color: #1f2937;
+ color: var(--app-text-strong, #1f2937);
font-weight: 400;
user-select: text;
word-break: normal;
@@ -1983,7 +1987,7 @@ $stream-row-height: 25px;
strong {
font-weight: 600;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
}
ul, ol {
@@ -1993,8 +1997,8 @@ $stream-row-height: 25px;
code {
font-family: 'Fira Code', monospace;
- background: #e2e8f0;
- color: #1f2937;
+ background: var(--app-surface-raised, #e2e8f0);
+ color: var(--app-text-strong, #1f2937);
padding: 1px 4px;
border-radius: 4px;
font-size: 11px;
@@ -2016,16 +2020,16 @@ $stream-row-height: 25px;
display: flex;
align-items: center;
gap: 8px;
- border-bottom: 1px solid #f1f5f9;
+ border-bottom: 1px solid var(--app-border, #f1f5f9);
padding-bottom: 6px;
width: 100%;
.action-icon {
font-size: 18px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
&.is-failed {
- color: #ef4444;
+ color: var(--app-error-text, #ef4444);
}
}
@@ -2034,7 +2038,7 @@ $stream-row-height: 25px;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', Arial, "Liberation Sans", sans-serif;
font-weight: 700;
font-size: 12px;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
flex-grow: 1;
}
@@ -2045,17 +2049,17 @@ $stream-row-height: 25px;
font-weight: 600;
padding: 1px 6px;
border-radius: 4px;
- background: #f0fdf4;
- color: #166534;
- border: 1px solid #bbf7d0;
+ background: var(--app-success-soft, #f0fdf4);
+ color: var(--app-success-text, #166534);
+ border: 1px solid var(--app-success-border, #bbf7d0);
letter-spacing: 0.2px;
margin-right: 6px;
white-space: nowrap;
&.is-outputter {
- background: linear-gradient(135deg, rgba(245, 243, 255, 0.95) 0%, rgba(238, 242, 255, 0.95) 100%);
- color: #6d28d9;
- border-color: #c4b5fd;
+ background: linear-gradient(135deg, rgb(var(--app-exploration-soft-rgb, 245 243 255) / 0.95) 0%, rgb(var(--app-primary-soft-rgb, 238 242 255) / 0.95) 100%);
+ color: var(--app-exploration-text, #6d28d9);
+ border-color: var(--app-exploration-border, #c4b5fd);
}
}
@@ -2075,22 +2079,22 @@ $stream-row-height: 25px;
}
&.is-failed {
- background: #fef2f2;
- color: #dc2626;
- border: 1px solid #fee2e2;
+ background: var(--app-error-soft, #fef2f2);
+ color: var(--app-error-text, #dc2626);
+ border: 1px solid var(--app-error-border, #fee2e2);
.status-icon {
- color: #ef4444;
+ color: var(--app-error-text, #ef4444);
}
}
&.is-paused {
- background: #fff7ed;
- color: #c2410c;
- border: 1px solid #fed7aa;
+ background: var(--app-warning-soft, #fff7ed);
+ color: var(--app-warning-text, #c2410c);
+ border: 1px solid var(--app-warning-border, #fed7aa);
.status-icon {
- color: #f97316;
+ color: var(--app-warning-text, #f97316);
}
}
@@ -2098,7 +2102,7 @@ $stream-row-height: 25px;
.action-expand-icon {
font-size: 18px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
margin-left: 4px;
padding: 2px;
border-radius: 50%;
@@ -2120,20 +2124,20 @@ $stream-row-height: 25px;
font-size: 12px;
.detail-label {
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
font-weight: 500;
width: 85px;
flex-shrink: 0;
}
.detail-value {
- color: #334155;
+ color: var(--app-text-control, #334155);
font-weight: 600;
word-break: break-word;
&.coordinate-badge {
- background: #e2e8f0;
- color: #475569;
+ background: var(--app-surface-raised, #e2e8f0);
+ color: var(--app-text-secondary, #475569);
padding: 2px 6px;
border-radius: 4px;
font-family: 'Fira Code', monospace;
@@ -2142,13 +2146,13 @@ $stream-row-height: 25px;
&.command-value {
font-family: 'Fira Code', Consolas, Monaco, monospace;
- background: #f1f5f9;
- border: 1px solid #e2e8f0;
+ background: var(--app-surface-soft, #f1f5f9);
+ border: 1px solid var(--app-border, #e2e8f0);
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
white-space: pre-wrap;
word-break: break-all;
@@ -2167,14 +2171,14 @@ $stream-row-height: 25px;
.failure-retry-details {
margin-top: 4px;
padding-top: 9px;
- border-top: 1px solid #fecaca;
+ border-top: 1px solid var(--app-error-border, #fecaca);
display: flex;
flex-direction: column;
gap: 7px;
animation: fadeInDetails 0.2s ease-out;
.failure-retry-title {
- color: #991b1b;
+ color: var(--app-error-text, #991b1b);
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
@@ -2185,12 +2189,12 @@ $stream-row-height: 25px;
display: flex;
justify-content: space-between;
gap: 12px;
- color: #7f1d1d;
+ color: var(--app-error-text, #7f1d1d);
font-size: 10.5px;
line-height: 1.4;
span:last-child {
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
text-align: right;
}
}
@@ -2208,10 +2212,10 @@ $stream-row-height: 25px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
- background: #f8fafc;
+ background: var(--app-surface-soft, #f8fafc);
padding: 10px;
border-radius: 8px;
- border: 1px solid #f1f5f9;
+ border: 1px solid var(--app-border, #f1f5f9);
.grid-item {
display: flex;
@@ -2227,13 +2231,13 @@ $stream-row-height: 25px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
}
.grid-value {
font-size: 12px;
font-weight: 600;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
word-break: break-all;
&.report-markdown-body {
@@ -2247,8 +2251,8 @@ $stream-row-height: 25px;
&.code-pill {
font-family: 'Roboto Mono', 'Fira Code', Consolas, monospace;
font-size: 11px;
- background: #e2e8f0;
- color: #334155;
+ background: var(--app-surface-raised, #e2e8f0);
+ color: var(--app-text-control, #334155);
padding: 2px 6px;
border-radius: 4px;
display: inline-block;
@@ -2260,7 +2264,7 @@ $stream-row-height: 25px;
font-family: 'Roboto Mono', 'Fira Code', Consolas, monospace;
font-size: 11px;
background: #0f172a;
- color: #38bdf8;
+ color: var(--app-info-text, #38bdf8);
padding: 4px 8px;
border-radius: 4px;
display: block;
@@ -2294,7 +2298,7 @@ $stream-row-height: 25px;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', Arial, "Liberation Sans", sans-serif;
font-size: 11.5px;
font-weight: 500;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
letter-spacing: normal;
text-transform: none;
@@ -2305,9 +2309,9 @@ $stream-row-height: 25px;
justify-content: center;
align-items: center;
max-width: 100%;
- border: 1px solid #e2e8f0;
+ border: 1px solid var(--app-border, #e2e8f0);
border-radius: 8px;
- background-color: #f8fafc;
+ background-color: var(--app-surface-soft, #f8fafc);
cursor: zoom-in;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
@@ -2316,7 +2320,7 @@ $stream-row-height: 25px;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
- border-color: #cbd5e1;
+ border-color: var(--app-border-control, #cbd5e1);
}
.img-relative-box {
@@ -2352,9 +2356,9 @@ $stream-row-height: 25px;
display: flex;
align-items: center;
gap: 8px;
- background: #fff1f2;
- border: 1px solid #fecdd3;
- color: #be123c;
+ background: var(--app-error-soft, #fff1f2);
+ border: 1px solid var(--app-error-border, #fecdd3);
+ color: var(--app-error-text, #be123c);
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
@@ -2362,7 +2366,7 @@ $stream-row-height: 25px;
.icon {
font-size: 16px;
- color: #e11d48;
+ color: var(--app-error-text, #e11d48);
}
}
}
@@ -2373,15 +2377,15 @@ $stream-row-height: 25px;
display: flex;
flex-direction: column;
gap: 12px;
- background: #ffffff;
- border: 1px solid rgba(0, 0, 0, 0.06);
+ background: var(--app-surface, #ffffff);
+ border: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.06);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.01), 0 2px 4px -1px rgba(0, 0, 0, 0.006);
.final-summary-header {
font-weight: 500;
font-size: 12px;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
display: flex;
align-items: center;
gap: 6px;
@@ -2391,11 +2395,11 @@ $stream-row-height: 25px;
.final-summary-body {
font-size: 12px;
line-height: 1.6;
- color: #27272a;
+ color: var(--app-text-strong, #27272a);
strong {
font-weight: 600;
- color: #09090b;
+ color: var(--app-text-strong, #09090b);
}
ul, ol {
@@ -2707,8 +2711,8 @@ $stream-row-height: 25px;
.outputter-synthesis-stream-card {
margin: 16px 0 12px 0;
border-radius: 12px;
- border: 1px solid rgba(196, 181, 253, 0.85);
- background: linear-gradient(135deg, rgba(245, 243, 255, 0.95) 0%, rgba(250, 245, 255, 0.98) 100%);
+ border: 1px solid rgb(var(--app-exploration-border-rgb, 196 181 253) / 0.85);
+ background: linear-gradient(135deg, rgb(var(--app-exploration-soft-rgb, 245 243 255) / 0.95) 0%, rgb(var(--app-exploration-soft-rgb, 250 245 255) / 0.98) 100%);
box-shadow: 0 4px 16px rgba(124, 58, 237, 0.08), 0 1px 3px rgba(0, 0, 0, 0.03);
overflow: hidden;
animation: fadeInModal 0.25s ease-out;
@@ -2718,8 +2722,8 @@ $stream-row-height: 25px;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
- background: rgba(237, 233, 254, 0.6);
- border-bottom: 1px solid rgba(196, 181, 253, 0.7);
+ background: rgb(var(--app-exploration-soft-rgb, 237 233 254) / 0.6);
+ border-bottom: 1px solid rgb(var(--app-exploration-border-rgb, 196 181 253) / 0.7);
gap: 12px;
flex-wrap: wrap;
@@ -2730,13 +2734,13 @@ $stream-row-height: 25px;
.icon-sparkle {
font-size: 22px;
- color: #7c3aed;
+ color: var(--app-exploration-text, #7c3aed);
}
.main-title {
font-size: 14px;
font-weight: 700;
- color: #4338ca;
+ color: var(--app-exploration-text, #4338ca);
}
}
@@ -2780,7 +2784,7 @@ $stream-row-height: 25px;
.parsed-note-title {
font-size: 14px;
font-weight: 700;
- color: #1e1b4b;
+ color: var(--app-exploration-text, #1e1b4b);
margin: 0;
}
@@ -2795,21 +2799,21 @@ $stream-row-height: 25px;
gap: 8px;
font-size: 12.5px;
line-height: 1.45;
- color: #374151;
+ color: var(--app-text-control, #374151);
.status-icon {
font-size: 16px;
- color: #7c3aed;
+ color: var(--app-exploration-text, #7c3aed);
margin-top: 1px;
flex-shrink: 0;
}
&.status-checked .status-icon {
- color: #10b981;
+ color: var(--app-success-text, #10b981);
}
&.status-progress .status-icon {
- color: #f59e0b;
+ color: var(--app-warning-text, #f59e0b);
}
}
}
@@ -2823,19 +2827,19 @@ $stream-row-height: 25px;
margin: 0;
font-size: 12.5px;
line-height: 1.5;
- color: #4b5563;
+ color: var(--app-text-secondary, #4b5563);
strong {
- color: #1e1b4b;
+ color: var(--app-exploration-text, #1e1b4b);
}
code {
font-family: 'JetBrains Mono', monospace;
- background: rgba(255, 255, 255, 0.9);
- border: 1px solid #e5e7eb;
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.9);
+ border: 1px solid var(--app-border, #e5e7eb);
padding: 1px 4px;
border-radius: 4px;
- color: #6d28d9;
+ color: var(--app-exploration-text, #6d28d9);
font-size: 11.5px;
}
}
@@ -2846,7 +2850,7 @@ $stream-row-height: 25px;
/* Checker blocks share the step timeline, marked by a violet left border. */
.agent-stream-container .stream-logs-content .phase-body .block-item.type-checker {
- border-left: 2px solid #ddd6fe;
+ border-left: 2px solid var(--app-exploration-border, #ddd6fe);
padding-left: 12px;
margin-left: -14px;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', Arial, sans-serif;
@@ -2854,7 +2858,7 @@ $stream-row-height: 25px;
.check-index {
flex: 0 0 14px;
text-align: right;
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
font-variant-numeric: tabular-nums;
user-select: none;
}
@@ -2873,7 +2877,7 @@ $stream-row-height: 25px;
font-size: 12px;
line-height: 1.5;
letter-spacing: 0.2px;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
user-select: none;
.stream-status-dot {
@@ -2882,7 +2886,7 @@ $stream-row-height: 25px;
height: 8px;
border-radius: 50%;
display: inline-block;
- background: #d4d4d8;
+ background: var(--app-surface-raised, #d4d4d8);
&.streaming {
background: #7c3aed;
@@ -2894,7 +2898,7 @@ $stream-row-height: 25px;
.check-title {
flex: 0 0 auto;
font-weight: 500;
- color: #52525b;
+ color: var(--app-text-secondary, #52525b);
}
.check-subject {
@@ -2909,15 +2913,15 @@ $stream-row-height: 25px;
flex: 0 0 auto;
margin-left: auto;
font-weight: 500;
- color: #52525b;
+ color: var(--app-text-secondary, #52525b);
white-space: nowrap;
}
}
- &.tone-passed .check-status { color: #15803d; }
- &.tone-failed .check-status { color: #b91c1c; }
- &.tone-inconclusive .check-status { color: #b45309; }
- &.tone-running .check-status { color: #6d28d9; }
+ &.tone-passed .check-status { color: var(--app-success-text, #15803d); }
+ &.tone-failed .check-status { color: var(--app-error-text, #b91c1c); }
+ &.tone-inconclusive .check-status { color: var(--app-warning-text, #b45309); }
+ &.tone-running .check-status { color: var(--app-exploration-text, #6d28d9); }
&.tone-muted { opacity: 0.6; }
.check-items {
@@ -2934,7 +2938,7 @@ $stream-row-height: 25px;
gap: 8px;
font-size: 12px;
line-height: 1.5;
- color: #3f3f46;
+ color: var(--app-text-control, #3f3f46);
.check-text {
flex: 1 1 auto;
@@ -2944,7 +2948,7 @@ $stream-row-height: 25px;
.check-method {
flex: 0 0 auto;
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
white-space: nowrap;
}
}
@@ -2964,7 +2968,7 @@ $stream-row-height: 25px;
gap: 8px;
font-size: 12px;
line-height: 1.5;
- color: #52525b;
+ color: var(--app-text-secondary, #52525b);
.check-index {
line-height: 18px;
@@ -2974,13 +2978,13 @@ $stream-row-height: 25px;
flex: 0 0 auto;
font-size: 15px;
line-height: 18px;
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
}
- &.passed .verdict-icon { color: #16a34a; }
- &.failed .verdict-icon { color: #dc2626; }
- &.inconclusive .verdict-icon { color: #d97706; }
- &.superseded, &.unchecked { color: #a1a1aa; }
+ &.passed .verdict-icon { color: var(--app-success-text, #16a34a); }
+ &.failed .verdict-icon { color: var(--app-error-text, #dc2626); }
+ &.inconclusive .verdict-icon { color: var(--app-warning-text, #d97706); }
+ &.superseded, &.unchecked { color: var(--app-text-faint, #a1a1aa); }
.verdict-body {
flex: 1 1 auto;
@@ -2990,14 +2994,14 @@ $stream-row-height: 25px;
.verdict-suggestion {
display: block;
- color: #1d4ed8;
+ color: var(--app-primary-text, #1d4ed8);
}
}
.check-note {
font-size: 12px;
line-height: 1.5;
- color: #71717a;
+ color: var(--app-text-muted, #71717a);
padding-left: 22px;
word-break: break-word;
}
@@ -3050,8 +3054,8 @@ $stream-row-height: 25px;
box-sizing: border-box;
max-width: calc(100vw - 24px);
max-height: 480px;
- background: #ffffff;
- border: 1px solid #cbd5e1;
+ background: var(--app-surface, #ffffff);
+ border: 1px solid var(--app-border-control, #cbd5e1);
border-radius: 12px;
box-shadow: 0 16px 40px -4px rgba(15, 23, 42, 0.16), 0 4px 12px rgba(0, 0, 0, 0.06);
z-index: 100;
@@ -3067,14 +3071,14 @@ $stream-row-height: 25px;
align-items: center;
gap: 8px;
padding-bottom: 8px;
- border-bottom: 1px solid #e2e8f0;
+ border-bottom: 1px solid var(--app-border, #e2e8f0);
font-size: 14px;
font-weight: 700;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
.material-symbols-outlined {
font-size: 18px;
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
}
}
@@ -3089,21 +3093,21 @@ $stream-row-height: 25px;
align-items: center;
gap: 7px;
padding-bottom: 9px;
- border-bottom: 1px solid #e2e8f0;
+ border-bottom: 1px solid var(--app-border, #e2e8f0);
.material-symbols-outlined {
font-size: 18px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
&.is-pro {
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
}
}
.run-info-title {
font-size: 13px;
font-weight: 700;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
}
.run-info-model {
@@ -3114,7 +3118,7 @@ $stream-row-height: 25px;
white-space: nowrap;
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 10.5px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
}
@@ -3124,7 +3128,7 @@ $stream-row-height: 25px;
justify-content: space-between;
gap: 12px;
padding: 9px 0;
- border-bottom: 1px solid #f1f5f9;
+ border-bottom: 1px solid var(--app-border, #f1f5f9);
&:last-child {
border-bottom: none;
@@ -3147,14 +3151,14 @@ $stream-row-height: 25px;
.run-info-label {
font-size: 12px;
font-weight: 500;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
white-space: nowrap;
}
.run-info-value {
font-size: 12.5px;
font-weight: 600;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
text-align: right;
font-variant-numeric: tabular-nums;
@@ -3169,21 +3173,21 @@ $stream-row-height: 25px;
margin-top: 2px;
font-size: 11px;
font-weight: 400;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
.run-info-sub-inline {
margin-left: 3px;
font-size: 11px;
font-weight: 400;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
.run-info-meter {
height: 4px;
margin-top: 7px;
border-radius: 2px;
- background: #e2e8f0;
+ background: var(--app-surface-raised, #e2e8f0);
overflow: hidden;
.run-info-meter-fill {
@@ -3205,7 +3209,7 @@ $stream-row-height: 25px;
.run-info-note {
padding-top: 9px;
font-size: 11.5px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
}
@@ -3229,12 +3233,12 @@ $stream-row-height: 25px;
align-items: center;
gap: 5px;
padding: 4px 10px;
- border: 1px solid #e2e8f0;
- background: #ffffff;
+ border: 1px solid var(--app-border, #e2e8f0);
+ background: var(--app-surface, #ffffff);
border-radius: 16px;
font-size: 11.5px;
font-weight: 500;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
cursor: pointer;
transition: all 0.15s ease;
@@ -3243,15 +3247,15 @@ $stream-row-height: 25px;
}
&:hover {
- background: #f8fafc;
- border-color: #cbd5e1;
- color: #1e293b;
+ background: var(--app-surface-soft, #f8fafc);
+ border-color: var(--app-border-control, #cbd5e1);
+ color: var(--app-text-control, #1e293b);
}
&.active {
- background: #eff6ff;
- border-color: #bfdbfe;
- color: #1d4ed8;
+ background: var(--app-primary-soft, #eff6ff);
+ border-color: var(--app-primary-border, #bfdbfe);
+ color: var(--app-primary-text, #1d4ed8);
font-weight: 600;
}
}
@@ -3267,15 +3271,15 @@ $stream-row-height: 25px;
.note-title {
font-size: 13.5px;
font-weight: 700;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin: 0 0 4px 0;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', sans-serif;
}
.code-segment {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
- background-color: #f1f3f4;
- color: #1967d2;
+ background-color: var(--app-info-soft, #f1f3f4);
+ color: var(--app-primary-text, #1967d2);
padding: 1px 5px;
border-radius: 4px;
font-size: 12px;
@@ -3299,7 +3303,7 @@ $stream-row-height: 25px;
opacity: 0.7;
}
.milestone-text {
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
}
}
}
@@ -3316,7 +3320,7 @@ $stream-row-height: 25px;
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
&.checked, &.progress {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
}
&.unchecked {
color: #cbd5e1;
@@ -3325,7 +3329,7 @@ $stream-row-height: 25px;
.milestone-text {
font-family: 'Google Sans', 'Google Sans Text', 'Inter', sans-serif;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
font-size: 13px;
line-height: 1.5;
word-break: break-word;
@@ -3345,19 +3349,19 @@ $stream-row-height: 25px;
align-items: flex-start;
gap: 8px;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', sans-serif;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
font-size: 12.5px;
line-height: 1.5;
&.checked {
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
.check-icon.checked {
opacity: 0.7;
}
}
&.verify {
- color: #334155;
+ color: var(--app-text-control, #334155);
gap: 8px;
align-items: flex-start;
@@ -3372,30 +3376,30 @@ $stream-row-height: 25px;
line-height: 1.2;
letter-spacing: 0.2px;
text-transform: lowercase;
- color: #475569;
- background: #f1f5f9;
- border: 1px solid #e2e8f0;
+ color: var(--app-text-secondary, #475569);
+ background: var(--app-surface-soft, #f1f5f9);
+ border: 1px solid var(--app-border, #e2e8f0);
border-radius: 4px;
padding: 1.5px 6px;
margin-top: 1.5px;
user-select: none;
&.assert {
- color: #0369a1;
- background: #f0f9ff;
- border-color: #e0f2fe;
+ color: var(--app-info-text, #0369a1);
+ background: var(--app-info-soft, #f0f9ff);
+ border-color: var(--app-info-border, #e0f2fe);
}
}
.line-text {
- color: #334155;
+ color: var(--app-text-control, #334155);
font-size: 12.5px;
line-height: 1.5;
}
}
&.finding {
- color: #92400e;
+ color: var(--app-warning-text, #92400e);
gap: 8px;
align-items: flex-start;
@@ -3410,9 +3414,9 @@ $stream-row-height: 25px;
line-height: 1.2;
letter-spacing: 0.2px;
text-transform: lowercase;
- color: #b45309;
- background: #fef3c7;
- border: 1px solid #fde68a;
+ color: var(--app-warning-text, #b45309);
+ background: var(--app-warning-soft, #fef3c7);
+ border: 1px solid var(--app-warning-border, #fde68a);
border-radius: 4px;
padding: 1.5px 6px;
margin-top: 1.5px;
@@ -3420,7 +3424,7 @@ $stream-row-height: 25px;
}
.line-text {
- color: #92400e;
+ color: var(--app-warning-text, #92400e);
font-size: 12.5px;
line-height: 1.5;
}
@@ -3433,7 +3437,7 @@ $stream-row-height: 25px;
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
&.checked, &.progress {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
}
&.unchecked {
color: #cbd5e1;
@@ -3441,7 +3445,7 @@ $stream-row-height: 25px;
}
.bullet {
- color: #9ca3af;
+ color: var(--app-text-faint, #9ca3af);
font-weight: bold;
user-select: none;
}
@@ -3463,8 +3467,8 @@ $stream-row-height: 25px;
}
.verification-card {
- background: #f8fafc;
- border: 1px solid #e2e8f0;
+ background: var(--app-surface-soft, #f8fafc);
+ border: 1px solid var(--app-border, #e2e8f0);
border-radius: 6px;
padding: 7px 10px;
display: flex;
@@ -3487,10 +3491,10 @@ $stream-row-height: 25px;
line-height: 1;
letter-spacing: 0.6px;
text-transform: uppercase;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
&.assert {
- color: #0284c7;
+ color: var(--app-info-text, #0284c7);
}
}
}
@@ -3499,35 +3503,35 @@ $stream-row-height: 25px;
font-family: inherit;
font-size: 12px;
line-height: 1.5;
- color: #334155;
+ color: var(--app-text-control, #334155);
word-break: break-word;
.bold-segment {
font-weight: 600;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
}
.code-segment {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 11px;
- background: #f1f5f9;
- color: #0f172a;
+ background: var(--app-surface-soft, #f1f5f9);
+ color: var(--app-text-strong, #0f172a);
padding: 1px 5px;
border-radius: 4px;
- border: 1px solid #e2e8f0;
+ border: 1px solid var(--app-border, #e2e8f0);
}
}
&.finding {
- background: #fffbeb;
- border-color: #fef3c7;
+ background: var(--app-warning-soft, #fffbeb);
+ border-color: var(--app-warning-border, #fef3c7);
.card-header .card-tag {
- color: #b45309;
+ color: var(--app-warning-text, #b45309);
}
.card-body {
- color: #92400e;
+ color: var(--app-warning-text, #92400e);
}
}
}
@@ -3538,14 +3542,14 @@ $stream-row-height: 25px;
gap: 6px;
margin-top: 6px;
padding-top: 6px;
- border-top: 1px dashed #e2e8f0;
+ border-top: 1px dashed var(--app-border, #e2e8f0);
.md-line {
display: flex;
align-items: flex-start;
gap: 6px;
font-size: 12.5px;
- color: #334155;
+ color: var(--app-text-control, #334155);
line-height: 1.5;
.line-text {
@@ -3561,7 +3565,7 @@ $stream-row-height: 25px;
width: 6px;
}
&::-webkit-scrollbar-thumb {
- background: #cbd5e1;
+ background: var(--app-surface-raised, #cbd5e1);
border-radius: 4px;
}
@@ -3570,7 +3574,7 @@ $stream-row-height: 25px;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
- border-bottom: 1px solid #e2e8f0;
+ border-bottom: 1px solid var(--app-border, #e2e8f0);
.dropdown-title {
display: flex;
@@ -3578,11 +3582,11 @@ $stream-row-height: 25px;
gap: 8px;
font-size: 13px;
font-weight: 700;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
.icon-main {
font-size: 18px;
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
}
}
}
@@ -3593,7 +3597,7 @@ $stream-row-height: 25px;
gap: 6px;
font-size: 11px;
font-weight: 700;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 4px 2px 2px 2px;
@@ -3604,11 +3608,11 @@ $stream-row-height: 25px;
.icon-bolt {
font-size: 15px;
- color: #d97706;
+ color: var(--app-warning-text, #d97706);
}
.icon-history {
font-size: 15px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
}
}
@@ -3616,9 +3620,9 @@ $stream-row-height: 25px;
padding: 20px 14px;
text-align: center;
font-size: 12px;
- color: #94a3b8;
- background: #f8fafc;
- border: 1px dashed #e2e8f0;
+ color: var(--app-text-faint, #94a3b8);
+ background: var(--app-surface-soft, #f8fafc);
+ border: 1px dashed var(--app-border, #e2e8f0);
border-radius: 6px;
display: flex;
flex-direction: column;
@@ -3628,7 +3632,7 @@ $stream-row-height: 25px;
.empty-icon {
font-size: 22px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
}
@@ -3640,8 +3644,8 @@ $stream-row-height: 25px;
.dropdown-item-card {
padding: 10px 12px;
- background: #f8fafc;
- border: 1px solid #e2e8f0;
+ background: var(--app-surface-soft, #f8fafc);
+ border: 1px solid var(--app-border, #e2e8f0);
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease;
@@ -3650,8 +3654,8 @@ $stream-row-height: 25px;
gap: 6px;
&:hover {
- background: #f1f5f9;
- border-color: #cbd5e1;
+ background: var(--app-surface-soft, #f1f5f9);
+ border-color: var(--app-border-control, #cbd5e1);
transform: translateY(-1px);
.item-header .item-header-actions {
@@ -3671,12 +3675,12 @@ $stream-row-height: 25px;
}
&.selected {
- background: #eff6ff;
- border-color: #93c5fd;
+ background: var(--app-primary-soft, #eff6ff);
+ border-color: var(--app-primary-border, #93c5fd);
box-shadow: 0 0 0 1px #2563eb;
.item-goal {
- color: #1e3a8a;
+ color: var(--app-primary-text, #1e3a8a);
font-weight: 600;
}
}
@@ -3684,12 +3688,12 @@ $stream-row-height: 25px;
&.running {
border-left: 3.5px solid #2563eb;
}
- &.paused, &.task_paused { border-left: 3.5px solid #d97706; background: #fffbeb; }
+ &.paused, &.task_paused { border-left: 3.5px solid #d97706; background: var(--app-warning-soft, #fffbeb); }
&.resuming, &.queued { border-left: 3.5px solid #7c3aed; }
- &.failed { border-left: 3.5px solid #dc2626; background: #fff7f7; }
+ &.failed { border-left: 3.5px solid #dc2626; background: var(--app-error-soft, #fff7f7); }
&.completed { border-left: 3.5px solid #16a34a; }
&.cancelled, &.interrupted { border-left: 3.5px solid #ea580c; }
- .item-error { color: #b91c1c; font-size: 11px; line-height: 1.4; overflow-wrap: anywhere; }
+ .item-error { color: var(--app-error-text, #b91c1c); font-size: 11px; line-height: 1.4; overflow-wrap: anywhere; }
.item-header {
display: flex;
@@ -3713,9 +3717,9 @@ $stream-row-height: 25px;
padding: 0 5px;
border-radius: 4px;
letter-spacing: 0.05em;
- border: 1px solid #e2e8f0;
- background: #fafafa;
- color: #64748b;
+ border: 1px solid var(--app-border, #e2e8f0);
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-text-muted, #64748b);
&::before {
content: '';
@@ -3727,65 +3731,65 @@ $stream-row-height: 25px;
}
&.running {
- background: #fafafa;
- color: #1f1f1f;
- border-color: #1f1f1f;
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-text-strong, #1f1f1f);
+ border-color: var(--app-border-control, #1f1f1f);
&::before {
background-color: #1f1f1f;
animation: pulse-black-dot-small 1.5s infinite ease-in-out;
}
}
&.paused {
- background: #fff7ed;
- color: #c2410c;
- border-color: #fdba74;
+ background: var(--app-warning-soft, #fff7ed);
+ color: var(--app-warning-text, #c2410c);
+ border-color: var(--app-warning-border, #fdba74);
&::before { background-color: #f97316; }
}
&.task_paused {
- background: #ffedd5;
- color: #9a3412;
+ background: var(--app-warning-soft, #ffedd5);
+ color: var(--app-warning-text, #9a3412);
border-color: #fb923c;
&::before { background-color: #ea580c; }
}
&.resuming, &.queued {
- background: #f3e8ff;
- color: #6b21a8;
- border-color: #c4b5fd;
+ background: var(--app-exploration-soft, #f3e8ff);
+ color: var(--app-exploration-text, #6b21a8);
+ border-color: var(--app-exploration-border, #c4b5fd);
&::before { background-color: #7c3aed; }
}
&.completed {
- background: #dcfce7;
- color: #166534;
- border-color: #86efac;
+ background: var(--app-success-soft, #dcfce7);
+ color: var(--app-success-text, #166534);
+ border-color: var(--app-success-border, #86efac);
&::before {
background-color: #16a34a;
}
}
&.pending {
- background: #fafafa;
- color: #64748b;
- border-color: #e2e8f0;
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-text-muted, #64748b);
+ border-color: var(--app-border, #e2e8f0);
&::before {
background-color: #94a3b8;
}
}
&.failed {
- background: #fee2e2;
- color: #991b1b;
- border-color: #fca5a5;
+ background: var(--app-error-soft, #fee2e2);
+ color: var(--app-error-text, #991b1b);
+ border-color: var(--app-error-border, #fca5a5);
&::before {
background-color: #ef4444;
}
}
&.cancelled {
- background: #ffedd5;
- color: #9a3412;
- border-color: #fdba74;
+ background: var(--app-warning-soft, #ffedd5);
+ color: var(--app-warning-text, #9a3412);
+ border-color: var(--app-warning-border, #fdba74);
&::before {
background-color: #ea580c;
}
}
- &.interrupted { background: #fef3c7; color: #92400e; border-color: #fbbf24; &::before { background-color: #d97706; } }
+ &.interrupted { background: var(--app-warning-soft, #fef3c7); color: var(--app-warning-text, #92400e); border-color: #fbbf24; &::before { background-color: #d97706; } }
.status-dot {
display: none;
@@ -3809,7 +3813,7 @@ $stream-row-height: 25px;
min-width: 0;
flex-shrink: 1;
font-size: 10.5px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
line-height: 1;
cursor: default;
transition: color 0.15s ease, opacity 0.15s ease;
@@ -3819,7 +3823,7 @@ $stream-row-height: 25px;
width: 12px;
height: 12px;
line-height: 12px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
opacity: 0.75;
flex-shrink: 0;
}
@@ -3834,9 +3838,9 @@ $stream-row-height: 25px;
}
&:hover {
- color: #334155;
+ color: var(--app-text-control, #334155);
.device-icon {
- color: #334155;
+ color: var(--app-text-control, #334155);
opacity: 1;
}
}
@@ -3844,7 +3848,7 @@ $stream-row-height: 25px;
.item-time-divider {
font-size: 10px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
line-height: 1;
user-select: none;
flex-shrink: 0;
@@ -3853,7 +3857,7 @@ $stream-row-height: 25px;
.item-time {
font-size: 11px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
white-space: nowrap;
flex-shrink: 0;
line-height: 1;
@@ -3871,7 +3875,7 @@ $stream-row-height: 25px;
border: none;
background: transparent;
border-radius: 3px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
cursor: pointer;
display: inline-flex;
align-items: center;
@@ -3886,8 +3890,8 @@ $stream-row-height: 25px;
}
&:hover {
- color: #ef4444;
- background: #fee2e2;
+ color: var(--app-error-text, #ef4444);
+ background: var(--app-error-soft, #fee2e2);
}
}
@@ -3902,7 +3906,7 @@ $stream-row-height: 25px;
border: none;
background: transparent;
border-radius: 3px;
- color: #ef4444;
+ color: var(--app-error-text, #ef4444);
cursor: pointer;
display: inline-flex;
align-items: center;
@@ -3917,8 +3921,8 @@ $stream-row-height: 25px;
}
&:hover {
- color: #dc2626;
- background: #fee2e2;
+ color: var(--app-error-text, #dc2626);
+ background: var(--app-error-soft, #fee2e2);
transform: translateY(-50%) scale(1.1);
}
}
@@ -3927,7 +3931,7 @@ $stream-row-height: 25px;
.item-goal {
font-size: 12.5px;
- color: #334155;
+ color: var(--app-text-control, #334155);
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
@@ -3942,12 +3946,12 @@ $stream-row-height: 25px;
justify-content: space-between;
gap: 10px;
font-size: 10.5px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
.item-id {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 10.5px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -3956,7 +3960,7 @@ $stream-row-height: 25px;
transition: color 0.15s ease;
&:hover {
- color: #475569;
+ color: var(--app-text-secondary, #475569);
}
}
@@ -3974,12 +3978,12 @@ $stream-row-height: 25px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
- color: #334155;
+ color: var(--app-text-control, #334155);
transition: background 0.15s ease;
.note-icon {
font-size: 18px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
}
.note-key-text {
@@ -3989,62 +3993,63 @@ $stream-row-height: 25px;
.check-icon {
font-size: 18px;
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
}
&:hover {
- background: #f1f5f9;
- color: #0f172a;
+ background: var(--app-surface-soft, #f1f5f9);
+ color: var(--app-text-strong, #0f172a);
}
&.selected {
- background: #eff6ff;
- color: #2563eb;
+ background: var(--app-primary-soft, #eff6ff);
+ color: var(--app-primary-text, #2563eb);
font-weight: 600;
.note-icon {
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
}
}
}
}
.guidance-step-note, .guidance-queue-item {
padding: 10px 13px;
- border: 1px solid #bfdbfe;
+ border: 1px solid var(--app-primary-border, #bfdbfe);
border-radius: 10px;
- background: #eff6ff;
- color: #1e3a8a;
+ background: var(--app-primary-soft, #eff6ff);
+ color: var(--app-primary-text, #1e3a8a);
line-height: 1.6;
overflow-wrap: anywhere;
.guidance-step-title { display: flex; align-items: center; gap: 6px; font-weight: 650; }
.material-symbols-outlined { font-size: 17px; }
- p { margin: 5px 0; white-space: pre-wrap; color: #1e293b; }
- small { color: #475569; }
+ p { margin: 5px 0; white-space: pre-wrap; color: var(--app-text-control, #1e293b); }
+ small { color: var(--app-text-secondary, #475569); }
}
.guidance-queue { display: grid; gap: 8px; padding: 12px 16px; }
.guidance-queue-item strong { font-size: 12px; }
+.guidance-queue-item small { display: block; margin-top: 2px; font-size: 11px; }
.session-failure-summary {
margin: 12px 16px 28px;
padding: 12px 14px;
- border: 1px solid #fecdd3;
+ border: 1px solid var(--app-error-border, #fecdd3);
border-radius: 10px;
- background: #fff7f8;
- color: #881337;
+ background: var(--app-error-soft, #fff7f8);
+ color: var(--app-error-text, #881337);
strong { font-size: 13px; }
p { margin: 6px 0 10px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
- button { border: 0; background: transparent; padding: 0; color: #be123c; font: inherit; font-weight: 600; cursor: pointer; }
+ button { border: 0; background: transparent; padding: 0; color: var(--app-error-text, #be123c); font: inherit; font-weight: 600; cursor: pointer; }
}
.report-expand-button {
align-self: flex-start;
- border: 1px solid #cbd5e1;
+ border: 1px solid var(--app-border-control, #cbd5e1);
border-radius: 6px;
padding: 4px 10px;
- background: #fff;
- color: #334155;
+ background: var(--app-surface, #fff);
+ color: var(--app-text-control, #334155);
font: inherit;
font-size: 12px;
cursor: pointer;
- &:hover { background: #f1f5f9; }
+ &:hover { background: var(--app-surface-soft, #f1f5f9); }
&:focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }
}
:host ::ng-deep .report-markdown-body {
@@ -4057,35 +4062,35 @@ $stream-row-height: 25px;
overflow-wrap: anywhere;
word-break: normal;
> div { margin: 3px 0; }
- h1, h2, h3 { margin: 12px 0 6px; font-size: 14px; color: #0f172a; }
+ h1, h2, h3 { margin: 12px 0 6px; font-size: 14px; color: var(--app-text-strong, #0f172a); }
ul, ol { padding-left: 22px; margin: 8px 0; }
li { margin: 4px 0; }
- pre { overflow: auto; white-space: pre-wrap; padding: 10px; border-radius: 7px; background: #e2e8f0; }
+ pre { overflow: auto; white-space: pre-wrap; padding: 10px; border-radius: 7px; background: var(--app-surface-raised, #e2e8f0); }
table { width: 100%; border-collapse: collapse; }
- th, td { padding: 7px 9px; border: 1px solid #cbd5e1; text-align: left; vertical-align: top; }
- th { background: #dbeafe; }
+ th, td { padding: 7px 9px; border: 1px solid var(--app-border-control, #cbd5e1); text-align: left; vertical-align: top; }
+ th { background: var(--app-primary-soft, #dbeafe); }
.md-para-gap { height: 8px; }
}
-.evidence-source { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 7px; border: 1px solid #cbd5e1; border-radius: 6px; background: #f1f5f9; color: #475569; font-size: 11px; cursor: pointer; }
+.evidence-source { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 2px 7px; border: 1px solid var(--app-border-control, #cbd5e1); border-radius: 6px; background: var(--app-surface-soft, #f1f5f9); color: var(--app-text-secondary, #475569); font-size: 11px; cursor: pointer; }
.plugin-tool-card {
.action-card-header { flex-wrap: wrap; }
- .plugin-call-kind { font-size: 10px; color: #64748b; }
+ .plugin-call-kind { font-size: 10px; color: var(--app-text-muted, #64748b); }
.plugin-result { white-space: pre-wrap; overflow-wrap: anywhere; }
&.is-auxiliary:not(.is-expanded):not(.is-failed):not(.is-running) {
- background: #f8fafc;
- border-color: #e2e8f0;
+ background: var(--app-surface-soft, #f8fafc);
+ border-color: var(--app-border, #e2e8f0);
box-shadow: none;
}
}
.stream-logs-content {padding-bottom:24px}
-.exploration-message {padding:12px 16px;border:1px solid #e2e8f0;border-radius:12px;margin:14px 0;background:#fff;color:#334155;font-size:13px;line-height:1.7}
-.exploration-message.user {background:#eff6ff;border-color:#dbeafe;margin-left:28px}.exploration-message.system {color:#64748b;background:#f8fafc;font-size:12px}
-.exploration-message small {color:#64748b;font-size:11px}.exploration-message p {white-space:pre-wrap;overflow-wrap:anywhere;margin:6px 0}.exploration-message button {font:inherit;padding:4px 10px;border:1px solid #cbd5e1;border-radius:6px;background:#fff;color:#334155;cursor:pointer}
+.exploration-message {padding:12px 16px;border:1px solid var(--app-border, #e2e8f0);border-radius:12px;margin:14px 0;background:var(--app-surface, #fff);color:var(--app-text-control, #334155);font-size:13px;line-height:1.7}
+.exploration-message.user {background:var(--app-primary-soft, #eff6ff);border-color:var(--app-primary-border, #dbeafe);margin-left:28px}.exploration-message.system {color:var(--app-text-muted, #64748b);background:var(--app-surface-soft, #f8fafc);font-size:12px}
+.exploration-message small {color:var(--app-text-muted, #64748b);font-size:11px}.exploration-message p {white-space:pre-wrap;overflow-wrap:anywhere;margin:6px 0}.exploration-message button {font:inherit;padding:4px 10px;border:1px solid var(--app-border-control, #cbd5e1);border-radius:6px;background:var(--app-surface, #fff);color:var(--app-text-control, #334155);cursor:pointer}
-.exploration-message video {width:100%;max-height:300px;background:#0f172a}.exploration-message summary {cursor:pointer}.exploration-message .error {color:#b91c1c}
-.exploration-message.assistant {border-left:3px solid #818cf8;background:#fafaff}
-.exploration-message.question {border:1px solid #f4c97a;border-left:3px solid #f59e0b;background:#fffbeb}
-.exploration-message small {display:flex;align-items:center;gap:6px;font-size:11px;margin-bottom:8px}.exploration-message small .material-symbols-outlined {font-size:16px;color:#6366f1}.exploration-message small b {margin-left:auto;color:#92400e;background:#fef3c7;border-radius:12px;padding:2px 8px}
+.exploration-message video {width:100%;max-height:300px;background:#0f172a}.exploration-message summary {cursor:pointer}.exploration-message .error {color:var(--app-error-text, #b91c1c)}
+.exploration-message.assistant {border-left:3px solid var(--app-exploration-border, #818cf8);background:var(--app-exploration-soft, #fafaff)}
+.exploration-message.question {border:1px solid var(--app-warning-border, #f4c97a);border-left:3px solid #f59e0b;background:var(--app-warning-soft, #fffbeb)}
+.exploration-message small {display:flex;align-items:center;gap:6px;font-size:11px;margin-bottom:8px}.exploration-message small .material-symbols-outlined {font-size:16px;color:var(--app-exploration-text, #6366f1)}.exploration-message small b {margin-left:auto;color:var(--app-warning-text, #92400e);background:var(--app-warning-soft, #fef3c7);border-radius:12px;padding:2px 8px}
diff --git a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts
index 8c08d10..3cbbd90 100644
--- a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts
+++ b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts
@@ -3,7 +3,9 @@ import { mergeConversation } from '../../utils/exploration-timeline.util';
import { TaskIdCopyComponent } from '../task-id-copy/task-id-copy.component';
import { TaskHistoryComponent } from '../task-history/task-history.component';
import { LlmRequestStatusComponent } from '../llm-request-status/llm-request-status.component';
+import { TaskLifecycleComponent } from '../task-lifecycle/task-lifecycle.component';
import { ReportMarkdownComponent } from '../report-markdown/report-markdown.component';
+import { PluginToolResultComponent } from '../plugin-tool-result/plugin-tool-result.component';
import { AutomationPanelComponent } from '../automation-panel/automation-panel.component';
import { UiLabelPipe } from '../../utils/ui-label.pipe';
/**
@@ -248,7 +250,7 @@ export type { MarkdownSegment, MarkdownLine, NoteMilestone, ParsedNote, StreamRe
@Component({
selector: 'app-agent-stream',
standalone: true,
- imports: [ReportMarkdownComponent, LlmRequestStatusComponent, TaskIdCopyComponent, UiLabelPipe, CommonModule, FormsModule, OverlayModule, TaskHistoryComponent, AutomationPanelComponent, TestResultBadgeComponent, TestResultCardComponent, TaskBatchFilterComponent],
+ imports: [PluginToolResultComponent, ReportMarkdownComponent, LlmRequestStatusComponent, TaskLifecycleComponent, TaskIdCopyComponent, UiLabelPipe, CommonModule, FormsModule, OverlayModule, TaskHistoryComponent, AutomationPanelComponent, TestResultBadgeComponent, TestResultCardComponent, TaskBatchFilterComponent],
templateUrl: './agent-stream.component.html',
styleUrl: './agent-stream.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
@@ -504,12 +506,13 @@ export class AgentStreamComponent implements AfterViewInit {
});
public guidanceStatusLabel(event: GuidanceEvent): string {
- const terminal = ['completed', 'failed', 'cancelled', 'interrupted']
+ const terminal = ['completed', 'success', 'failed', 'cancelled', 'interrupted', 'skipped']
.includes(this.agentService.currentSession()?.status || '');
- if (event.status === 'pending') {
- return terminal ? '任务结束前未进入模型' : '已接收,正在打断当前模型步骤';
- }
- return terminal ? '模型已读取,未留下后续动作记录' : '模型已读取,等待动作记录';
+ if (event.status === 'unapplied' || (event.status === 'pending' && terminal)) return '任务结束,未记录消费';
+ if (event.status === 'unconfirmed' || (event.status === 'reading' && terminal)) return '任务结束,读取未确认';
+ if (event.status === 'reading') return '执行器正在读取';
+ if (event.status === 'pending') return '已接收,等待安全时点读取';
+ return terminal ? '执行器已读取,未留下后续动作记录' : '执行器已读取,等待动作记录';
}
public phases = computed(() => {
@@ -959,6 +962,11 @@ export class AgentStreamComponent implements AfterViewInit {
return 'completed';
}
+ public taskPhaseLabel(session: Session): string | null {
+ if (['completed', 'failed', 'cancelled', 'interrupted', 'skipped'].includes(this.getTaskStatus(session))) return null;
+ return ({ waiting_resources: '等待资源', paused: '已暂停', demonstrating: '人工示范中', analyzing_demo: '分析示范中' } as Record
)[session.phase || ''] || null;
+ }
+
/**
* Determine the device serial number for the session
*/
diff --git a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.html b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.html
index 4deadb0..fdf4769 100644
--- a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.html
+++ b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.html
@@ -20,6 +20,7 @@
@if (r.error) { }
下载 Word下载 Excel
步骤截图 ZIP
分段录屏 ZIP
diff --git a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.scss b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.scss
index 9e5e7eb..4e2a9c5 100644
--- a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.scss
+++ b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.scss
@@ -1,60 +1,60 @@
-:host { display:block;min-width:0;color:#1e293b;font-size:13px;line-height:1.65; }
+:host { display:block;min-width:0;color:var(--app-text-control, #1e293b);font-size:13px;line-height:1.65; }
.back-to-batch{margin-bottom:12px;max-width:100%;text-align:left;overflow-wrap:anywhere}
-.job.selected{background:#eff6ff;border-radius:8px;padding-left:8px;padding-right:8px}.job.selected button{color:#1d4ed8;border-color:#93c5fd;background:#fff}
-.tabs {display:flex;gap:4px;padding:4px;margin-bottom:18px;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:20px;
- button {flex:1;padding:7px 8px;border:0;border-radius:16px;color:#475569;background:transparent;white-space:nowrap;
- &.selected {color:#2563eb;background:#fff;box-shadow:0 1px 4px #0f172a12;} }
+.job.selected{background:var(--app-primary-soft, #eff6ff);border-radius:8px;padding-left:8px;padding-right:8px}.job.selected button{color:var(--app-primary-text, #1d4ed8);border-color:var(--app-primary-border, #93c5fd);background:var(--app-surface, #fff)}
+.tabs {display:flex;gap:4px;padding:4px;margin-bottom:18px;background:var(--app-surface-soft, #f1f5f9);border:1px solid var(--app-border, #e2e8f0);border-radius:20px;
+ button {flex:1;padding:7px 8px;border:0;border-radius:16px;color:var(--app-text-secondary, #475569);background:transparent;white-space:nowrap;
+ &.selected {color:var(--app-primary-text, #2563eb);background:var(--app-surface, #fff);box-shadow:0 1px 4px #0f172a12;} }
}
-section {padding:16px;margin-bottom:14px;border:1px solid #e2e8f0;border-radius:12px;background:#fff;&:last-child{margin-bottom:0;}}
-h3 {color:#0f172a;font-size:14px;font-weight:600;margin:0 0 8px;}
-p {margin:8px 0 12px;color:#475569;overflow-wrap:anywhere;}strong{font-weight:600;}.muted,small{color:#64748b;font-size:12px;}
-.error,.notice{padding:10px 12px;border-radius:8px;margin:0 0 14px;}.error{color:#b91c1c;background:#fff1f2;border:1px solid #fecdd3;}.notice{color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;}
-button,a,.upload {display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:34px;border:1px solid #cbd5e1;border-radius:8px;padding:7px 12px;background:#fff;color:#334155;text-decoration:none;cursor:pointer;font:inherit;font-weight:500;line-height:1.5;transition:background .15s,border-color .15s;
- &:hover{background:#f8fafc;border-color:#94a3b8;}&:focus-visible{outline:2px solid #3b82f6;outline-offset:2px;}&:disabled{color:#94a3b8;background:#f8fafc;border-color:#e2e8f0;cursor:not-allowed;}}
-a{color:#2563eb;border-color:#bfdbfe;background:#eff6ff;}button.primary{background:#2563eb;color:white;border-color:#2563eb;width:100%;margin-top:10px;}button.primary:hover{background:#1d4ed8;}button.primary:disabled{background:#e2e8f0;color:#64748b;border-color:#e2e8f0;}
-.actions,.fields,.format-options{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}.actions{margin:12px 0 4px;}.fields>*{flex:1;min-width:130px;}.format-options{margin:12px 0;}.format-options label{flex:1;min-width:120px;margin:0;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;background:#f8fafc;}
-label{display:block;margin:12px 0;color:#334155;font-weight:500;}
-input:not([type=checkbox]):not([type=file]),select,textarea{box-sizing:border-box;display:block;width:100%;margin-top:6px;border:1px solid #cbd5e1;border-radius:8px;padding:9px 10px;background:#fff;color:#1e293b;font:inherit;font-weight:400;&:focus{outline:2px solid #bfdbfe;border-color:#3b82f6;}}
-input[type=checkbox]{width:15px;height:15px;accent-color:#2563eb;vertical-align:-3px;margin-right:6px;}
+section {padding:16px;margin-bottom:14px;border:1px solid var(--app-border, #e2e8f0);border-radius:12px;background:var(--app-surface, #fff);&:last-child{margin-bottom:0;}}
+h3 {color:var(--app-text-strong, #0f172a);font-size:14px;font-weight:600;margin:0 0 8px;}
+p {margin:8px 0 12px;color:var(--app-text-secondary, #475569);overflow-wrap:anywhere;}strong{font-weight:600;}.muted,small{color:var(--app-text-muted, #64748b);font-size:12px;}
+.error,.notice{padding:10px 12px;border-radius:8px;margin:0 0 14px;}.error{color:var(--app-error-text, #b91c1c);background:var(--app-error-soft, #fff1f2);border:1px solid var(--app-error-border, #fecdd3);}.notice{color:var(--app-success-text, #047857);background:var(--app-success-soft, #ecfdf5);border:1px solid var(--app-success-border, #a7f3d0);}
+button,a,.upload {display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:34px;border:1px solid var(--app-border-control, #cbd5e1);border-radius:8px;padding:7px 12px;background:var(--app-surface, #fff);color:var(--app-text-control, #334155);text-decoration:none;cursor:pointer;font:inherit;font-weight:500;line-height:1.5;transition:background .15s,border-color .15s;
+ &:hover{background:var(--app-surface-soft, #f8fafc);border-color:var(--app-border-control, #94a3b8);}&:focus-visible{outline:2px solid #3b82f6;outline-offset:2px;}&:disabled{color:var(--app-text-faint, #94a3b8);background:var(--app-surface-soft, #f8fafc);border-color:var(--app-border, #e2e8f0);cursor:not-allowed;}}
+a{color:var(--app-primary-text, #2563eb);border-color:var(--app-primary-border, #bfdbfe);background:var(--app-primary-soft, #eff6ff);}button.primary{background:#2563eb;color:white;border-color:#2563eb;width:100%;margin-top:10px;}button.primary:hover{background:#1d4ed8;}button.primary:disabled{background:var(--app-surface-raised, #e2e8f0);color:var(--app-text-muted, #64748b);border-color:var(--app-border, #e2e8f0);}
+.actions,.fields,.format-options{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}.actions{margin:12px 0 4px;}.fields>*{flex:1;min-width:130px;}.format-options{margin:12px 0;}.format-options label{flex:1;min-width:120px;margin:0;padding:10px 12px;border:1px solid var(--app-border, #e2e8f0);border-radius:8px;background:var(--app-surface-soft, #f8fafc);}
+label{display:block;margin:12px 0;color:var(--app-text-control, #334155);font-weight:500;}
+input:not([type=checkbox]):not([type=file]),select,textarea{box-sizing:border-box;display:block;width:100%;margin-top:6px;border:1px solid var(--app-border-control, #cbd5e1);border-radius:8px;padding:9px 10px;background:var(--app-surface, #fff);color:var(--app-text-control, #1e293b);font:inherit;font-weight:400;&:focus{outline:2px solid var(--app-primary-border, #bfdbfe);border-color:#3b82f6;}}
+input[type=checkbox]{width:15px;height:15px;accent-color:var(--app-primary-text, #2563eb);vertical-align:-3px;margin-right:6px;}
.upload{position:relative;margin:0;color:#fff;background:#2563eb;border-color:#2563eb;}.upload:hover{background:#1d4ed8;border-color:#1d4ed8;}.upload input{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;}
-.device{background:#f8fafc;padding:12px 16px;}.device label{margin:0;}
-.selection-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:12px 0;color:#475569;}
+.device{background:var(--app-surface-soft, #f8fafc);padding:12px 16px;}.device label{margin:0;}
+.selection-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:12px 0;color:var(--app-text-secondary, #475569);}
.selection-column{flex:0 0 18px;text-align:center;}.selection-column input{margin:0;}
-.case-preview-list{max-height:320px;overflow:auto;border:1px solid #e2e8f0;border-radius:8px;margin-bottom:14px;}
-.case-preview{display:flex;align-items:flex-start;gap:10px;padding:12px;border-bottom:1px solid #e2e8f0;&:last-child{border-bottom:0;}&.unselected{background:#f8fafc;color:#64748b;}}
-.case-preview-content{min-width:0;flex:1;strong{display:block;overflow-wrap:anywhere;}small{display:block;}details{margin:5px 0 0;}summary{padding:0;font-size:12px;color:#2563eb;}pre{max-height:160px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;background:#f8fafc;border-radius:6px;padding:8px;font-size:11px;}}
+.case-preview-list{max-height:320px;overflow:auto;border:1px solid var(--app-border, #e2e8f0);border-radius:8px;margin-bottom:14px;}
+.case-preview{display:flex;align-items:flex-start;gap:10px;padding:12px;border-bottom:1px solid var(--app-border, #e2e8f0);&:last-child{border-bottom:0;}&.unselected{background:var(--app-surface-soft, #f8fafc);color:var(--app-text-muted, #64748b);}}
+.case-preview-content{min-width:0;flex:1;strong{display:block;overflow-wrap:anywhere;}small{display:block;}details{margin:5px 0 0;}summary{padding:0;font-size:12px;color:var(--app-primary-text, #2563eb);}pre{max-height:160px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;background:var(--app-surface-soft, #f8fafc);border-radius:6px;padding:8px;font-size:11px;}}
.case-goal{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;white-space:pre-wrap;margin:5px 0;font-size:12px;}
-.execution-summary{padding:9px 12px;border-radius:8px;background:#eff6ff;color:#1d4ed8;font-weight:500;}
-.record{display:flex;width:100%;text-align:left;align-items:flex-start;flex-direction:column;gap:4px;margin:8px 0 0;padding:12px;overflow-wrap:anywhere;}.record span{color:#64748b;font-size:12px;font-weight:400;}.record.selected{border-color:#93c5fd;background:#eff6ff;}.record.selected strong{color:#1d4ed8;}
-.batch-record{position:relative;display:flex;gap:8px;align-items:stretch;margin-top:8px;}.batch-record>app-favorite-button{position:absolute;left:9px;top:9px;z-index:1}.batch-record .record{padding-left:44px;flex:1;min-width:0;width:auto;margin:0;}.batch-record .delete-batch{flex:0 0 40px;min-width:40px;padding:0;color:#b91c1c;}.batch-record .delete-batch:hover:not(:disabled){background:#fff1f2;border-color:#fda4af;}.batch-record .delete-batch .material-symbols-outlined{font-size:19px;}
-.archive-progress{margin:12px 0;padding:11px 13px;border:1px solid #bfdbfe;border-radius:10px;background:#f8fbff;color:#1e40af;}
+.execution-summary{padding:9px 12px;border-radius:8px;background:var(--app-primary-soft, #eff6ff);color:var(--app-primary-text, #1d4ed8);font-weight:500;}
+.record{display:flex;width:100%;text-align:left;align-items:flex-start;flex-direction:column;gap:4px;margin:8px 0 0;padding:12px;overflow-wrap:anywhere;}.record span{color:var(--app-text-muted, #64748b);font-size:12px;font-weight:400;}.record.selected{border-color:var(--app-primary-border, #93c5fd);background:var(--app-primary-soft, #eff6ff);}.record.selected strong{color:var(--app-primary-text, #1d4ed8);}
+.batch-record{position:relative;display:flex;gap:8px;align-items:stretch;margin-top:8px;}.batch-record>app-favorite-button{position:absolute;left:9px;top:9px;z-index:1}.batch-record .record{padding-left:44px;flex:1;min-width:0;width:auto;margin:0;}.batch-record .delete-batch{flex:0 0 40px;min-width:40px;padding:0;color:var(--app-error-text, #b91c1c);}.batch-record .delete-batch:hover:not(:disabled){background:var(--app-error-soft, #fff1f2);border-color:var(--app-error-border, #fda4af);}.batch-record .delete-batch .material-symbols-outlined{font-size:19px;}
+.archive-progress{margin:12px 0;padding:11px 13px;border:1px solid var(--app-primary-border, #bfdbfe);border-radius:10px;background:var(--app-primary-soft, #f8fbff);color:var(--app-primary-text, #1e40af);}
.archive-progress-heading{display:flex;align-items:center;gap:8px;}
-.archive-progress p{margin:7px 0 0;font-size:12px;color:#475569;}
-.archive-progress.archive-failed{border-color:#fecdd3;background:#fff1f2;color:#be123c;}
-.archive-track{height:7px;margin-top:10px;border-radius:999px;background:#dbeafe;overflow:hidden;}
+.archive-progress p{margin:7px 0 0;font-size:12px;color:var(--app-text-secondary, #475569);}
+.archive-progress.archive-failed{border-color:var(--app-error-border, #fecdd3);background:var(--app-error-soft, #fff1f2);color:var(--app-error-text, #be123c);}
+.archive-track{height:7px;margin-top:10px;border-radius:999px;background:var(--app-primary-soft, #dbeafe);overflow:hidden;}
.archive-track span{display:block;height:100%;border-radius:inherit;background:#3b82f6;transition:width .3s ease;}
-.archive-spinner{width:13px;height:13px;border:2px solid #bfdbfe;border-top-color:#2563eb;border-radius:50%;animation:archive-spin .8s linear infinite;}
+.archive-spinner{width:13px;height:13px;border:2px solid var(--app-primary-border, #bfdbfe);border-top-color:#2563eb;border-radius:50%;animation:archive-spin .8s linear infinite;}
@keyframes archive-spin{to{transform:rotate(360deg);}}
-.code{font-family:Consolas,monospace;font-size:12px;line-height:1.6;resize:vertical;background:#f8fafc;}.table-wrap{max-height:260px;overflow:auto;border:1px solid #e2e8f0;border-radius:8px;margin-bottom:14px;}table{width:100%;border-collapse:collapse;}th,td{padding:9px 10px;border-bottom:1px solid #e2e8f0;text-align:left;overflow-wrap:anywhere;}th{background:#f8fafc;color:#475569;font-weight:500;}
-.preview{white-space:pre-wrap;max-height:400px;overflow:auto;font:inherit;color:#334155;background:#f8fafc;border-radius:8px;padding:12px;border:1px solid #e2e8f0;}
-.job{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:10px 0;border-bottom:1px solid #f1f5f9;}.job:last-child{border-bottom:0;}.job button{flex:1;justify-content:flex-start;text-align:left;min-width:150px;}.job span{font-size:12px;background:#f1f5f9;color:#475569;border-radius:5px;padding:3px 7px;}.job small{width:100%;}details{margin:14px 0;}summary{color:#475569;cursor:pointer;padding:8px 0;}
-.task-guidance{margin-top:16px;padding:14px;border:1px solid #bfdbfe;border-radius:10px;background:#f8fbff;}
+.code{font-family:Consolas,monospace;font-size:12px;line-height:1.6;resize:vertical;background:var(--app-surface-soft, #f8fafc);}.table-wrap{max-height:260px;overflow:auto;border:1px solid var(--app-border, #e2e8f0);border-radius:8px;margin-bottom:14px;}table{width:100%;border-collapse:collapse;}th,td{padding:9px 10px;border-bottom:1px solid var(--app-border, #e2e8f0);text-align:left;overflow-wrap:anywhere;}th{background:var(--app-surface-soft, #f8fafc);color:var(--app-text-secondary, #475569);font-weight:500;}
+.preview{white-space:pre-wrap;max-height:400px;overflow:auto;font:inherit;color:var(--app-text-control, #334155);background:var(--app-surface-soft, #f8fafc);border-radius:8px;padding:12px;border:1px solid var(--app-border, #e2e8f0);}
+.job{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:10px 0;border-bottom:1px solid var(--app-border, #f1f5f9);}.job:last-child{border-bottom:0;}.job button{flex:1;justify-content:flex-start;text-align:left;min-width:150px;}.job span{font-size:12px;background:var(--app-surface-soft, #f1f5f9);color:var(--app-text-secondary, #475569);border-radius:5px;padding:3px 7px;}.job small{width:100%;}details{margin:14px 0;}summary{color:var(--app-text-secondary, #475569);cursor:pointer;padding:8px 0;}
+.task-guidance{margin-top:16px;padding:14px;border:1px solid var(--app-primary-border, #bfdbfe);border-radius:10px;background:var(--app-primary-soft, #f8fbff);}
.task-guidance label{margin:8px 0 4px;}
-.report-overview{padding:14px;border:1px solid #dbeafe;border-radius:10px;background:#f8fbff;}
+.report-overview{padding:14px;border:1px solid var(--app-primary-border, #dbeafe);border-radius:10px;background:var(--app-primary-soft, #f8fbff);}
.report-heading{display:flex;align-items:flex-start;gap:8px;}.report-heading .report-title{flex:1;min-width:0;}
-.report-title{display:block;font-size:14px;line-height:1.5;color:#0f172a;overflow-wrap:anywhere;}
-.report-badges{display:flex;gap:7px;flex-wrap:wrap;margin-top:9px;span{padding:3px 8px;border:1px solid #bfdbfe;border-radius:20px;background:#eff6ff;color:#1e40af;font-size:12px;&.failed{background:#fff1f2;border-color:#fecdd3;color:#be123c;}}}
-.report-error{margin-top:11px;padding:10px 12px;background:#fff1f2;border-left:3px solid #e11d48;border-radius:6px;color:#9f1239;strong{font-size:12px;}p{margin:4px 0 0;color:#9f1239;white-space:pre-wrap;}}
-.report-meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin:12px 0;div{min-width:0;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;}strong{font-size:12px;color:#475569;}p{margin:5px 0 0;white-space:pre-wrap;color:#1e293b;}}
-.report-content{margin-top:14px;padding:14px;border:1px solid #e2e8f0;border-radius:10px;background:#fff;h3{margin-bottom:12px;}}
-.report-body{overflow-x:auto;overflow-wrap:anywhere;color:#1e293b;line-height:1.75;}
+.report-title{display:block;font-size:14px;line-height:1.5;color:var(--app-text-strong, #0f172a);overflow-wrap:anywhere;}
+.report-badges{display:flex;gap:7px;flex-wrap:wrap;margin-top:9px;span{padding:3px 8px;border:1px solid var(--app-primary-border, #bfdbfe);border-radius:20px;background:var(--app-primary-soft, #eff6ff);color:var(--app-primary-text, #1e40af);font-size:12px;&.failed{background:var(--app-error-soft, #fff1f2);border-color:var(--app-error-border, #fecdd3);color:var(--app-error-text, #be123c);}}}
+.report-error{margin-top:11px;padding:10px 12px;background:var(--app-error-soft, #fff1f2);border-left:3px solid #e11d48;border-radius:6px;color:var(--app-error-text, #9f1239);strong{font-size:12px;}p{margin:4px 0 0;color:var(--app-error-text, #9f1239);white-space:pre-wrap;}}
+.report-meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin:12px 0;div{min-width:0;padding:10px 12px;border:1px solid var(--app-border, #e2e8f0);border-radius:8px;background:var(--app-surface, #fff);}strong{font-size:12px;color:var(--app-text-secondary, #475569);}p{margin:5px 0 0;white-space:pre-wrap;color:var(--app-text-control, #1e293b);}}
+.report-content{margin-top:14px;padding:14px;border:1px solid var(--app-border, #e2e8f0);border-radius:10px;background:var(--app-surface, #fff);h3{margin-bottom:12px;}}
+.report-body{overflow-x:auto;overflow-wrap:anywhere;color:var(--app-text-control, #1e293b);line-height:1.75;}
:host ::ng-deep .report-body, :host ::ng-deep .report-markdown-body {
- h1,h2,h3,h4{margin:18px 0 8px;color:#0f172a;line-height:1.4;}h1{font-size:18px;}h2{font-size:16px;}h3,h4{font-size:14px;}
+ h1,h2,h3,h4{margin:18px 0 8px;color:var(--app-text-strong, #0f172a);line-height:1.4;}h1{font-size:18px;}h2{font-size:16px;}h3,h4{font-size:14px;}
> div{margin:3px 0;}
ul,ol{margin:8px 0;padding-left:24px;}li{margin:4px 0;}
- blockquote{margin:10px 0;padding:8px 12px;border-left:3px solid #93c5fd;background:#f8fafc;}
- pre{max-width:100%;overflow:auto;padding:10px 12px;background:#f1f5f9;border-radius:6px;white-space:pre-wrap;}
- code{font-family:Consolas,monospace;background:#f1f5f9;padding:1px 3px;border-radius:3px;}
- table{width:100%;border-collapse:collapse;font-size:12px;}th,td{padding:7px 9px;border:1px solid #e2e8f0;vertical-align:top;}th{background:#eff6ff;color:#1e3a8a;}
+ blockquote{margin:10px 0;padding:8px 12px;border-left:3px solid var(--app-primary-border, #93c5fd);background:var(--app-surface-soft, #f8fafc);}
+ pre{max-width:100%;overflow:auto;padding:10px 12px;background:var(--app-surface-soft, #f1f5f9);border-radius:6px;white-space:pre-wrap;}
+ code{font-family:Consolas,monospace;background:var(--app-surface-soft, #f1f5f9);padding:1px 3px;border-radius:3px;}
+ table{width:100%;border-collapse:collapse;font-size:12px;}th,td{padding:7px 9px;border:1px solid var(--app-border, #e2e8f0);vertical-align:top;}th{background:var(--app-primary-soft, #eff6ff);color:var(--app-primary-text, #1e3a8a);}
.md-para-gap{height:8px;}
}
diff --git a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.ts b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.ts
index a548b54..c260e79 100644
--- a/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.ts
+++ b/apps/showcase_ui/src/app/components/automation-panel/automation-panel.component.ts
@@ -11,6 +11,7 @@ import { TestResultBadgeComponent } from '../test-result-badge/test-result-badge
import { TestResultCardComponent } from '../test-result-badge/test-result-card.component';
import { CaseReviewResponse } from '../../core/models/session.model';
import { TaskPluginsComponent, TaskPluginOptions } from '../task-plugins/task-plugins.component';
+import { EvidenceSourcesComponent } from '../evidence-sources/evidence-sources.component';
interface ArchiveProgress {
status: 'idle' | 'preparing' | 'ready' | 'failed';
@@ -23,7 +24,7 @@ interface ArchiveProgress {
}
@Component({
- selector: 'app-automation-panel', standalone: true, imports: [FavoriteButtonComponent, ReportMarkdownComponent, CommonModule, FormsModule, TestResultBadgeComponent, TestResultCardComponent, TaskPluginsComponent],
+ selector: 'app-automation-panel', standalone: true, imports: [FavoriteButtonComponent, ReportMarkdownComponent, CommonModule, FormsModule, TestResultBadgeComponent, TestResultCardComponent, TaskPluginsComponent, EvidenceSourcesComponent],
templateUrl: './automation-panel.component.html', styleUrl: './automation-panel.component.scss'
})
export class AutomationPanelComponent {
diff --git a/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss b/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss
index b69b2b0..84b2a96 100644
--- a/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss
+++ b/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss
@@ -15,9 +15,9 @@
*/
$border-color: rgba(0, 0, 0, 0.08);
-$text-primary: #1f1f1f;
-$text-secondary: #71717a;
-$text-muted: #a1a1aa;
+$text-primary: var(--app-text-strong, #1f1f1f);
+$text-secondary: var(--app-text-muted, #71717a);
+$text-muted: var(--app-text-faint, #a1a1aa);
:host {
display: flex;
@@ -32,7 +32,7 @@ $text-muted: #a1a1aa;
display: flex;
flex-direction: column;
height: 100%;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
box-sizing: border-box;
.chat-header {
@@ -43,23 +43,23 @@ $text-muted: #a1a1aa;
height: 60px;
padding: 0 20px;
border-bottom: 1px solid $border-color;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
box-sizing: border-box;
.tab-selector-bar {
display: flex;
height: 36px;
box-sizing: border-box;
- background: rgba(241, 245, 249, 0.72);
+ background: rgb(var(--app-surface-soft-rgb, 241 245 249) / 0.72);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
- border: 1px solid rgba(226, 232, 240, 0.85);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.85);
border-radius: 18px;
padding: 3px;
gap: 3px;
align-items: center;
flex-shrink: 0;
- box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgba(255, 255, 255, 0.8);
+ box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.8);
.tab-selector-btn {
display: inline-flex;
@@ -74,7 +74,7 @@ $text-muted: #a1a1aa;
border-radius: 15px;
font-size: 13px;
font-weight: 600;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
cursor: pointer;
line-height: 1;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
@@ -90,13 +90,13 @@ $text-muted: #a1a1aa;
}
&:hover:not(.active) {
- color: #0f172a;
- background: rgba(255, 255, 255, 0.7);
+ color: var(--app-text-strong, #0f172a);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.7);
}
&.active {
- background: #ffffff;
- color: #2563eb;
+ background: var(--app-surface, #ffffff);
+ color: var(--app-primary-text, #2563eb);
font-weight: 700;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.02);
}
@@ -116,7 +116,7 @@ $text-muted: #a1a1aa;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.92), rgba(26, 115, 232, 0.85));
backdrop-filter: blur(14px) saturate(180%);
-webkit-backdrop-filter: blur(14px) saturate(180%);
- border: 1px solid rgba(255, 255, 255, 0.35);
+ border: 1px solid rgb(var(--app-border-rgb, 255 255 255) / 0.35);
border-radius: 16px;
font-size: 13.5px;
font-weight: 600;
@@ -127,7 +127,7 @@ $text-muted: #a1a1aa;
gap: 5px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
margin-right: 8px;
- box-shadow: 0 4px 14px rgba(37, 99, 235, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.45);
+ box-shadow: 0 4px 14px rgba(37, 99, 235, 0.3), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.45);
.material-symbols-outlined {
font-size: 16px;
@@ -136,8 +136,8 @@ $text-muted: #a1a1aa;
&:hover {
background: linear-gradient(135deg, rgba(37, 99, 235, 1), rgba(26, 115, 232, 0.95));
- border-color: rgba(255, 255, 255, 0.6);
- box-shadow: 0 6px 18px rgba(37, 99, 235, 0.42), inset 0 1px 1px rgba(255, 255, 255, 0.6);
+ border-color: rgb(var(--app-border-rgb, 255 255 255) / 0.6);
+ box-shadow: 0 6px 18px rgba(37, 99, 235, 0.42), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.6);
transform: translateY(-0.5px);
}
@@ -151,17 +151,17 @@ $text-muted: #a1a1aa;
height: 32px;
padding: 0;
box-sizing: border-box;
- background: rgba(255, 255, 255, 0.72);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.72);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
- border: 1px solid rgba(203, 213, 225, 0.85);
+ border: 1px solid rgb(var(--app-border-control-rgb, 203 213 225) / 0.85);
border-radius: 50%;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
- box-shadow: 0 2px 6px -1px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgba(255, 255, 255, 0.9);
+ box-shadow: 0 2px 6px -1px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
.material-symbols-outlined {
@@ -170,9 +170,9 @@ $text-muted: #a1a1aa;
}
&:hover:not(:disabled) {
- background: #fef2f2;
- color: #ef4444;
- border-color: #fca5a5;
+ background: var(--app-error-soft, #fef2f2);
+ color: var(--app-error-text, #ef4444);
+ border-color: var(--app-error-border, #fca5a5);
box-shadow: 0 3px 10px rgba(239, 68, 68, 0.15);
transform: scale(1.05);
}
@@ -192,7 +192,7 @@ $text-muted: #a1a1aa;
display: flex;
flex-direction: column;
gap: 8px;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
&::-webkit-scrollbar {
width: 5px;
@@ -205,7 +205,7 @@ $text-muted: #a1a1aa;
.queue-section-header {
font-size: 11.5px;
font-weight: 700;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
margin: 6px 0 2px;
display: flex;
align-items: center;
@@ -221,18 +221,18 @@ $text-muted: #a1a1aa;
display: flex;
align-items: center;
justify-content: center;
- background: #f1f5f9;
- border: 1px dashed #cbd5e1;
+ background: var(--app-surface-soft, #f1f5f9);
+ border: 1px dashed var(--app-border-control, #cbd5e1);
border-radius: 8px;
padding: 12px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
font-size: 12px;
text-align: center;
}
.history-section {
margin-top: 12px;
- border-top: 1px solid rgba(226, 232, 240, 0.8);
+ border-top: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.8);
padding-top: 12px;
&[open] .history-summary {
@@ -242,7 +242,7 @@ $text-muted: #a1a1aa;
.history-summary {
font-size: 11.5px;
font-weight: 700;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
cursor: pointer;
outline: none;
user-select: none;
@@ -265,8 +265,8 @@ $text-muted: #a1a1aa;
}
&:hover {
- background: #f1f5f9;
- color: #1e293b;
+ background: var(--app-surface-soft, #f1f5f9);
+ color: var(--app-text-control, #1e293b);
}
}
@@ -278,8 +278,8 @@ $text-muted: #a1a1aa;
}
.task-card {
- background: #ffffff;
- border: 1px solid rgba(226, 232, 240, 0.8);
+ background: var(--app-surface, #ffffff);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.8);
border-radius: 10px;
padding: 10px 12px;
display: flex;
@@ -292,7 +292,7 @@ $text-muted: #a1a1aa;
&:hover {
transform: translateY(-1px);
- border-color: rgba(203, 213, 225, 1);
+ border-color: rgb(var(--app-border-control-rgb, 203 213 225) / 1);
box-shadow: 0 3px 8px rgba(148, 163, 184, 0.08);
.task-card-header .task-card-header-actions {
@@ -314,7 +314,7 @@ $text-muted: #a1a1aa;
&.selected {
border-color: #3b82f6;
box-shadow: 0 0 0 1px #3b82f6, 0 4px 12px rgba(59, 130, 246, 0.08);
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
}
&.running {
@@ -336,7 +336,7 @@ $text-muted: #a1a1aa;
min-width: 0;
.task-status-group { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
- .task-origin-badge { display: inline-flex; align-items: center; box-sizing: border-box; height: 20px; padding: 0 6px; border: 1px solid #e2e8f0; border-radius: 5px; background: #f8fafc; color: #64748b; font-size: 11px; line-height: 14px; white-space: nowrap; }
+ .task-origin-badge { display: inline-flex; align-items: center; box-sizing: border-box; height: 20px; padding: 0 6px; border: 1px solid var(--app-border, #e2e8f0); border-radius: 5px; background: var(--app-surface-soft, #f8fafc); color: var(--app-text-muted, #64748b); font-size: 11px; line-height: 14px; white-space: nowrap; }
.task-badge {
flex-shrink: 0;
@@ -350,9 +350,9 @@ $text-muted: #a1a1aa;
padding: 0 5px;
border-radius: 4px;
letter-spacing: 0.05em;
- border: 1px solid #e2e8f0;
- background: #fafafa;
- color: #64748b;
+ border: 1px solid var(--app-border, #e2e8f0);
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-text-muted, #64748b);
&::before {
content: '';
@@ -364,48 +364,48 @@ $text-muted: #a1a1aa;
}
&.running {
- background: #fafafa;
- color: #1f1f1f;
- border-color: #1f1f1f;
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-text-strong, #1f1f1f);
+ border-color: var(--app-border-control, #1f1f1f);
&::before {
background-color: #1f1f1f;
animation: pulse-black-dot-small 1.5s infinite ease-in-out;
}
}
&.paused {
- background: #fff7ed;
- color: #c2410c;
- border-color: #fdba74;
+ background: var(--app-warning-soft, #fff7ed);
+ color: var(--app-warning-text, #c2410c);
+ border-color: var(--app-warning-border, #fdba74);
&::before { background-color: #f97316; }
}
&.completed {
- background: #fafafa;
- color: #1f1f1f;
- border-color: #e2e8f0;
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-text-strong, #1f1f1f);
+ border-color: var(--app-border, #e2e8f0);
&::before {
background-color: #1f1f1f;
}
}
&.pending {
- background: #fafafa;
- color: #64748b;
- border-color: #e2e8f0;
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-text-muted, #64748b);
+ border-color: var(--app-border, #e2e8f0);
&::before {
background-color: #94a3b8;
}
}
&.failed {
- background: #fafafa;
- color: #ef4444;
- border-color: #fee2e2;
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-error-text, #ef4444);
+ border-color: var(--app-error-border, #fee2e2);
&::before {
background-color: #ef4444;
}
}
&.cancelled {
- background: #fafafa;
- color: #d97706;
- border-color: #fef3c7;
+ background: var(--app-surface-soft, #fafafa);
+ color: var(--app-warning-text, #d97706);
+ border-color: var(--app-warning-border, #fef3c7);
&::before {
background-color: #f59e0b;
}
@@ -433,7 +433,7 @@ $text-muted: #a1a1aa;
min-width: 0;
flex-shrink: 1;
font-size: 11px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
line-height: 1;
cursor: default;
transition: color 0.15s ease, opacity 0.15s ease;
@@ -443,7 +443,7 @@ $text-muted: #a1a1aa;
width: 12.5px;
height: 12.5px;
line-height: 12.5px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
opacity: 0.75;
flex-shrink: 0;
}
@@ -458,9 +458,9 @@ $text-muted: #a1a1aa;
}
&:hover {
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
.device-icon {
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
opacity: 1;
}
}
@@ -477,7 +477,7 @@ $text-muted: #a1a1aa;
.task-time {
font-size: 11.5px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
white-space: nowrap;
flex-shrink: 0;
line-height: 1;
@@ -495,7 +495,7 @@ $text-muted: #a1a1aa;
border: none;
background: transparent;
border-radius: 4px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
cursor: pointer;
display: inline-flex;
align-items: center;
@@ -510,8 +510,8 @@ $text-muted: #a1a1aa;
}
&:hover:not(:disabled) {
- color: #ef4444;
- background: #fee2e2;
+ color: var(--app-error-text, #ef4444);
+ background: var(--app-error-soft, #fee2e2);
}
&:disabled {
@@ -531,7 +531,7 @@ $text-muted: #a1a1aa;
border: none;
background: transparent;
border-radius: 4px;
- color: #ef4444;
+ color: var(--app-error-text, #ef4444);
cursor: pointer;
display: inline-flex;
align-items: center;
@@ -546,8 +546,8 @@ $text-muted: #a1a1aa;
}
&:hover:not(:disabled) {
- color: #dc2626;
- background: #fee2e2;
+ color: var(--app-error-text, #dc2626);
+ background: var(--app-error-soft, #fee2e2);
transform: translateY(-50%) scale(1.1);
}
@@ -572,7 +572,7 @@ $text-muted: #a1a1aa;
.task-goal {
font-size: 12.5px;
font-weight: 500;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
line-height: 1.4;
word-break: break-word;
display: -webkit-box;
@@ -587,15 +587,15 @@ $text-muted: #a1a1aa;
justify-content: space-between;
align-items: center;
gap: 12px;
- border-top: 1px solid rgba(241, 245, 249, 0.8);
+ border-top: 1px solid rgb(var(--app-border-rgb, 241 245 249) / 0.8);
padding-top: 6px;
font-size: 11.5px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
.task-id {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 11px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -604,14 +604,14 @@ $text-muted: #a1a1aa;
transition: color 0.15s ease;
&:hover {
- color: #475569;
+ color: var(--app-text-secondary, #475569);
}
}
.task-date {
flex-shrink: 0;
font-size: 11px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
}
}
@@ -635,7 +635,7 @@ $text-muted: #a1a1aa;
.selector-title {
font-size: 11px;
font-weight: 700;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
letter-spacing: 0.05em;
}
@@ -649,12 +649,12 @@ $text-muted: #a1a1aa;
align-items: center;
gap: 6px;
padding: 6px 12px;
- border: 1px solid #e2e8f0;
- background: #ffffff;
+ border: 1px solid var(--app-border, #e2e8f0);
+ background: var(--app-surface, #ffffff);
border-radius: 20px;
font-size: 13px;
font-weight: 500;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
cursor: pointer;
transition: all 0.15s ease;
@@ -663,15 +663,15 @@ $text-muted: #a1a1aa;
}
&:hover {
- background: #f8fafc;
- border-color: #cbd5e1;
- color: #1e293b;
+ background: var(--app-surface-soft, #f8fafc);
+ border-color: var(--app-border-control, #cbd5e1);
+ color: var(--app-text-control, #1e293b);
}
&.active {
- background: #eff6ff;
- border-color: #bfdbfe;
- color: #1d4ed8;
+ background: var(--app-primary-soft, #eff6ff);
+ border-color: var(--app-primary-border, #bfdbfe);
+ color: var(--app-primary-text, #1d4ed8);
font-weight: 600;
}
}
@@ -694,8 +694,8 @@ $text-muted: #a1a1aa;
width: 52px;
height: 52px;
border-radius: 50%;
- background: #f1f5f9;
- border: 1px solid #e2e8f0;
+ background: var(--app-surface-soft, #f1f5f9);
+ border: 1px solid var(--app-border, #e2e8f0);
display: flex;
align-items: center;
justify-content: center;
@@ -704,7 +704,7 @@ $text-muted: #a1a1aa;
.material-symbols-outlined {
font-size: 26px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
}
@@ -712,14 +712,14 @@ $text-muted: #a1a1aa;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
font-weight: 600;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
margin: 0 0 6px 0;
}
.empty-notes-subtitle {
font-family: 'Google Sans', 'Google Sans Text', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 12.5px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
line-height: 1.5;
max-width: 240px;
margin: 0;
@@ -736,7 +736,7 @@ $text-muted: #a1a1aa;
.note-title {
font-size: 14px;
font-weight: 700;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
margin-top: 0;
margin-bottom: 20px;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', sans-serif;
@@ -744,8 +744,8 @@ $text-muted: #a1a1aa;
.code-segment {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
- background-color: #f1f3f4;
- color: #1967d2;
+ background-color: var(--app-info-soft, #f1f3f4);
+ color: var(--app-primary-text, #1967d2);
padding: 2px 6px;
border-radius: 4px;
font-size: 14px;
@@ -756,8 +756,8 @@ $text-muted: #a1a1aa;
.checked {
.code-segment {
- color: #a1a1aa;
- background-color: #f8f9fa;
+ color: var(--app-text-faint, #a1a1aa);
+ background-color: var(--app-surface-soft, #f8f9fa);
}
}
@@ -778,7 +778,7 @@ $text-muted: #a1a1aa;
opacity: 0.7;
}
.milestone-text {
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
}
}
}
@@ -795,11 +795,11 @@ $text-muted: #a1a1aa;
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
&.checked {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
}
&.progress {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
}
&.unchecked {
@@ -809,7 +809,7 @@ $text-muted: #a1a1aa;
.milestone-text {
font-family: 'Google Sans', 'Google Sans Text', 'Inter', sans-serif;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
font-size: 14px;
line-height: 1.8;
font-weight: 400;
@@ -839,7 +839,7 @@ $text-muted: #a1a1aa;
align-items: flex-start;
gap: 10px;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', sans-serif;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
font-size: 14px;
line-height: 1.8;
@@ -848,7 +848,7 @@ $text-muted: #a1a1aa;
}
&.checked {
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
.check-icon.checked {
opacity: 0.7;
@@ -862,11 +862,11 @@ $text-muted: #a1a1aa;
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
&.checked {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
}
&.progress {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
}
&.unchecked {
@@ -875,7 +875,7 @@ $text-muted: #a1a1aa;
}
.bullet {
- color: #9ca3af;
+ color: var(--app-text-faint, #9ca3af);
font-weight: bold;
user-select: none;
padding-left: 2px;
@@ -907,8 +907,8 @@ $text-muted: #a1a1aa;
}
.verification-card {
- background: #f8fafc;
- border: 1px solid #e2e8f0;
+ background: var(--app-surface-soft, #f8fafc);
+ border: 1px solid var(--app-border, #e2e8f0);
border-radius: 6px;
padding: 8px 12px;
display: flex;
@@ -931,10 +931,10 @@ $text-muted: #a1a1aa;
line-height: 1;
letter-spacing: 0.6px;
text-transform: uppercase;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
&.assert {
- color: #0284c7;
+ color: var(--app-info-text, #0284c7);
}
}
}
@@ -943,35 +943,35 @@ $text-muted: #a1a1aa;
font-family: inherit;
font-size: 13.5px;
line-height: 1.6;
- color: #334155;
+ color: var(--app-text-control, #334155);
word-break: break-word;
.bold-segment {
font-weight: 600;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
}
.code-segment {
font-family: 'Fira Code', 'Roboto Mono', Consolas, monospace;
font-size: 12px;
- background: #f1f5f9;
- color: #0f172a;
+ background: var(--app-surface-soft, #f1f5f9);
+ color: var(--app-text-strong, #0f172a);
padding: 1.5px 6px;
border-radius: 4px;
- border: 1px solid #e2e8f0;
+ border: 1px solid var(--app-border, #e2e8f0);
}
}
&.finding {
- background: #fffbeb;
- border-color: #fef3c7;
+ background: var(--app-warning-soft, #fffbeb);
+ border-color: var(--app-warning-border, #fef3c7);
.card-header .card-tag {
- color: #b45309;
+ color: var(--app-warning-text, #b45309);
}
.card-body {
- color: #92400e;
+ color: var(--app-warning-text, #92400e);
}
}
}
@@ -988,7 +988,7 @@ $text-muted: #a1a1aa;
margin-bottom: 14px;
min-height: 22px;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', sans-serif;
- color: #1f1f1f;
+ color: var(--app-text-strong, #1f1f1f);
font-size: 14px;
line-height: 1.6;
@@ -1001,7 +1001,7 @@ $text-muted: #a1a1aa;
font-weight: 700;
margin-top: 24px;
margin-bottom: 12px;
- border-bottom: 1px solid #e5e7eb;
+ border-bottom: 1px solid var(--app-border, #e5e7eb);
padding-bottom: 6px;
}
@@ -1020,17 +1020,17 @@ $text-muted: #a1a1aa;
}
&.checked {
- color: #a1a1aa;
+ color: var(--app-text-faint, #a1a1aa);
.check-icon {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
opacity: 0.7;
}
}
&.progress {
.check-icon {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
}
}
@@ -1042,7 +1042,7 @@ $text-muted: #a1a1aa;
&.list-item {
.bullet {
- color: #9ca3af;
+ color: var(--app-text-faint, #9ca3af);
font-weight: bold;
user-select: none;
}
diff --git a/apps/showcase_ui/src/app/components/evidence-sources/evidence-sources.component.spec.ts b/apps/showcase_ui/src/app/components/evidence-sources/evidence-sources.component.spec.ts
new file mode 100644
index 0000000..b40abc7
--- /dev/null
+++ b/apps/showcase_ui/src/app/components/evidence-sources/evidence-sources.component.spec.ts
@@ -0,0 +1,27 @@
+import { TestBed } from '@angular/core/testing';
+import { EvidenceSourcesComponent } from './evidence-sources.component';
+
+describe('EvidenceSourcesComponent', () => {
+ it('keeps supporting API data separate from the platform and retains actual record identifiers', () => {
+ const fixture = TestBed.createComponent(EvidenceSourcesComponent);
+ fixture.componentRef.setInput('value', {total_sources:1, sources_omitted:0, unattributed_step_count:2,
+ note:'界面结果不能单独证明后端持久状态。', sources:[{
+ id:'src', category:'auxiliary', kind:'api', label:'安卓业务后台', target_id:'service', plugin_id:'data',
+ reference_count:1, references:[{step_number:7, step_id:'actual-step', call_id:'actual-call'}]
+ }]});
+ fixture.detectChanges();
+ const text = fixture.nativeElement.textContent;
+ expect(text).toContain('辅助来源 · 接口数据');
+ expect(text).toContain('actual-step');
+ expect(text).toContain('actual-call');
+ expect(text).toContain('来源未完整记录的步骤:2');
+ expect(text).not.toContain('界面来源 · 安卓界面');
+ expect(fixture.nativeElement.querySelector('app-test-result-badge')).toBeNull();
+ });
+
+ it('does not fabricate source metadata for an older backend', () => {
+ const fixture = TestBed.createComponent(EvidenceSourcesComponent);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.querySelector('details')).toBeNull();
+ });
+});
diff --git a/apps/showcase_ui/src/app/components/evidence-sources/evidence-sources.component.ts b/apps/showcase_ui/src/app/components/evidence-sources/evidence-sources.component.ts
new file mode 100644
index 0000000..3b36173
--- /dev/null
+++ b/apps/showcase_ui/src/app/components/evidence-sources/evidence-sources.component.ts
@@ -0,0 +1,52 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+
+interface SourceReference { step_id?: string; step_number?: number; call_id?: string; source_id?: string; }
+interface RecordedSource {
+ id: string; kind: string; label: string; target_id: string; plugin_id: string;
+ category: 'interface' | 'auxiliary'; references: SourceReference[]; reference_count: number;
+}
+interface SourceSummary {
+ sources: RecordedSource[]; total_sources: number; sources_omitted: number;
+ unattributed_step_count: number; note: string;
+}
+
+@Component({
+ selector: 'app-evidence-sources', standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `@if (value(); as summary) {
+
+ 证据来源 · {{ summary.total_sources ? summary.total_sources + ' 个' : '未记录' }}
+ {{ summary.note }}
+ @for (source of summary.sources; track source.id) {
+
+ {{ source.category === 'interface' ? '界面来源' : '辅助来源' }} · {{ kind(source.kind) }}
+ {{ source.label || source.target_id }}
+ 目标 {{ source.target_id }}@if(source.plugin_id) { · 插件 {{ source.plugin_id }} }
+
+ @for (ref of source.references; track $index) {
+ - 步骤 {{ ref.step_number ?? '未记录' }}@if(ref.step_id) { ·
{{ ref.step_id }} }@if(ref.call_id) { · 调用 {{ ref.call_id }} }
+ }
+
+ @if(source.reference_count > source.references.length) { 另 {{ source.reference_count - source.references.length }} 条引用见步骤记录。 }
+
+ }
+ @if(summary.sources_omitted) { 另 {{ summary.sources_omitted }} 个来源见原始步骤记录。
}
+ @if(summary.unattributed_step_count) { 来源未完整记录的步骤:{{ summary.unattributed_step_count }};不据此推断执行平台。
}
+
+ }`,
+ styles: [`
+ :host { display:block; margin:10px 0; }
+ .sources { border:1px solid var(--app-border-control,#cbd5e1); border-radius:8px; padding:10px 12px; background:var(--app-surface,#fff); color:var(--app-text-control,#1e293b); font-size:12px; line-height:1.7; overflow-wrap:anywhere; }
+ summary { cursor:pointer; font-weight:600; }
+ article { border-top:1px solid var(--app-border-control,#cbd5e1); margin-top:8px; padding-top:8px; }
+ p,small { color:var(--app-text-secondary,#475569); }
+ ul { padding-left:20px; margin:4px 0; }
+ code { font-size:11px; }
+ `]
+})
+export class EvidenceSourcesComponent {
+ value = input
(null);
+ kind(kind: string): string {
+ return ({android:'安卓界面', computer:'电脑界面', api:'接口数据', terminal:'终端', file:'文件', other:'其他'} as Record)[kind] || '来源类型未记录';
+ }
+}
diff --git a/apps/showcase_ui/src/app/components/exploration-panel/exploration-panel.component.scss b/apps/showcase_ui/src/app/components/exploration-panel/exploration-panel.component.scss
index d047d35..662a04e 100644
--- a/apps/showcase_ui/src/app/components/exploration-panel/exploration-panel.component.scss
+++ b/apps/showcase_ui/src/app/components/exploration-panel/exploration-panel.component.scss
@@ -1,78 +1,78 @@
-:host { display:flex; flex-direction:column; min-height:0; height:100%; overflow:hidden; color:#334155; font-size:13px; }
+:host { display:flex; flex-direction:column; min-height:0; height:100%; overflow:hidden; color:var(--app-text-control, #334155); font-size:13px; }
.exploration-panel { padding:18px; display:flex; flex-direction:column; gap:16px; }
header,.row,.actions { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
header,.row { justify-content:space-between; }
-header strong { font-size:15px; color:#0f172a; }
+header strong { font-size:15px; color:var(--app-text-strong, #0f172a); }
p { margin:8px 0; line-height:1.65; white-space:pre-wrap; overflow-wrap:anywhere; }
-header p,.muted,small { color:#64748b; font-size:12px; }
+header p,.muted,small { color:var(--app-text-muted, #64748b); font-size:12px; }
button,input,select,textarea { font:inherit; box-sizing:border-box; }
-button { display:inline-flex; align-items:center; justify-content:center; gap:4px; border:1px solid #dbe3ec; border-radius:8px; background:white; color:#334155; padding:6px 10px; min-height:32px; cursor:pointer; }
-button:hover:enabled { background:#f1f5f9; border-color:#94a3b8; }
+button { display:inline-flex; align-items:center; justify-content:center; gap:4px; border:1px solid var(--app-border, #dbe3ec); border-radius:8px; background:var(--app-surface, white); color:var(--app-text-control, #334155); padding:6px 10px; min-height:32px; cursor:pointer; }
+button:hover:enabled { background:var(--app-surface-soft, #f1f5f9); border-color:var(--app-border-control, #94a3b8); }
button:disabled { opacity:.5; cursor:default; }
-button.primary { background:#0f172a; color:white; border-color:#0f172a; }
+button.primary { background:#0f172a; color:white; border-color:var(--app-border-control, #0f172a); }
.material-symbols-outlined { font-size:18px; }
-label { display:flex; flex-direction:column; gap:6px; color:#475569; margin:8px 0; }
-input,select,textarea { width:100%; min-width:0; border:1px solid #cbd5e1; border-radius:8px; padding:8px 10px; background:white; color:#0f172a; }
+label { display:flex; flex-direction:column; gap:6px; color:var(--app-text-secondary, #475569); margin:8px 0; }
+input,select,textarea { width:100%; min-width:0; border:1px solid var(--app-border-control, #cbd5e1); border-radius:8px; padding:8px 10px; background:var(--app-surface, white); color:var(--app-text-strong, #0f172a); }
textarea { resize:vertical; line-height:1.6; }
-input:focus,select:focus,textarea:focus { outline:2px solid #94a3b8; outline-offset:1px; }
-details { border:1px solid #e2e8f0; border-radius:10px; padding:10px 12px; }
-summary { cursor:pointer; font-size:12px; color:#475569; }
-.session-summary,.case-editor { padding:12px; border:1px solid #e2e8f0; border-radius:12px; background:#f8fafc; }
+input:focus,select:focus,textarea:focus { outline:2px solid var(--app-border-control, #94a3b8); outline-offset:1px; }
+details { border:1px solid var(--app-border, #e2e8f0); border-radius:10px; padding:10px 12px; }
+summary { cursor:pointer; font-size:12px; color:var(--app-text-secondary, #475569); }
+.session-summary,.case-editor { padding:12px; border:1px solid var(--app-border, #e2e8f0); border-radius:12px; background:var(--app-surface-soft, #f8fafc); }
.session-summary .actions { margin-top:10px; }
.conversation { display:flex; flex-direction:column; gap:12px; }
-.message { padding:10px 12px; border-radius:12px; background:#f1f5f9; border:1px solid transparent; }
-.message.user { margin-left:20px; background:#ecfdf5; border-color:#d1fae5; }
-.message.system { padding:4px 8px; background:transparent; color:#64748b; font-size:12px; }
-.recommendation { border-left:2px solid #94a3b8; padding-left:10px; line-height:1.6; }
+.message { padding:10px 12px; border-radius:12px; background:var(--app-surface-soft, #f1f5f9); border:1px solid transparent; }
+.message.user { margin-left:20px; background:var(--app-success-soft, #ecfdf5); border-color:var(--app-success-border, #d1fae5); }
+.message.system { padding:4px 8px; background:transparent; color:var(--app-text-muted, #64748b); font-size:12px; }
+.recommendation { border-left:2px solid var(--app-border-control, #94a3b8); padding-left:10px; line-height:1.6; }
.recommendation button { margin:6px 0 0 8px; }
-.composer { border-top:1px solid #e2e8f0; padding-top:4px; }
+.composer { border-top:1px solid var(--app-border, #e2e8f0); padding-top:4px; }
.composer > button { margin-top:8px; }
-.demo { border:1px solid #bbf7d0; border-radius:12px; padding:12px; background:#f0fdf4; }
-.demo.warning { background:#fffbeb; border-color:#fcd34d; }
+.demo { border:1px solid var(--app-success-border, #bbf7d0); border-radius:12px; padding:12px; background:var(--app-success-soft, #f0fdf4); }
+.demo.warning { background:var(--app-warning-soft, #fffbeb); border-color:#fcd34d; }
.record-dot { display:inline-block; width:7px; height:7px; border-radius:50%; background:#94a3b8; margin-right:6px; }
.record-dot.recording { background:#dc2626; animation:pulse 1.8s infinite; }
@keyframes pulse { 50% { opacity:.4; } }
@media(prefers-reduced-motion:reduce) { .record-dot.recording { animation:none; } }
-.feedback { padding:9px 12px; background:#f0fdf4; border-radius:8px; font-size:12px; }
-.error { color:#b91c1c; }
-.feedback.error { background:#fef2f2; }
-.run,.demo-history { padding:10px 0; border-bottom:1px solid #e2e8f0; }
+.feedback { padding:9px 12px; background:var(--app-success-soft, #f0fdf4); border-radius:8px; font-size:12px; }
+.error { color:var(--app-error-text, #b91c1c); }
+.feedback.error { background:var(--app-error-soft, #fef2f2); }
+.run,.demo-history { padding:10px 0; border-bottom:1px solid var(--app-border, #e2e8f0); }
.run-link { border:none; padding-left:0; background:transparent; text-align:left; }
video { width:100%; max-height:260px; margin-top:8px; background:#0f172a; }
-a { color:#0f766e; font-size:12px; }
-.empty { color:#64748b; text-align:center; padding:25px 10px; }
+a { color:var(--app-info-text, #0f766e); font-size:12px; }
+.empty { color:var(--app-text-muted, #64748b); text-align:center; padding:25px 10px; }
.empty .material-symbols-outlined { font-size:32px; }
.settings-fields,.case-editor { display:flex; flex-direction:column; gap:3px; }
app-agent-stream {flex:1;min-height:0;display:block}
-.composer {flex:none;margin:0 20px 16px;padding:12px 16px;background:#fff;border:1px solid #dbe3ec;border-radius:16px;box-shadow:0 4px 18px #0f172a06;max-height:45%;overflow:auto}
+.composer {flex:none;margin:0 20px 16px;padding:12px 16px;background:var(--app-surface, #fff);border:1px solid var(--app-border, #dbe3ec);border-radius:16px;box-shadow:0 4px 18px #0f172a06;max-height:45%;overflow:auto}
.composer label {margin:0 0 6px}.composer textarea {resize:none}.composer-actions {margin-top:8px}.composer-actions small {flex:1}
-.session-summary {margin-bottom:18px;background:#fff}.session-summary p {margin:6px 0}.session-summary .row strong {display:flex;align-items:center;gap:6px}
-.execution-history {margin-top:8px;border:0;padding:4px 0}.case-editor {margin:16px 0;background:#fff}.case-compact {display:flex;flex-wrap:wrap;align-items:center;gap:8px}.case-compact .actions {margin-left:auto}
+.session-summary {margin-bottom:18px;background:var(--app-surface, #fff)}.session-summary p {margin:6px 0}.session-summary .row strong {display:flex;align-items:center;gap:6px}
+.execution-history {margin-top:8px;border:0;padding:4px 0}.case-editor {margin:16px 0;background:var(--app-surface, #fff)}.case-compact {display:flex;flex-wrap:wrap;align-items:center;gap:8px}.case-compact .actions {margin-left:auto}
.demo {padding:8px 10px;margin-bottom:8px}.demo p {margin:4px 0}
.composer {margin:0 24px 18px;padding:14px 18px;border-radius:22px;box-shadow:0 6px 24px #0f172a08;max-height:50%;box-sizing:border-box;min-width:0}
.composer-heading {display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px}
-.composer-heading label {margin:0;font-size:12px;color:#64748b}
+.composer-heading label {margin:0;font-size:12px;color:var(--app-text-muted, #64748b)}
.composer .manual-control {display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;margin:0 0 10px}
.composer .manual-control label {display:flex;flex-direction:row;align-items:center;gap:8px;margin:0;min-width:0}
.composer .manual-control select {width:auto;min-width:110px;height:36px;padding:6px 10px}
.composer .manual-control button {align-self:center;height:36px;white-space:nowrap}
-.composer-status {font-size:11px;border-radius:20px;padding:3px 8px;background:#eef2ff;color:#4f46e5}
-.composer-status.waiting {color:#92400e;background:#fef3c7}
+.composer-status {font-size:11px;border-radius:20px;padding:3px 8px;background:var(--app-primary-soft, #eef2ff);color:var(--app-exploration-text, #4f46e5)}
+.composer-status.waiting {color:var(--app-warning-text, #92400e);background:var(--app-warning-soft, #fef3c7)}
.composer textarea {border:0;border-radius:0;padding:10px 0;background:transparent;min-height:66px;max-height:140px;font-size:14px;box-shadow:none}
.composer textarea:focus {outline:none}
-.composer:focus-within {border-color:#a5b4fc;box-shadow:0 0 0 3px #eef2ff}
-.composer-actions {gap:6px;flex-wrap:wrap;border-top:1px solid #f1f5f9;padding-top:10px}
-.composer-actions .tool-button {border:0;background:#f8fafc;color:#64748b;border-radius:10px;padding:6px 9px}
-.composer-actions .send-hint {flex:1;text-align:right;font-size:10px;color:#94a3b8;min-width:100px}
+.composer:focus-within {border-color:var(--app-primary-border, #a5b4fc);box-shadow:0 0 0 3px #eef2ff}
+.composer-actions {gap:6px;flex-wrap:wrap;border-top:1px solid var(--app-border, #f1f5f9);padding-top:10px}
+.composer-actions .tool-button {border:0;background:var(--app-surface-soft, #f8fafc);color:var(--app-text-muted, #64748b);border-radius:10px;padding:6px 9px}
+.composer-actions .send-hint {flex:1;text-align:right;font-size:10px;color:var(--app-text-faint, #94a3b8);min-width:100px}
.send-button {min-height:36px;border-radius:12px;padding:7px 12px;background:#4f46e5!important;border-color:#4f46e5!important;gap:6px}
-.composer-footnote {font-size:11px;color:#94a3b8;margin:8px 0 0}
-.composer.needs-answer {border-color:#f4c97a}
-.question-banner {display:flex;gap:10px;background:#fffbeb;border:1px solid #fde68a;border-radius:12px;padding:12px;margin-bottom:12px;color:#92400e}
-.question-banner > .material-symbols-outlined {font-size:22px;margin-top:2px}.question-banner p {margin:4px 0;font-size:13px}.question-banner strong {font-size:13px}.question-banner button {margin-top:5px;background:#fff7d6;border-color:#f4c97a;color:#92400e}
-.case-editor {border-color:#c7d2fe;background:linear-gradient(135deg,#f5f7ff,#fff);padding:20px;border-radius:18px}
-.case-title {display:flex;align-items:center;gap:8px;color:#4f46e5;font-size:14px}.case-title .material-symbols-outlined {font-size:22px}
-.case-compact {display:block}.case-compact h3 {font-size:18px;line-height:1.5;color:#1e293b;margin:14px 0 6px}.case-compact small {font-size:11px;font-weight:400;background:#e0e7ff;color:#4338ca;padding:3px 6px;border-radius:5px;white-space:nowrap}.case-compact .actions {margin:16px 0 0}.case-compact .primary {background:#4f46e5;border-color:#4f46e5}
-.session-summary {padding:12px 16px;border-radius:14px}.exploration-goal{display:flex;align-items:flex-start;gap:6px}.session-summary .exploration-goal p{flex:1;min-width:0;margin:4px 0}.case-compact h3{display:flex;align-items:flex-start;gap:6px}.case-compact h3>span{min-width:0;overflow-wrap:anywhere}.session-summary p {color:#64748b;font-size:12px}
+.composer-footnote {font-size:11px;color:var(--app-text-faint, #94a3b8);margin:8px 0 0}
+.composer.needs-answer {border-color:var(--app-warning-border, #f4c97a)}
+.question-banner {display:flex;gap:10px;background:var(--app-warning-soft, #fffbeb);border:1px solid var(--app-warning-border, #fde68a);border-radius:12px;padding:12px;margin-bottom:12px;color:var(--app-warning-text, #92400e)}
+.question-banner > .material-symbols-outlined {font-size:22px;margin-top:2px}.question-banner p {margin:4px 0;font-size:13px}.question-banner strong {font-size:13px}.question-banner button {margin-top:5px;background:var(--app-warning-soft, #fff7d6);border-color:var(--app-warning-border, #f4c97a);color:var(--app-warning-text, #92400e)}
+.case-editor {border-color:var(--app-primary-border, #c7d2fe);background:linear-gradient(135deg,var(--app-primary-soft, #f5f7ff),var(--app-surface, #fff));padding:20px;border-radius:18px}
+.case-title {display:flex;align-items:center;gap:8px;color:var(--app-exploration-text, #4f46e5);font-size:14px}.case-title .material-symbols-outlined {font-size:22px}
+.case-compact {display:block}.case-compact h3 {font-size:18px;line-height:1.5;color:var(--app-text-control, #1e293b);margin:14px 0 6px}.case-compact small {font-size:11px;font-weight:400;background:var(--app-primary-soft, #e0e7ff);color:var(--app-exploration-text, #4338ca);padding:3px 6px;border-radius:5px;white-space:nowrap}.case-compact .actions {margin:16px 0 0}.case-compact .primary {background:#4f46e5;border-color:#4f46e5}
+.session-summary {padding:12px 16px;border-radius:14px}.exploration-goal{display:flex;align-items:flex-start;gap:6px}.session-summary .exploration-goal p{flex:1;min-width:0;margin:4px 0}.case-compact h3{display:flex;align-items:flex-start;gap:6px}.case-compact h3>span{min-width:0;overflow-wrap:anywhere}.session-summary p {color:var(--app-text-muted, #64748b);font-size:12px}
@media(max-width:600px) {.composer {margin:0 12px 12px;padding:12px}.send-hint {display:none}.send-button {margin-left:auto}.question-banner {max-height:160px;overflow:auto}.case-editor {padding:14px}}
diff --git a/apps/showcase_ui/src/app/components/favorite-button/favorite-button.component.ts b/apps/showcase_ui/src/app/components/favorite-button/favorite-button.component.ts
index 5b61b31..e085345 100644
--- a/apps/showcase_ui/src/app/components/favorite-button/favorite-button.component.ts
+++ b/apps/showcase_ui/src/app/components/favorite-button/favorite-button.component.ts
@@ -8,7 +8,7 @@ import { FavoriteKind, FavoritesService } from '../../services/favorites.service
(click)="toggle($event)">{{favorite() ? 'star' : 'star_outline'}}
@if(error()){{{error()}}}
@if(api.error() && !api.loaded()){}`,
- styles: [`:host{display:inline-flex;align-items:center;position:relative;flex-shrink:0}button{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:7px;background:transparent;color:#64748b;cursor:pointer}button:hover{background:#fef3c7;color:#b45309}button.saved{color:#c47b0b}button.saved span{font-variation-settings:'FILL' 1}button:focus-visible{outline:2px solid #818cf8;outline-offset:2px}button:disabled{opacity:.45;cursor:default}.material-symbols-outlined{font-size:19px}.feedback{position:absolute;left:0;top:30px;min-width:170px;max-width:260px;padding:8px 10px;z-index:20;border:1px solid #fecaca;border-radius:8px;background:#fff7f7;color:#b91c1c;font-size:12px;line-height:1.5;box-shadow:0 4px 16px #0f172a12}.retry{font-size:10px;width:auto}`]
+ styles: [`:host{display:inline-flex;align-items:center;position:relative;flex-shrink:0}button{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:7px;background:transparent;color:var(--app-text-muted, #64748b);cursor:pointer}button:hover{background:var(--app-warning-soft, #fef3c7);color:var(--app-warning-text, #b45309)}button.saved{color:var(--app-warning-text, #c47b0b)}button.saved span{font-variation-settings:'FILL' 1}button:focus-visible{outline:2px solid var(--app-exploration-border, #818cf8);outline-offset:2px}button:disabled{opacity:.45;cursor:default}.material-symbols-outlined{font-size:19px}.feedback{position:absolute;left:0;top:30px;min-width:170px;max-width:260px;padding:8px 10px;z-index:20;border:1px solid var(--app-error-border, #fecaca);border-radius:8px;background:var(--app-error-soft, #fff7f7);color:var(--app-error-text, #b91c1c);font-size:12px;line-height:1.5;box-shadow:0 4px 16px #0f172a12}.retry{font-size:10px;width:auto}`]
})
export class FavoriteButtonComponent {
api = inject(FavoritesService);
diff --git a/apps/showcase_ui/src/app/components/featured-tasks/featured-tasks.component.scss b/apps/showcase_ui/src/app/components/featured-tasks/featured-tasks.component.scss
index 60ff6ba..488b8ca 100644
--- a/apps/showcase_ui/src/app/components/featured-tasks/featured-tasks.component.scss
+++ b/apps/showcase_ui/src/app/components/featured-tasks/featured-tasks.component.scss
@@ -1,58 +1,58 @@
-:host { display: block; width: 100%; color: #334155; }
+:host { display: block; width: 100%; color: var(--app-text-control, #334155); }
.featured { margin-top: 26px; }
.heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
-h2 { display: flex; align-items: center; gap: 7px; margin: 0; font-size: 16px; font-weight: 650; color: #1e293b; }
-h2 span { font-size: 20px; color: #d29a32; }
-.heading p { font-size: 12px; color: #64748b; margin: 6px 0 0; }
+h2 { display: flex; align-items: center; gap: 7px; margin: 0; font-size: 16px; font-weight: 650; color: var(--app-text-control, #1e293b); }
+h2 span { font-size: 20px; color: var(--app-warning-text, #d29a32); }
+.heading p { font-size: 12px; color: var(--app-text-muted, #64748b); margin: 6px 0 0; }
.heading > input { max-width: 210px; width: 40%; border-radius: 20px; }
.filters { display: flex; gap: 5px; flex-wrap: wrap; margin: 17px 0; }
-.filters button { border: 0; padding: 6px 13px; border-radius: 18px; font-size: 12px; background: transparent; color: #64748b; }
-.filters button.selected { background: #e0e7ff; color: #4338ca; font-weight: 600; }
+.filters button { border: 0; padding: 6px 13px; border-radius: 18px; font-size: 12px; background: transparent; color: var(--app-text-muted, #64748b); }
+.filters button.selected { background: var(--app-primary-soft, #e0e7ff); color: var(--app-exploration-text, #4338ca); font-weight: 600; }
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); gap: 14px; align-items: start; }
-.card { padding: 16px; border: 1px solid #e2e8f0; border-radius: 16px; background: #fff; min-width: 0; transition: border-color .15s, box-shadow .15s; }
-.card:hover { border-color: #c7d2fe; box-shadow: 0 4px 16px #33415508; }
-.card.expanded { border-color: #a5b4fc; }
+.card { padding: 16px; border: 1px solid var(--app-border, #e2e8f0); border-radius: 16px; background: var(--app-surface, #fff); min-width: 0; transition: border-color .15s, box-shadow .15s; }
+.card:hover { border-color: var(--app-primary-border, #c7d2fe); box-shadow: 0 4px 16px #33415508; }
+.card.expanded { border-color: var(--app-primary-border, #a5b4fc); }
.card-overview { display: flex; flex-direction: column; min-height: 178px; }
.card-heading { display: flex; align-items: center; justify-content: space-between; gap: 6px; min-height: 22px; }
-.mode { padding: 3px 8px; background: #f1f5f9; color: #475569; border-radius: 6px; font-size: 11px; line-height: 16px; }
-.mode.pro { background: #ede9fe; color: #6d28d9; }
-.mode.explore { background: #e0f2fe; color: #0369a1; }
-.case-version { font-size: 11px; color: #94a3b8; white-space: nowrap; }
+.mode { padding: 3px 8px; background: var(--app-surface-soft, #f1f5f9); color: var(--app-text-secondary, #475569); border-radius: 6px; font-size: 11px; line-height: 16px; }
+.mode.pro { background: var(--app-exploration-soft, #ede9fe); color: var(--app-exploration-text, #6d28d9); }
+.mode.explore { background: var(--app-info-soft, #e0f2fe); color: var(--app-info-text, #0369a1); }
+.case-version { font-size: 11px; color: var(--app-text-faint, #94a3b8); white-space: nowrap; }
.card-title { display: flex; align-items: flex-start; gap: 6px; margin: 13px 0 7px; min-height: 42px; }
-.card-title h3 { flex: 1; min-width: 0; margin: 0; font-size: 14px; line-height: 21px; overflow-wrap: anywhere; color: #1e293b;
+.card-title h3 { flex: 1; min-width: 0; margin: 0; font-size: 14px; line-height: 21px; overflow-wrap: anywhere; color: var(--app-text-control, #1e293b);
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
-.star { display: flex; flex-shrink: 0; width: 24px; height: 24px; align-items: center; justify-content: center; padding: 0; border: 0; background: transparent; color: #c58b23; }
+.star { display: flex; flex-shrink: 0; width: 24px; height: 24px; align-items: center; justify-content: center; padding: 0; border: 0; background: transparent; color: var(--app-warning-text, #c58b23); }
.star span { font-size: 19px; font-variation-settings: 'FILL' 1; }
-.model-summary { font-size: 12px; line-height: 18px; color: #64748b; margin: 0 0 12px; min-height: 36px; overflow-wrap: anywhere;
+.model-summary { font-size: 12px; line-height: 18px; color: var(--app-text-muted, #64748b); margin: 0 0 12px; min-height: 36px; overflow-wrap: anywhere;
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.card-actions { display: flex; align-items: center; justify-content: space-between; margin-top: auto; gap: 10px; }
.card-actions button { display: flex; align-items: center; gap: 4px; min-height: 34px; padding: 7px 10px; border-radius: 8px; font-size: 12px; white-space: nowrap; }
.card-actions span { font-size: 16px; }
-.configure { color: #64748b; border: 0; background: transparent; }
+.configure { color: var(--app-text-muted, #64748b); border: 0; background: transparent; }
.primary { border: 1px solid #6366f1; background: #6366f1; color: white; }
.primary:hover { background: #4f46e5; }
-.configuration { border-top: 1px solid #e2e8f0; margin-top: 14px; padding-top: 14px; display: grid; gap: 12px; }
-.configuration label { display: grid; gap: 6px; font-size: 12px; color: #475569; }
+.configuration { border-top: 1px solid var(--app-border, #e2e8f0); margin-top: 14px; padding-top: 14px; display: grid; gap: 12px; }
+.configuration label { display: grid; gap: 6px; font-size: 12px; color: var(--app-text-secondary, #475569); }
.configuration .checkbox { display: flex; align-items: center; }
.checkbox input { width: auto; min-height: auto; }
-input, select { box-sizing: border-box; min-width: 0; width: 100%; min-height: 40px; padding: 10px 12px; border: 1px solid #dbe3ec; border-radius: 10px; color: #334155; background: white; font: inherit; font-size: 13px; }
-option { color: #334155; background: #fff; }
-.hint { font-size: 11px; color: #64748b; line-height: 1.5; margin: 7px 0 0; }
-.save { justify-self: start; padding: 7px 10px; border: 1px solid #cbd5e1; background: #fff; color: #475569; border-radius: 8px; font-size: 12px; }
-details { border: 1px solid #e2e8f0; padding: 10px; border-radius: 9px; }
+input, select { box-sizing: border-box; min-width: 0; width: 100%; min-height: 40px; padding: 10px 12px; border: 1px solid var(--app-border, #dbe3ec); border-radius: 10px; color: var(--app-text-control, #334155); background: var(--app-surface, white); font: inherit; font-size: 13px; }
+option { color: var(--app-text-control, #334155); background: var(--app-surface, #fff); }
+.hint { font-size: 11px; color: var(--app-text-muted, #64748b); line-height: 1.5; margin: 7px 0 0; }
+.save { justify-self: start; padding: 7px 10px; border: 1px solid var(--app-border-control, #cbd5e1); background: var(--app-surface, #fff); color: var(--app-text-secondary, #475569); border-radius: 8px; font-size: 12px; }
+details { border: 1px solid var(--app-border, #e2e8f0); padding: 10px; border-radius: 9px; }
details summary { font-size: 12px; cursor: pointer; overflow-wrap: anywhere; }
details[open] > label, details[open] > app-setting-slider { margin-top: 12px; display: block; }
details[open] > app-model-selector { margin-top: 12px; }
button { font-family: inherit; cursor: pointer; }
button:disabled { opacity: .5; cursor: default; }
-button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible { outline: 2px solid #818cf8; outline-offset: 2px; }
-.empty { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 28px 18px; border: 1px dashed #cbd5e1; border-radius: 16px; color: #64748b; font-size: 12px; line-height: 1.7; }
-.empty > span { font-size: 28px; color: #c5a365; margin-bottom: 9px; }
-.empty strong { font-size: 14px; color: #475569; }
+button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible { outline: 2px solid var(--app-exploration-border, #818cf8); outline-offset: 2px; }
+.empty { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 28px 18px; border: 1px dashed var(--app-border-control, #cbd5e1); border-radius: 16px; color: var(--app-text-muted, #64748b); font-size: 12px; line-height: 1.7; }
+.empty > span { font-size: 28px; color: var(--app-warning-text, #c5a365); margin-bottom: 9px; }
+.empty strong { font-size: 14px; color: var(--app-text-secondary, #475569); }
.empty p { max-width: 440px; margin: 8px 0 0; }
.error, .notice { padding: 10px 12px; border-radius: 8px; font-size: 12px; line-height: 1.6; }
-.error { background: #fff1f2; color: #b91c1c; }
-.notice { background: #f0fdf4; color: #166534; }
+.error { background: var(--app-error-soft, #fff1f2); color: var(--app-error-text, #b91c1c); }
+.notice { background: var(--app-success-soft, #f0fdf4); color: var(--app-success-text, #166534); }
@media (max-width: 560px) {
.heading { align-items: flex-start; flex-direction: column; }
.heading > input { width: 100%; max-width: none; }
diff --git a/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss b/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss
index 36003a0..cab474a 100644
--- a/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss
+++ b/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss
@@ -32,12 +32,12 @@
width: 400px;
max-width: calc(100vw - 32px);
max-height: calc(100dvh - var(--player-top, 60px) - 12px);
- background: rgba(255, 255, 255, 0.84);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.84);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
- border: 1px solid rgba(255, 255, 255, 0.85);
+ border: 1px solid rgb(var(--app-border-rgb, 255 255 255) / 0.85);
border-radius: 16px;
- box-shadow: 0 20px 50px -10px rgba(15, 23, 42, 0.22), 0 0 0 1px rgba(226, 232, 240, 0.6), inset 0 1px 1px rgba(255, 255, 255, 0.9);
+ box-shadow: 0 20px 50px -10px rgba(15, 23, 42, 0.22), 0 0 0 1px rgba(226, 232, 240, 0.6), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
z-index: 1050;
overflow: hidden;
display: flex;
@@ -80,9 +80,9 @@
/* --- Window Header (Draggable Handle) --- */
.window-header {
height: 44px;
- background: rgba(255, 255, 255, 0.75);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.75);
backdrop-filter: blur(16px);
- border-bottom: 1px solid rgba(226, 232, 240, 0.75);
+ border-bottom: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.75);
display: flex;
align-items: center;
justify-content: space-between;
@@ -104,14 +104,14 @@
.header-icon {
font-size: 18px;
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
flex-shrink: 0;
}
.header-title {
font-size: 13px;
font-weight: 600;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -121,9 +121,9 @@
.mode-switcher-group {
display: flex;
align-items: center;
- background: rgba(241, 245, 249, 0.65);
+ background: rgb(var(--app-surface-soft-rgb, 241 245 249) / 0.65);
backdrop-filter: blur(12px);
- border: 1px solid rgba(226, 232, 240, 0.8);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.8);
border-radius: 8px;
padding: 2px;
margin-left: 4px;
@@ -132,7 +132,7 @@
.mode-btn {
border: none;
background: transparent;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
font-size: 10px;
font-weight: 700;
padding: 2px 7px;
@@ -152,8 +152,8 @@
}
&:hover:not(:disabled) {
- color: #0f172a;
- background: rgba(255, 255, 255, 0.6);
+ color: var(--app-text-strong, #0f172a);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.6);
}
&.active {
@@ -180,9 +180,9 @@
.mode-toggle-group {
display: flex;
align-items: center;
- background: rgba(241, 245, 249, 0.7);
+ background: rgb(var(--app-surface-soft-rgb, 241 245 249) / 0.7);
backdrop-filter: blur(12px);
- border: 1px solid rgba(226, 232, 240, 0.85);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.85);
border-radius: 8px;
padding: 2px;
margin-right: 4px;
@@ -194,7 +194,7 @@
gap: 3px;
border: none;
background: transparent;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
font-size: 11px;
font-weight: 600;
padding: 2px 7px;
@@ -207,8 +207,8 @@
}
&:hover:not(.active) {
- color: #0f172a;
- background: rgba(255, 255, 255, 0.8);
+ color: var(--app-text-strong, #0f172a);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.8);
}
&.active {
@@ -222,9 +222,9 @@
.speed-pill-group {
display: flex;
align-items: center;
- background: rgba(241, 245, 249, 0.65);
+ background: rgb(var(--app-surface-soft-rgb, 241 245 249) / 0.65);
backdrop-filter: blur(12px);
- border: 1px solid rgba(226, 232, 240, 0.8);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.8);
border-radius: 8px;
padding: 2px;
margin-right: 4px;
@@ -232,7 +232,7 @@
.speed-btn {
border: none;
background: transparent;
- color: #334155;
+ color: var(--app-text-control, #334155);
font-size: 11px;
font-weight: 700;
padding: 2px 6px;
@@ -241,8 +241,8 @@
transition: all 0.15s ease;
&:hover {
- color: #0f172a;
- background: #cbd5e1;
+ color: var(--app-text-strong, #0f172a);
+ background: var(--app-surface-raised, #cbd5e1);
}
&.active {
@@ -255,11 +255,11 @@
}
.header-btn {
- background: rgba(241, 245, 249, 0.75);
+ background: rgb(var(--app-surface-soft-rgb, 241 245 249) / 0.75);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
- border: 1px solid rgba(226, 232, 240, 0.85);
- color: #1e293b;
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.85);
+ color: var(--app-text-control, #1e293b);
cursor: pointer;
display: flex;
align-items: center;
@@ -267,21 +267,21 @@
width: 28px;
height: 28px;
border-radius: 7px;
- box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgba(255, 255, 255, 0.8);
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.8);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
- background: rgba(255, 255, 255, 0.95);
- color: #0f172a;
- border-color: rgba(203, 213, 225, 0.95);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.95);
+ color: var(--app-text-strong, #0f172a);
+ border-color: rgb(var(--app-border-control-rgb, 203 213 225) / 0.95);
transform: translateY(-0.5px);
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.08);
}
&.close-btn:hover {
- background: rgba(254, 226, 226, 0.95);
- color: #dc2626;
- border-color: rgba(252, 165, 165, 0.9);
+ background: rgb(var(--app-error-soft-rgb, 254 226 226) / 0.95);
+ color: var(--app-error-text, #dc2626);
+ border-color: rgb(var(--app-error-border-rgb, 252 165 165) / 0.9);
box-shadow: 0 2px 6px rgba(220, 38, 38, 0.15);
}
@@ -301,7 +301,7 @@
.video-body {
min-height: 0;
position: relative;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
display: flex;
flex-direction: column;
align-items: stretch;
@@ -314,16 +314,16 @@
flex-direction: column;
align-items: center;
gap: 10px;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
width: 100%;
box-sizing: border-box;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
font-size: 13px;
font-weight: 500;
.spinning-icon {
font-size: 28px;
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
animation: spin 1s linear infinite;
}
}
@@ -336,7 +336,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
min-height: 260px;
overflow: hidden;
@@ -346,7 +346,7 @@
display: block;
object-fit: contain;
user-select: none;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
}
.live-status-overlay {
@@ -363,9 +363,9 @@
display: flex;
align-items: center;
gap: 6px;
- background: #fef2f2;
- border: 1px solid #fca5a5;
- color: #dc2626;
+ background: var(--app-error-soft, #fef2f2);
+ border: 1px solid var(--app-error-border, #fca5a5);
+ color: var(--app-error-text, #dc2626);
font-size: 10.5px;
font-weight: 700;
padding: 3px 9px;
@@ -384,10 +384,10 @@
.live-refresh-btn {
pointer-events: auto;
- background: rgba(255, 255, 255, 0.95);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.95);
backdrop-filter: blur(6px);
- border: 1px solid #cbd5e1;
- color: #1e293b;
+ border: 1px solid var(--app-border-control, #cbd5e1);
+ color: var(--app-text-control, #1e293b);
cursor: pointer;
display: flex;
align-items: center;
@@ -399,9 +399,9 @@
transition: all 0.15s ease;
&:hover {
- background: #f1f5f9;
- color: #0f172a;
- border-color: #94a3b8;
+ background: var(--app-surface-soft, #f1f5f9);
+ color: var(--app-text-strong, #0f172a);
+ border-color: var(--app-border-control, #94a3b8);
transform: rotate(90deg);
}
@@ -415,7 +415,7 @@
.step-replay-container {
display: flex;
flex-direction: column;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
position: relative;
width: 100%;
overflow: hidden;
@@ -424,16 +424,16 @@
display: flex;
align-items: center;
gap: 6px;
- background: #fffbeb;
- color: #b45309;
+ background: var(--app-warning-soft, #fffbeb);
+ color: var(--app-warning-text, #b45309);
padding: 6px 12px;
font-size: 11.5px;
font-weight: 500;
- border-bottom: 1px solid #fde68a;
+ border-bottom: 1px solid var(--app-warning-border, #fde68a);
.notice-icon {
font-size: 14px;
- color: #d97706;
+ color: var(--app-warning-text, #d97706);
flex-shrink: 0;
}
}
@@ -443,7 +443,7 @@
align-items: center;
justify-content: center;
position: relative;
- background: #f8fafc;
+ background: var(--app-surface-soft, #f8fafc);
min-height: 280px;
max-height: 520px;
overflow: hidden;
@@ -458,21 +458,21 @@
align-items: center;
justify-content: center;
gap: 8px;
- background: #f8fafc;
- color: #64748b;
+ background: var(--app-surface-soft, #f8fafc);
+ color: var(--app-text-muted, #64748b);
font-size: 12px;
font-weight: 500;
z-index: 5;
.spinning-icon {
font-size: 24px;
- color: #0284c7;
+ color: var(--app-info-text, #0284c7);
animation: spin 1s linear infinite;
}
.broken-icon {
font-size: 28px;
- color: #94a3b8;
+ color: var(--app-text-faint, #94a3b8);
}
}
@@ -495,7 +495,7 @@
cursor: pointer;
border-radius: 8px;
box-shadow: 0 4px 20px -2px rgba(15, 23, 42, 0.14), 0 0 0 1px rgba(226, 232, 240, 0.9);
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
transition: opacity 0.15s ease;
&:hover {
@@ -532,7 +532,7 @@
background: rgba(15, 23, 42, 0.88);
backdrop-filter: blur(14px) saturate(180%);
-webkit-backdrop-filter: blur(14px) saturate(180%);
- border: 1px solid rgba(255, 255, 255, 0.2);
+ border: 1px solid rgb(var(--app-border-rgb, 255 255 255) / 0.2);
border-radius: 10px;
padding: 6px 10px;
color: #ffffff;
@@ -580,7 +580,7 @@
.action-type-icon {
font-size: 15px;
- color: #38bdf8;
+ color: var(--app-info-text, #38bdf8);
flex-shrink: 0;
}
}
@@ -595,8 +595,8 @@
.pre-post-toggle {
display: inline-flex;
align-items: center;
- background: rgba(255, 255, 255, 0.12);
- border: 1px solid rgba(255, 255, 255, 0.15);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.12);
+ border: 1px solid rgb(var(--app-border-rgb, 255 255 255) / 0.15);
border-radius: 6px;
padding: 2px;
gap: 2px;
@@ -614,7 +614,7 @@
&:hover {
color: #ffffff;
- background: rgba(255, 255, 255, 0.2);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.2);
}
&.active {
@@ -639,11 +639,11 @@
&:hover {
color: #ffffff;
- background: rgba(255, 255, 255, 0.15);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.15);
}
&.pinned {
- color: #38bdf8;
+ color: var(--app-info-text, #38bdf8);
background: rgba(56, 189, 248, 0.2);
}
@@ -664,7 +664,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
video {
min-height: 0;
@@ -674,21 +674,21 @@
display: block;
object-fit: contain;
cursor: pointer;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
}
}
.video-controls-footer {
- background: rgba(255, 255, 255, 0.84);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.84);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
- border-top: 1px solid rgba(226, 232, 240, 0.75);
+ border-top: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.75);
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 10px;
flex-shrink: 0;
- box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgba(255, 255, 255, 0.9);
+ box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
.scrubber-bar-container {
position: relative;
@@ -714,7 +714,7 @@
left: 0;
right: 0;
height: 5px;
- background: rgba(226, 232, 240, 0.85);
+ background: rgb(var(--app-surface-raised-rgb, 226 232 240) / 0.85);
border-radius: 3px;
}
@@ -740,7 +740,7 @@
width: 12px;
height: 12px;
padding: 0;
- border: 2px solid #fff;
+ border: 2px solid var(--app-border, #fff);
border-radius: 50%;
background: #64748b;
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.25);
@@ -775,8 +775,8 @@
width: 7px;
height: 7px;
border-radius: 50%;
- background: rgba(255, 255, 255, 0.9);
- border: 1.5px solid #94a3b8;
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.9);
+ border: 1.5px solid var(--app-border-control, #94a3b8);
transform: translate(-50%, 0);
pointer-events: auto;
cursor: pointer;
@@ -785,17 +785,17 @@
&:hover {
transform: translate(-50%, 0) scale(1.4);
border-color: #0284c7;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
}
&.passed {
background: #0284c7;
- border-color: #ffffff;
+ border-color: var(--app-border, #ffffff);
}
&.active {
background: #38bdf8;
- border-color: #ffffff;
+ border-color: var(--app-border, #ffffff);
transform: translate(-50%, 0) scale(1.3);
box-shadow: 0 0 6px rgba(56, 189, 248, 0.8);
}
@@ -815,12 +815,12 @@
}
.ctrl-btn {
- background: rgba(241, 245, 249, 0.75);
+ background: rgb(var(--app-surface-soft-rgb, 241 245 249) / 0.75);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
- border: 1px solid rgba(226, 232, 240, 0.85);
- box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgba(255, 255, 255, 0.85);
- color: #0f172a;
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.85);
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.85);
+ color: var(--app-text-strong, #0f172a);
width: 32px;
height: 32px;
border-radius: 8px;
@@ -831,9 +831,9 @@
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
- background: rgba(255, 255, 255, 0.95);
- border-color: rgba(191, 219, 254, 0.9);
- color: #0284c7;
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.95);
+ border-color: rgb(var(--app-primary-border-rgb, 191 219 254) / 0.9);
+ color: var(--app-info-text, #0284c7);
transform: translateY(-0.5px);
box-shadow: 0 3px 8px rgba(15, 23, 42, 0.08);
}
@@ -843,8 +843,8 @@
}
&.active {
- color: #0284c7;
- background: rgba(224, 242, 254, 0.85);
+ color: var(--app-info-text, #0284c7);
+ background: rgb(var(--app-info-soft-rgb, 224 242 254) / 0.85);
border-color: rgba(56, 189, 248, 0.8);
box-shadow: 0 2px 6px rgba(2, 132, 199, 0.15);
}
@@ -853,16 +853,16 @@
background: linear-gradient(135deg, rgba(2, 132, 199, 0.92), rgba(37, 99, 235, 0.88));
backdrop-filter: blur(14px) saturate(180%);
-webkit-backdrop-filter: blur(14px) saturate(180%);
- border: 1px solid rgba(255, 255, 255, 0.35);
+ border: 1px solid rgb(var(--app-border-rgb, 255 255 255) / 0.35);
color: #ffffff;
width: 36px;
height: 36px;
border-radius: 50%;
- box-shadow: 0 4px 14px rgba(2, 132, 199, 0.35), inset 0 1px 1px rgba(255, 255, 255, 0.5);
+ box-shadow: 0 4px 14px rgba(2, 132, 199, 0.35), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.5);
&:hover {
background: linear-gradient(135deg, rgba(2, 132, 199, 1), rgba(37, 99, 235, 0.95));
- box-shadow: 0 6px 18px rgba(2, 132, 199, 0.45), inset 0 1px 1px rgba(255, 255, 255, 0.6);
+ box-shadow: 0 6px 18px rgba(2, 132, 199, 0.45), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.6);
transform: translateY(-1px) scale(1.04);
}
@@ -883,18 +883,18 @@
font-size: 11px;
font-weight: 700;
font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
- color: #334155;
+ color: var(--app-text-control, #334155);
letter-spacing: -0.2px;
&:hover {
- color: #0284c7;
- border-color: rgba(191, 219, 254, 0.9);
+ color: var(--app-info-text, #0284c7);
+ border-color: rgb(var(--app-primary-border-rgb, 191 219 254) / 0.9);
}
}
.material-symbols-outlined {
font-size: 18px;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
}
}
@@ -902,7 +902,7 @@
font-size: 12px;
font-weight: 700;
font-family: 'JetBrains Mono', 'Roboto Mono', monospace;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
margin-left: 4px;
letter-spacing: 0.5px;
}
@@ -911,7 +911,7 @@
/* --- Clean, Minimal High-Contrast Empty State --- */
.empty-video-fallback {
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
padding: 36px 20px;
display: flex;
flex-direction: column;
@@ -924,8 +924,8 @@
width: 48px;
height: 48px;
border-radius: 50%;
- background: #fef2f2;
- border: 1px solid #fee2e2;
+ background: var(--app-error-soft, #fef2f2);
+ border: 1px solid var(--app-error-border, #fee2e2);
display: flex;
align-items: center;
justify-content: center;
@@ -933,7 +933,7 @@
.fallback-icon {
font-size: 24px;
- color: #ef4444;
+ color: var(--app-error-text, #ef4444);
}
}
@@ -941,13 +941,13 @@
margin: 0 0 6px 0;
font-size: 13.5px;
font-weight: 600;
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
}
.fallback-desc {
margin: 0 0 12px 0;
font-size: 12px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
line-height: 1.4;
max-width: 240px;
}
@@ -1026,25 +1026,25 @@
gap: 8px;
align-items: center;
padding: 8px 12px;
- background: #f8fafc;
- border-bottom: 1px solid #e2e8f0;
+ background: var(--app-surface-soft, #f8fafc);
+ border-bottom: 1px solid var(--app-border, #e2e8f0);
font-size: 12px;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
select {
min-width: 0;
flex: 1;
- border: 1px solid #cbd5e1;
+ border: 1px solid var(--app-border-control, #cbd5e1);
border-radius: 6px;
padding: 4px;
- background: white;
+ background: var(--app-surface, white);
}
}
.recording-notice {
padding: 8px 12px;
- background: #fffbeb;
- color: #92400e;
+ background: var(--app-warning-soft, #fffbeb);
+ color: var(--app-warning-text, #92400e);
font-size: 12px;
}
@@ -1055,8 +1055,8 @@
align-items: center;
justify-content: center;
padding: 24px;
- color: #475569;
- background: #f1f5f9;
+ color: var(--app-text-secondary, #475569);
+ background: var(--app-surface-soft, #f1f5f9);
text-align: center;
z-index: 2;
}
@@ -1066,13 +1066,13 @@
justify-content: space-between;
flex-wrap: wrap;
gap: 4px 12px;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
font-size: 11px;
line-height: 16px;
}
.recording-skip-notice {
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
font-size: 11px;
line-height: 16px;
}
diff --git a/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss b/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss
index e1d13c8..fb42945 100644
--- a/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss
+++ b/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss
@@ -15,10 +15,10 @@
*/
$border-color: rgba(0, 0, 0, 0.08);
-$text-primary: #1f2937;
-$text-secondary: #4b5563;
-$text-muted: #9ca3af;
-$bg-main: #f9fafb;
+$text-primary: var(--app-text-strong, #1f2937);
+$text-secondary: var(--app-text-secondary, #4b5563);
+$text-muted: var(--app-text-faint, #9ca3af);
+$bg-main: var(--app-surface-soft, #f9fafb);
:host {
display: flex;
@@ -32,7 +32,7 @@ $bg-main: #f9fafb;
display: flex;
flex-direction: column;
height: 100%;
- background: rgba(255, 255, 255, 0.4);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.4);
backdrop-filter: blur(16px);
box-sizing: border-box;
@@ -42,7 +42,7 @@ $bg-main: #f9fafb;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid $border-color;
- background: rgba(255, 255, 255, 0.6);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.6);
flex-wrap: wrap;
gap: 16px;
@@ -74,10 +74,10 @@ $bg-main: #f9fafb;
.session-dropdown {
padding: 8px 12px;
- border: 1px solid rgba(0, 0, 0, 0.12);
+ border: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.12);
border-radius: 8px;
font-size: 13.5px;
- background-color: #ffffff;
+ background-color: var(--app-surface, #ffffff);
outline: none;
color: $text-primary;
min-width: 250px;
@@ -92,8 +92,8 @@ $bg-main: #f9fafb;
.btn-refresh {
padding: 8px 14px;
- background: #ffffff;
- border: 1px solid rgba(0, 0, 0, 0.12);
+ background: var(--app-surface, #ffffff);
+ border: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.12);
border-radius: 8px;
font-size: 13px;
font-weight: 600;
@@ -105,7 +105,7 @@ $bg-main: #f9fafb;
transition: background 0.2s, transform 0.1s;
&:hover {
- background: #f3f4f6;
+ background: var(--app-surface-raised, #f3f4f6);
}
&:active {
@@ -119,7 +119,7 @@ $bg-main: #f9fafb;
flex: 1;
overflow-y: auto;
padding: 24px;
- background: rgba(249, 250, 251, 0.3);
+ background: rgb(var(--app-surface-soft-rgb, 249 250 251) / 0.3);
&::-webkit-scrollbar {
width: 6px;
@@ -172,8 +172,8 @@ $bg-main: #f9fafb;
gap: 16px;
.log-card {
- background: #ffffff;
- border: 1px solid rgba(0, 0, 0, 0.06);
+ background: var(--app-surface, #ffffff);
+ border: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.06);
border-radius: 12px;
padding: 16px 20px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.01), 0 2px 4px -1px rgba(0, 0, 0, 0.006);
@@ -214,24 +214,24 @@ $bg-main: #f9fafb;
font-family: monospace;
&.llm_stream {
- background: #e0f2fe;
- color: #0369a1;
+ background: var(--app-info-soft, #e0f2fe);
+ color: var(--app-info-text, #0369a1);
}
&.trace_recorded {
- background: #f3e8ff;
- color: #6b21a8;
+ background: var(--app-exploration-soft, #f3e8ff);
+ color: var(--app-exploration-text, #6b21a8);
}
&.step_recorded {
- background: #ecfdf5;
- color: #047857;
+ background: var(--app-success-soft, #ecfdf5);
+ color: var(--app-success-text, #047857);
}
&.step_updated {
- background: #fef3c7;
- color: #b45309;
+ background: var(--app-warning-soft, #fef3c7);
+ color: var(--app-warning-text, #b45309);
}
&.background_tasks_updated {
- background: #fff1f2;
- color: #be123c;
+ background: var(--app-error-soft, #fff1f2);
+ color: var(--app-error-text, #be123c);
}
}
}
@@ -260,28 +260,28 @@ $bg-main: #f9fafb;
}
.summary-bubble {
- background: #f1f5f9;
- border: 1px solid rgba(0, 0, 0, 0.05);
- border-left: 4px solid #475569;
+ background: var(--app-surface-soft, #f1f5f9);
+ border: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.05);
+ border-left: 4px solid var(--app-border-control, #475569);
border-radius: 8px;
padding: 12px 16px;
font-size: 13.5px;
line-height: 1.5;
- color: #1e293b;
+ color: var(--app-text-control, #1e293b);
}
}
details {
- border: 1px solid rgba(0, 0, 0, 0.06);
+ border: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.06);
border-radius: 8px;
- background: #fafafa;
+ background: var(--app-surface-soft, #fafafa);
overflow: hidden;
transition: border-color 0.2s;
&[open] {
- border-color: rgba(0, 0, 0, 0.12);
+ border-color: rgb(var(--app-border-control-rgb, 0 0 0) / 0.12);
.details-summary {
- border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+ border-bottom: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.06);
background: rgba(0, 0, 0, 0.02);
}
}
@@ -294,7 +294,7 @@ $bg-main: #f9fafb;
cursor: pointer;
user-select: none;
outline: none;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
transition: background 0.2s;
&:hover {
@@ -306,18 +306,18 @@ $bg-main: #f9fafb;
padding: 14px;
font-size: 13px;
line-height: 1.6;
- color: #334155;
+ color: var(--app-text-control, #334155);
&.thinking-text {
- background: #f0fdf4;
- color: #14532d;
+ background: var(--app-success-soft, #f0fdf4);
+ color: var(--app-success-text, #14532d);
white-space: pre-wrap;
font-family: inherit;
}
&.planning-text {
- background: #f8fafc;
- color: #334155;
+ background: var(--app-surface-soft, #f8fafc);
+ color: var(--app-text-control, #334155);
white-space: pre-wrap;
font-family: inherit;
}
@@ -326,28 +326,28 @@ $bg-main: #f9fafb;
&.thinking-details {
border-left: 4px solid #16a34a;
.details-summary {
- color: #16a34a;
+ color: var(--app-success-text, #16a34a);
}
}
&.planning-details {
border-left: 4px solid #4f46e5;
.details-summary {
- color: #4f46e5;
+ color: var(--app-exploration-text, #4f46e5);
}
}
&.tool-details {
border-left: 4px solid #2563eb;
.details-summary {
- color: #2563eb;
+ color: var(--app-primary-text, #2563eb);
}
}
&.result-details {
border-left: 4px solid #0891b2;
.details-summary {
- color: #0891b2;
+ color: var(--app-info-text, #0891b2);
}
}
}
@@ -355,7 +355,7 @@ $bg-main: #f9fafb;
&.raw-body {
details {
- border: 1px solid rgba(0, 0, 0, 0.06);
+ border: 1px solid rgb(var(--app-border-control-rgb, 0 0 0) / 0.06);
border-radius: 8px;
overflow: hidden;
@@ -366,7 +366,7 @@ $bg-main: #f9fafb;
color: $text-secondary;
cursor: pointer;
outline: none;
- background: #ffffff;
+ background: var(--app-surface, #ffffff);
}
.details-content {
@@ -392,11 +392,11 @@ $bg-main: #f9fafb;
height: 4px;
}
&::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.2);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.2);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.3);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.3);
}
}
}
diff --git a/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.html b/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.html
index fe698a3..11d60e1 100644
--- a/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.html
+++ b/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.html
@@ -47,7 +47,7 @@
@if (review.last_failure_category) { 最近错误类别{{ review.last_failure_category }} }
@if (review.status === 'budget_exhausted') { 结束原因取证、模型请求与重试已耗尽验收总预算;未确认通过。 }
@if (review.status === 'request_retries_exhausted') { 结束原因验收模型请求重试已用尽;未确认通过,没有重跑设备操作。 }
- @if (review.status === 'cancelled') { 结束原因验收被停止或指导中断,不作为请求超时重试。 }
+ @if (review.status === 'cancelled') { 结束原因{{ cancellationReason(review.cancel_reason) }} }
}
@@ -57,7 +57,12 @@
- 本地请求编号
- {{ request.request_id }}
- 本次尝试编号
- {{ request.attempt_id }}
+ @if (request.previous_attempt_id) { - 上次尝试编号
- {{ request.previous_attempt_id }}
}
+ - 出站请求编号
- {{ request.client_request_id || '未记录' }}
- 服务端请求编号
- {{ request.server_request_id || '服务未提供' }}
+ - 响应编号
- {{ request.response_id || '服务未提供' }}
+ @if (request.server_trace_id) { - 服务端链路编号
- {{ request.server_trace_id }}
}
+ @if (request.traceparent) { - 链路上下文
- {{ request.traceparent }}
}
- 模型配置
- {{ request.model || '未记录' }} / {{ request.reasoning_effort || '默认' }} · 第 {{ request.attempt }} 次请求,最多重试 {{ request.max_retries }} 次
@if (request.final_review; as review) { - 所属最终验收
- {{ review.review_id }} · {{ reviewStage(review) }} · 请求记录时剩余 {{ review.remaining_seconds }} 秒
}
- 输出 Token 上限
- {{ request.max_output_tokens || '未设置(由模型服务决定)' }}
@@ -68,6 +73,8 @@
- 首个有效输出
- {{ request.first_output_at ? (request.first_output_at * 1000 | date:'HH:mm:ss.SSS') : '未收到/未记录' }}
- 最后有效输出
- {{ request.last_output_at ? (request.last_output_at * 1000 | date:'HH:mm:ss.SSS') : '未收到/未记录' }}
- 结束时间
- {{ request.ended_at ? (request.ended_at * 1000 | date:'HH:mm:ss.SSS') : '尚未记录' }}
+ @if (termination(request); as reason) { - 结束原因
- {{ reason }}
}
+ @if (request.cancel_phase) { - 中断阶段
- {{ phase(request.cancel_phase) }}
}
- 输入图片
- {{ request.images.length }} 张 @for (image of request.images; track $index) { {{ image.width && image.height ? image.width + ' × ' + image.height : '尺寸未知' }} }
- Token 用量
- {{ request.usage ? '输入 ' + request.usage.prompt_tokens + ' / 输出 ' + request.usage.completion_tokens + ' / 合计 ' + request.usage.total_tokens : '未知(服务未报告)' }}
@if (request.error) { - 失败原因
- {{ request.error_type }} · {{ request.error }} · 阶段 {{ phase(request.failure_phase || '') }} @if (request.category) { · {{ request.category }} }
}
diff --git a/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.scss b/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.scss
index 62c13ba..1538c51 100644
--- a/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.scss
+++ b/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.scss
@@ -1,26 +1,26 @@
:host { display: block; }
-.model-requests { margin: 10px 0 16px; color: #475569; font-size: 12px; }
-.request-live { padding: 12px 14px; border: 1px solid #dbeafe; border-radius: 10px; background: #f8fbff; margin-bottom: 8px; }
+.model-requests { margin: 10px 0 16px; color: var(--app-text-secondary, #475569); font-size: 12px; }
+.request-live { padding: 12px 14px; border: 1px solid var(--app-primary-border, #dbeafe); border-radius: 10px; background: var(--app-primary-soft, #f8fbff); margin-bottom: 8px; }
.request-heading { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
-strong { color: #334155; font-size: 13px; }
-.model { color: #64748b; overflow-wrap: anywhere; }
-.elapsed { margin-left: auto; color: #1d4ed8; font-variant-numeric: tabular-nums; }
+strong { color: var(--app-text-control, #334155); font-size: 13px; }
+.model { color: var(--app-text-muted, #64748b); overflow-wrap: anywhere; }
+.elapsed { margin-left: auto; color: var(--app-primary-text, #1d4ed8); font-variant-numeric: tabular-nums; }
.status-dot { height: 7px; width: 7px; border-radius: 50%; background: #64748b; flex: 0 0 auto; }
.waiting { background: #3b82f6; animation: pulse 1.5s ease-in-out infinite; }
-.retrying { border-color: #fde68a; background: #fffbeb; .elapsed { color: #92400e; } .waiting { background: #d97706; } }
-.stale { border-color: #cbd5e1; background: #f8fafc; }
+.retrying { border-color: var(--app-warning-border, #fde68a); background: var(--app-warning-soft, #fffbeb); .elapsed { color: var(--app-warning-text, #92400e); } .waiting { background: #d97706; } }
+.stale { border-color: var(--app-border-control, #cbd5e1); background: var(--app-surface-soft, #f8fafc); }
p { margin: 6px 0 0; line-height: 1.7; }
-.error { color: #b45309; overflow-wrap: anywhere; }
-summary { cursor: pointer; padding: 5px 0; line-height: 1.7; } summary:hover { color: #2563eb; }
+.error { color: var(--app-warning-text, #b45309); overflow-wrap: anywhere; }
+summary { cursor: pointer; padding: 5px 0; line-height: 1.7; } summary:hover { color: var(--app-primary-text, #2563eb); }
summary:focus-visible, a:focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }
-.diagnostics-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; margin: 8px 0; color: #64748b; }
-a { color: #2563eb; text-decoration: none; border: 1px solid #dbeafe; border-radius: 6px; padding: 4px 9px; }
+.diagnostics-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; margin: 8px 0; color: var(--app-text-muted, #64748b); }
+a { color: var(--app-primary-text, #2563eb); text-decoration: none; border: 1px solid var(--app-primary-border, #dbeafe); border-radius: 6px; padding: 4px 9px; }
.request-history { max-height: 360px; overflow-y: auto; }
-.request-detail { border-top: 1px solid #e2e8f0; padding: 4px 0; }
+.request-detail { border-top: 1px solid var(--app-border, #e2e8f0); padding: 4px 0; }
dl { display: grid; grid-template-columns: 110px minmax(0, 1fr); gap: 7px 10px; margin: 8px 0 12px; line-height: 1.6; }
-dt { color: #64748b; } dd { margin: 0; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
-.image-size, .fallback { display: inline-block; padding: 0 5px; margin: 2px 3px; background: #f1f5f9; border-radius: 4px; }
-.notice { color: #64748b; }
+dt { color: var(--app-text-muted, #64748b); } dd { margin: 0; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
+.image-size, .fallback { display: inline-block; padding: 0 5px; margin: 2px 3px; background: var(--app-surface-soft, #f1f5f9); border-radius: 4px; }
+.notice { color: var(--app-text-muted, #64748b); }
@keyframes pulse { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .waiting { animation: none; } }
@media (max-width: 480px) { dl { grid-template-columns: 90px minmax(0, 1fr); } .elapsed { margin-left: 15px; } }
diff --git a/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.spec.ts b/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.spec.ts
index fe579c6..dc43ebc 100644
--- a/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.spec.ts
+++ b/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.spec.ts
@@ -1,7 +1,7 @@
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
-import { FinalReview, LlmRequestStatusComponent, ModelRequest, requestHint, reviewStage } from './llm-request-status.component';
+import { cancellationReason, FinalReview, LlmRequestStatusComponent, ModelRequest, requestHint, requestTermination, reviewStage } from './llm-request-status.component';
const waiting: ModelRequest = {
request_id: 'call-1', attempt_id: 'attempt-1', model: 'gpt-6-luna', role: 'FlashRunner',
@@ -118,4 +118,42 @@ describe('模型请求诊断', () => {
expect(reviewStage({ ...review, status: 'cancelled' })).toBe('验收已中断');
expect(reviewStage({ ...review, status: 'completed' })).toBe('验收完成');
});
+
+ it('取消原因有来源才显示,旧记录与未知字段保留未知', () => {
+ expect(cancellationReason('user_stop')).toContain('用户停止');
+ expect(cancellationReason('guidance_replaced')).toContain('新指导替换');
+ expect(cancellationReason('task_paused')).toContain('任务暂停');
+ expect(cancellationReason('task_replaced')).toContain('新任务替换');
+ expect(cancellationReason('superseded')).toContain('新的检查或计划');
+ expect(cancellationReason()).toContain('取消来源未记录');
+ expect(cancellationReason('untrusted new instruction')).toContain('取消来源未记录');
+ expect(requestTermination({ ...waiting, phase: 'cancelled' })).toContain('取消来源未记录');
+ });
+
+ it('区分三层超时、未完成输出与未确认来源的499', () => {
+ expect(requestTermination({ ...waiting, phase: 'failed', timeout_scope: 'request' })).toContain('单次模型请求');
+ expect(requestTermination({ ...waiting, phase: 'failed', timeout_scope: 'transport' })).toContain('连接或读写超时');
+ expect(requestTermination({ ...waiting, phase: 'failed', timeout_scope: 'final_review' })).toContain('最终验收总预算');
+ expect(requestTermination({ ...waiting, phase: 'cancelled', cancel_reason: 'outer_timeout', timeout_scope: 'outer' })).toContain('外层等待时限');
+ expect(requestTermination({ ...waiting, phase: 'failed', http_status: 499 })).toContain('来源尚未确认');
+ expect(requestTermination({ ...waiting, phase: 'failed', category: 'connection' })).toContain('来源需结合服务端记录');
+ expect(requestTermination({ ...waiting, phase: 'failed', response_guard: { reason: 'missing_completion' } })).toContain('本次输出未被采用');
+ expect(requestTermination({ ...waiting, phase: 'completed' })).toBe('');
+ });
+
+ it('将本次尝试、上次重试、链路编号与结束原因一起展示', () => {
+ const fixture = TestBed.createComponent(LlmRequestStatusComponent);
+ fixture.componentRef.setInput('sessionId', 'cancelled'); fixture.detectChanges();
+ TestBed.inject(HttpTestingController).expectOne('/api/sessions/cancelled/request-diagnostics').flush({ requests: [{
+ ...waiting, active: false, phase: 'cancelled', cancel_reason: 'guidance_replaced',
+ cancel_phase: 'retry_wait', previous_attempt_id: 'attempt-before', client_request_id: 'client-wire-id',
+ server_request_id: 'server-wire-id', response_id: 'resp-from-server', server_trace_id: 'gateway-trace',
+ traceparent: '00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01', ended_at: Date.now() / 1000
+ }] });
+ fixture.detectChanges();
+ const text = fixture.nativeElement.textContent;
+ for (const value of ['attempt-before', 'client-wire-id', 'server-wire-id', 'resp-from-server', 'gateway-trace', '新指导替换了旧请求', '中断阶段', '等待重试']) expect(text).toContain(value);
+ expect(fixture.nativeElement.querySelector('.request-live')).toBeNull();
+ fixture.destroy();
+ });
});
diff --git a/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.ts b/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.ts
index cb1f241..ca42cb5 100644
--- a/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.ts
+++ b/apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.ts
@@ -9,6 +9,10 @@ export interface ModelRequest {
attempt: number; max_retries: number; timeout_seconds: number; elapsed_seconds: number;
started_at: number; headers_at?: number; first_output_at?: number; last_output_at?: number;
ended_at?: number; retry_at?: number; http_status?: number; server_request_id?: string;
+ client_request_id?: string | null; previous_attempt_id?: string | null; response_id?: string;
+ server_trace_id?: string | null; traceparent?: string;
+ cancel_reason?: string; cancel_phase?: string; timeout_scope?: string;
+ response_guard?: { reason: string; status?: string };
error?: string; error_type?: string; failure_phase?: string;
output_chunks?: number; tool_argument_chars?: number; max_output_tokens?: number | null;
tools?: { name: string | null; argument_chars: number }[];
@@ -24,6 +28,29 @@ export interface FinalReview {
total_seconds: number; elapsed_seconds: number; remaining_seconds: number;
request_count: number; retry_count: number; request_attempt?: number; max_retries: number;
request_phase?: string; last_failure_category?: string;
+ cancel_reason?: string;
+}
+
+export function cancellationReason(reason?: string): string {
+ return ({
+ user_stop: '用户停止了任务。',
+ guidance_replaced: '新指导替换了旧请求,任务可按新指导继续。',
+ task_paused: '任务暂停,已中断当前请求。',
+ task_replaced: '新任务替换了当前任务。',
+ superseded: '新的检查或计划替换了当前请求。',
+ outer_timeout: '外层等待时限已到,取消了当前请求。'
+ } as Record)[reason || ''] || '取消来源未记录,无法确定是否由用户停止、指导或外层时限触发。';
+}
+
+export function requestTermination(request: ModelRequest): string {
+ if (request.phase === 'cancelled') return cancellationReason(request.cancel_reason);
+ if (request.timeout_scope === 'final_review') return '最终验收总预算已用尽,本次请求已中断。';
+ if (request.timeout_scope === 'request') return '单次模型请求时限已到。';
+ if (request.timeout_scope === 'transport') return '模型连接或读写超时。';
+ if (request.http_status === 499) return '服务返回 HTTP 499;来源尚未确认,需对照本地记录和服务端日志。';
+ if (request.response_guard) return '模型响应未完整结束或工具参数无效,本次输出未被采用。';
+ if (request.category === 'connection') return '模型连接中断或不可用,来源需结合服务端记录核对。';
+ return '';
}
export function reviewStage(review: FinalReview): string {
@@ -73,6 +100,8 @@ export class LlmRequestStatusComponent {
phase = requestPhase;
hint = requestHint;
reviewStage = reviewStage;
+ termination = requestTermination;
+ cancellationReason = cancellationReason;
constructor() {
effect(() => this.activeChange.emit(this.active().length > 0 || this.activeReviews().length > 0));
diff --git a/apps/showcase_ui/src/app/components/model-selector/model-selector.component.ts b/apps/showcase_ui/src/app/components/model-selector/model-selector.component.ts
index 9aa2340..cc16c88 100644
--- a/apps/showcase_ui/src/app/components/model-selector/model-selector.component.ts
+++ b/apps/showcase_ui/src/app/components/model-selector/model-selector.component.ts
@@ -24,19 +24,19 @@ let nextId = 0;
}
`,
styles: [`
- :host { display: grid; gap: 7px; min-width: 0; color: #334155; font-size: 13px; }
+ :host { display: grid; gap: 7px; min-width: 0; color: var(--app-text-control, #334155); font-size: 13px; }
label { font-weight: 600; line-height: 1.5; }
.selector { position: relative; min-width: 0; }
select, input { box-sizing: border-box; width: 100%; min-width: 0; min-height: 40px;
- padding: 10px 12px; border: 1px solid #dbe3ec; border-radius: 10px; background: #fff;
+ padding: 10px 12px; border: 1px solid var(--app-border, #dbe3ec); border-radius: 10px; background: var(--app-surface, #fff);
color: inherit; font: inherit; }
select { appearance: none; padding-right: 36px; cursor: pointer; text-overflow: ellipsis; }
.selector > span { position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
- pointer-events: none; font-size: 19px; color: #64748b; }
- select:hover:not(:disabled), input:hover:not(:disabled) { border-color: #a5b4fc; }
- select:focus-visible, input:focus-visible { outline: 2px solid #a5b4fc; outline-offset: 2px; }
+ pointer-events: none; font-size: 19px; color: var(--app-text-muted, #64748b); }
+ select:hover:not(:disabled), input:hover:not(:disabled) { border-color: var(--app-primary-border, #a5b4fc); }
+ select:focus-visible, input:focus-visible { outline: 2px solid var(--app-primary-border, #a5b4fc); outline-offset: 2px; }
select:disabled, input:disabled { opacity: .5; cursor: default; }
- option { background: #fff; color: #334155; }
+ option { background: var(--app-surface, #fff); color: var(--app-text-control, #334155); }
`]
})
export class ModelSelectorComponent {
diff --git a/apps/showcase_ui/src/app/components/model-settings/capability-settings.component.ts b/apps/showcase_ui/src/app/components/model-settings/capability-settings.component.ts
index 3b20abd..986e177 100644
--- a/apps/showcase_ui/src/app/components/model-settings/capability-settings.component.ts
+++ b/apps/showcase_ui/src/app/components/model-settings/capability-settings.component.ts
@@ -29,7 +29,7 @@ interface Capabilities {
template: `
`,
- styles: [`section { margin-top: 24px; padding-top: 20px; border-top: 1px solid #64748b55; }
- h3 { margin: 0 0 8px; } p, small { color: #64748b; line-height: 1.6; }
+ styles: [`section { margin-top: 24px; padding-top: 20px; border-top: 1px solid rgb(var(--app-border-control-rgb, 100 116 139) / 0.3333); }
+ h3 { margin: 0 0 8px; } p, small { color: var(--app-text-muted, #64748b); line-height: 1.6; }
label { display: flex; gap: 12px; align-items: flex-start; padding: 14px 0; cursor: pointer; }
- input { width: 20px; height: 20px; accent-color: #0284c7; flex-shrink: 0; }
+ input { width: 20px; height: 20px; accent-color: var(--app-info-text, #0284c7); flex-shrink: 0; }
small { display: block; margin-top: 4px; } button { padding: 10px 18px; border-radius: 10px; border: 1px solid #38bdf877; background: #0284c7; color: white; font: inherit; cursor: pointer; }
- button:disabled { opacity: .5; cursor: default; } .error { color: #be123c; }
- .execution-settings { margin: 12px 0 20px; padding: 12px 14px; border: 1px solid #cbd5e1; border-radius: 10px; background: #f8fafc; }
- summary { cursor: pointer; font-weight: 600; color: #334155; }
+ button:disabled { opacity: .5; cursor: default; } .error { color: var(--app-error-text, #be123c); }
+ .execution-settings { margin: 12px 0 20px; padding: 12px 14px; border: 1px solid var(--app-border-control, #cbd5e1); border-radius: 10px; background: var(--app-surface-soft, #f8fafc); }
+ summary { cursor: pointer; font-weight: 600; color: var(--app-text-control, #334155); }
.number-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; }
- .number-fields label { display: block; padding: 0; color: #334155; }
- .number-fields input, .number-fields select { box-sizing: border-box; display: block; width: 100%; height: auto; margin-top: 8px; padding: 9px; border: 1px solid #cbd5e1; border-radius: 8px; font: inherit; }
- section {font-size:13px;border-color:#e2e8f0}h3 {font-size:15px;color:#334155}
- section > label {align-items:center;flex-direction:row-reverse;justify-content:space-between;border-bottom:1px solid #f1f5f9;gap:20px}section > label > span {flex:1}small {font-size:12px}
- input[role=switch] {appearance:none;position:relative;width:36px;height:22px;border:1px solid #cbd5e1;border-radius:20px;background:#e2e8f0;cursor:pointer;transition:background .15s}
- input[role=switch]::after {content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 3px #0f172a25;transition:transform .15s}
- input[role=switch]:checked {background:#6366f1;border-color:#6366f1}input[role=switch]:checked::after {transform:translateX(14px)}input[role=switch]:focus-visible {outline:2px solid #a5b4fc;outline-offset:3px}input:disabled {opacity:.5;cursor:default}
- button {background:#4f46e5;border-color:#4f46e5;border-radius:12px;font-weight:600}.execution-settings {border-color:#e2e8f0;border-radius:14px;padding:14px 16px}.number-fields input,.number-fields select {background:#fff;border-color:#dbe3ec}
+ .number-fields label { display: block; padding: 0; color: var(--app-text-control, #334155); }
+ .number-fields input, .number-fields select { box-sizing: border-box; display: block; width: 100%; height: auto; margin-top: 8px; padding: 9px; border: 1px solid var(--app-border-control, #cbd5e1); border-radius: 8px; font: inherit; }
+ section {font-size:13px;border-color:var(--app-border, #e2e8f0)}h3 {font-size:15px;color:var(--app-text-control, #334155)}
+ section > label {align-items:center;flex-direction:row-reverse;justify-content:space-between;border-bottom:1px solid var(--app-border, #f1f5f9);gap:20px}section > label > span {flex:1}small {font-size:12px}
+ input[role=switch] {appearance:none;position:relative;width:36px;height:22px;border:1px solid var(--app-border-control, #cbd5e1);border-radius:20px;background:var(--app-surface-raised, #e2e8f0);cursor:pointer;transition:background .15s}
+ input[role=switch]::after {content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--app-surface, #fff);box-shadow:0 1px 3px #0f172a25;transition:transform .15s}
+ input[role=switch]:checked {background:#6366f1;border-color:#6366f1}input[role=switch]:checked::after {transform:translateX(14px)}input[role=switch]:focus-visible {outline:2px solid var(--app-primary-border, #a5b4fc);outline-offset:3px}input:disabled {opacity:.5;cursor:default}
+ button {background:#4f46e5;border-color:#4f46e5;border-radius:12px;font-weight:600}.execution-settings {border-color:var(--app-border, #e2e8f0);border-radius:14px;padding:14px 16px}.number-fields input,.number-fields select {background:var(--app-surface, #fff);border-color:var(--app-border, #dbe3ec)}
@media(max-width: 480px) { .number-fields { grid-template-columns: 1fr; } }`]
})
export class CapabilitySettingsComponent implements OnInit {
changed = output();
private http = inject(HttpClient);
+ private savedConfig: Capabilities | null = null;
config: Capabilities = { adb_auto_connect: true, adb_discovery_interval_seconds: 10, adb_discovery_timeout_seconds: 5, video_enabled: false, recording_enabled: false, outputter_enabled: true, flash_final_check_enabled: false, step_summarizer_enabled: true, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
busy = signal(false); loading = signal(true); loaded = signal(false); message = signal(''); error = signal('');
ngOnInit(): void {
this.http.get('/api/system/capability-settings').pipe(timeout(UI_READ_TIMEOUT_MS), finalize(() => this.loading.set(false))).subscribe({
- next: config => { this.config = config; this.loaded.set(true); this.changed.emit(config); }, error: () => this.error.set('功能配置加载失败,请刷新页面重试。')
+ next: config => { this.savedConfig = structuredClone(config); this.config = config; this.loaded.set(true); this.changed.emit(config); }, error: () => this.error.set('功能配置加载失败,请刷新页面重试。')
+ });
+ }
+ saveFlashFinalCheck(enabled: boolean): void {
+ if (this.busy() || !this.loaded() || !this.savedConfig) return;
+ const previous = this.savedConfig.flash_final_check_enabled;
+ this.config.flash_final_check_enabled = enabled;
+ this.busy.set(true); this.message.set('正在保存 Flash 判定设置…'); this.error.set('');
+ // Preserve other settings that are still being edited, including invalid drafts.
+ const update = { ...this.savedConfig, flash_final_check_enabled: enabled };
+ this.http.post('/api/system/capability-settings', update).pipe(finalize(() => this.busy.set(false))).subscribe({
+ next: config => {
+ this.savedConfig = structuredClone(config);
+ this.config.flash_final_check_enabled = config.flash_final_check_enabled;
+ this.changed.emit(config);
+ this.message.set(`Flash 最终 AI 判定已${config.flash_final_check_enabled ? '开启' : '关闭'},尚未开始的任务将使用此设置,无需重启服务。`);
+ },
+ error: () => {
+ this.config.flash_final_check_enabled = previous;
+ this.message.set('');
+ this.error.set('Flash 判定设置保存失败,开关已恢复。请检查连接后重新切换。');
+ }
});
}
save(): void {
@@ -113,7 +135,7 @@ export class CapabilitySettingsComponent implements OnInit {
}
this.busy.set(true); this.message.set(''); this.error.set('');
this.http.post('/api/system/capability-settings', this.config).pipe(finalize(() => this.busy.set(false))).subscribe({
- next: config => { this.config = config; this.changed.emit(config); this.message.set('功能设置已保存,新任务将使用此配置。'); },
+ next: config => { this.savedConfig = structuredClone(config); this.config = config; this.changed.emit(config); this.message.set('功能设置已保存,新任务将使用此配置。'); },
error: () => this.error.set('保存失败,请检查服务器连接和 .env 文件权限。')
});
}
diff --git a/apps/showcase_ui/src/app/components/model-settings/model-settings.component.spec.ts b/apps/showcase_ui/src/app/components/model-settings/model-settings.component.spec.ts
index 5a7d289..1bee664 100644
--- a/apps/showcase_ui/src/app/components/model-settings/model-settings.component.spec.ts
+++ b/apps/showcase_ui/src/app/components/model-settings/model-settings.component.spec.ts
@@ -221,7 +221,7 @@ describe('持久化模型与功能设置', () => {
http.expectNone('/api/system/capability-settings');
});
- it('Flash 最终 AI 判定默认关闭,保存失败保留选择,成功后重新打开仍保留', async () => {
+ it('Flash 判定切换即保存,失败恢复原值,重新切换后持久生效', async () => {
const fixture = TestBed.createComponent(CapabilitySettingsComponent);
const capabilities = { ...fixture.componentInstance.config };
fixture.detectChanges();
@@ -234,17 +234,20 @@ describe('持久化模型与功能设置', () => {
expect(toggle.checked).toBeFalse();
expect(root.textContent).toContain('不影响 Pro');
toggle.click(); fixture.detectChanges(); await fixture.whenStable();
- root.querySelector('button')!.click();
let request = http.expectOne('/api/system/capability-settings');
expect(request.request.body.flash_final_check_enabled).toBeTrue();
+ expect(fixture.componentInstance.busy()).toBeTrue();
request.flush({}, {status:500,statusText:'Error'});
fixture.detectChanges();
- expect(fixture.componentInstance.config.flash_final_check_enabled).toBeTrue();
+ await fixture.whenStable();
+ expect(fixture.componentInstance.config.flash_final_check_enabled).toBeFalse();
+ expect(toggle.checked).toBeFalse();
expect(fixture.componentInstance.error()).toContain('保存失败');
- root.querySelector('button')!.click();
+ toggle.click(); fixture.detectChanges(); await fixture.whenStable();
request = http.expectOne('/api/system/capability-settings');
const saved = request.request.body;
request.flush(saved);
+ expect(fixture.componentInstance.message()).toContain('无需重启');
fixture.destroy();
const reopened = TestBed.createComponent(CapabilitySettingsComponent);
reopened.detectChanges();
@@ -253,4 +256,24 @@ describe('持久化模型与功能设置', () => {
expect(reopened.nativeElement.querySelector('[aria-label="Flash 最终 AI 判定与分析"]').checked).toBeTrue();
reopened.destroy();
});
+
+ it('自动保存 Flash 判定不提交其他未保存或无效的草稿', async () => {
+ const fixture = TestBed.createComponent(CapabilitySettingsComponent);
+ const capabilities = { ...fixture.componentInstance.config };
+ fixture.detectChanges();
+ http.expectOne('/api/system/capability-settings').flush(capabilities);
+ fixture.detectChanges(); await fixture.whenStable();
+ fixture.componentInstance.config.task_timeout_seconds = 0;
+ fixture.componentInstance.config.recording_enabled = true;
+ const toggle: HTMLInputElement = fixture.nativeElement.querySelector('[aria-label="Flash 最终 AI 判定与分析"]');
+ toggle.click(); fixture.detectChanges(); await fixture.whenStable();
+ const request = http.expectOne('/api/system/capability-settings');
+ expect(request.request.body.flash_final_check_enabled).toBeTrue();
+ expect(request.request.body.task_timeout_seconds).toBe(1800);
+ expect(request.request.body.recording_enabled).toBeFalse();
+ request.flush(request.request.body);
+ expect(fixture.componentInstance.config.task_timeout_seconds).toBe(0);
+ expect(fixture.componentInstance.config.recording_enabled).toBeTrue();
+ fixture.destroy();
+ });
});
diff --git a/apps/showcase_ui/src/app/components/model-settings/model-settings.component.ts b/apps/showcase_ui/src/app/components/model-settings/model-settings.component.ts
index fb2b074..6f6d54a 100644
--- a/apps/showcase_ui/src/app/components/model-settings/model-settings.component.ts
+++ b/apps/showcase_ui/src/app/components/model-settings/model-settings.component.ts
@@ -78,27 +78,27 @@ interface ModelSettings {
`,
styles: [`
- .model-settings { display: grid; gap: 10px; color: #1e293b; }
- p, small { margin: 0; line-height: 1.6; color: #64748b; }
+ .model-settings { display: grid; gap: 10px; color: var(--app-text-control, #1e293b); }
+ p, small { margin: 0; line-height: 1.6; color: var(--app-text-muted, #64748b); }
label { font-weight: 600; } label span { font-weight: 400; font-size: 12px; }
- input, select { box-sizing: border-box; width: 100%; padding: 12px; border-radius: 10px; border: 1px solid #64748b66;
- background: #f8fafc; color: inherit; font: inherit; }
+ input, select { box-sizing: border-box; width: 100%; padding: 12px; border-radius: 10px; border: 1px solid rgb(var(--app-border-control-rgb, 100 116 139) / 0.4000);
+ background: var(--app-surface-soft, #f8fafc); color: inherit; font: inherit; }
input:focus, select:focus { outline: 2px solid #38bdf8; }
.number-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 8px; }
.number-fields input { margin-top: 8px; }
- .recovery-settings { border: 1px solid #cbd5e1; border-radius: 10px; background: #f8fafc; padding: 10px 14px; }
- .recovery-settings summary { cursor: pointer; font-weight: 600; color: #334155; }
+ .recovery-settings { border: 1px solid var(--app-border-control, #cbd5e1); border-radius: 10px; background: var(--app-surface-soft, #f8fafc); padding: 10px 14px; }
+ .recovery-settings summary { cursor: pointer; font-weight: 600; color: var(--app-text-control, #334155); }
.recovery-content { display: grid; gap: 10px; padding-top: 12px; }
.recovery-content label input, .recovery-content label select { margin-top: 8px; }
.actions { display: flex; gap: 12px; margin-top: 8px; }
button { padding: 11px 20px; border-radius: 10px; border: 1px solid #38bdf877; background: #0284c7; color: white; cursor: pointer; font: inherit; }
- button:disabled { opacity: .5; cursor: default; } .success { color: #047857; } .error { color: #be123c; }
+ button:disabled { opacity: .5; cursor: default; } .success { color: var(--app-success-text, #047857); } .error { color: var(--app-error-text, #be123c); }
button:focus-visible { outline: 2px solid #0284c7; outline-offset: 2px; }
small { overflow-wrap: anywhere; } @media(max-width: 480px) { .number-fields { grid-template-columns: 1fr; } }
- .model-settings {gap:12px;font-size:13px} label {margin-top:6px}input,select {background:#fff;border-color:#dbe3ec;padding:11px 12px}
- input:focus,select:focus {outline:2px solid #a5b4fc;outline-offset:2px}
- .actions {border-top:1px solid #e2e8f0;padding-top:16px;align-items:center}.actions button {min-height:40px;background:#4f46e5;border-color:#4f46e5;font-weight:600;border-radius:12px}.actions button[type=button] {background:#fff;border-color:#dbe3ec;color:#475569}
- .recovery-settings {border-color:#e2e8f0;border-radius:14px;padding:14px 16px}.recovery-settings summary {font-size:13px}
+ .model-settings {gap:12px;font-size:13px} label {margin-top:6px}input,select {background:var(--app-surface, #fff);border-color:var(--app-border, #dbe3ec);padding:11px 12px}
+ input:focus,select:focus {outline:2px solid var(--app-primary-border, #a5b4fc);outline-offset:2px}
+ .actions {border-top:1px solid var(--app-border, #e2e8f0);padding-top:16px;align-items:center}.actions button {min-height:40px;background:#4f46e5;border-color:#4f46e5;font-weight:600;border-radius:12px}.actions button[type=button] {background:var(--app-surface, #fff);border-color:var(--app-border, #dbe3ec);color:var(--app-text-secondary, #475569)}
+ .recovery-settings {border-color:var(--app-border, #e2e8f0);border-radius:14px;padding:14px 16px}.recovery-settings summary {font-size:13px}
`]
})
export class ModelSettingsComponent implements OnInit {
diff --git a/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss b/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss
index 8c72398..296fc95 100644
--- a/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss
+++ b/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss
@@ -23,14 +23,14 @@
box-sizing: border-box;
display: inline-flex;
align-items: center;
- background: rgba(255, 255, 255, 0.72);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.72);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
- border: 1px solid rgba(226, 232, 240, 0.85);
+ border: 1px solid rgb(var(--app-border-rgb, 226 232 240) / 0.85);
border-radius: 18px;
padding: 3px;
gap: 3px;
- box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.06), 0 1px 2px rgba(0, 0, 0, 0.02), inset 0 1px 1px rgba(255, 255, 255, 0.9);
+ box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.06), 0 1px 2px rgba(0, 0, 0, 0.02), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.9);
user-select: none;
font-family: 'Google Sans', 'Google Sans Text', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
transition: background 0.25s cubic-bezier(0.4, 0, 0.2, 1),
@@ -38,15 +38,15 @@
border-color 0.25s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
- background: rgba(255, 255, 255, 0.85);
- box-shadow: 0 4px 14px -2px rgba(15, 23, 42, 0.1), 0 2px 4px rgba(0, 0, 0, 0.03), inset 0 1px 1px rgba(255, 255, 255, 1);
- border-color: rgba(203, 213, 225, 0.9);
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.85);
+ box-shadow: 0 4px 14px -2px rgba(15, 23, 42, 0.1), 0 2px 4px rgba(0, 0, 0, 0.03), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 1);
+ border-color: rgb(var(--app-border-control-rgb, 203 213 225) / 0.9);
}
.app-version {
padding: 0 10px 0 8px;
- border-left: 1px solid #e2e8f0;
- color: #64748b;
+ border-left: 1px solid var(--app-border, #e2e8f0);
+ color: var(--app-text-muted, #64748b);
font-size: 11px;
font-weight: 500;
line-height: 18px;
@@ -65,7 +65,7 @@
border-radius: 15px;
font-size: 13px;
font-weight: 600;
- color: #475569;
+ color: var(--app-text-secondary, #475569);
text-decoration: none;
cursor: pointer;
line-height: 1;
@@ -83,24 +83,24 @@
display: inline-flex;
align-items: center;
justify-content: center;
- color: #64748b;
+ color: var(--app-text-muted, #64748b);
transition: color 0.15s ease, transform 0.15s ease;
}
.tab-label {
- color: #475569;
+ color: var(--app-text-secondary, #475569);
transition: color 0.15s ease;
}
&:hover:not(.active) {
- background: rgba(255, 255, 255, 0.7);
- color: #0f172a;
+ background: rgb(var(--app-surface-rgb, 255 255 255) / 0.7);
+ color: var(--app-text-strong, #0f172a);
.tab-icon {
- color: #1a73e8;
+ color: var(--app-primary-text, #1a73e8);
}
.tab-label {
- color: #0f172a;
+ color: var(--app-text-strong, #0f172a);
}
}
@@ -110,7 +110,7 @@
-webkit-backdrop-filter: blur(12px);
color: #ffffff !important;
font-weight: 700;
- box-shadow: 0 2px 8px rgba(26, 115, 232, 0.35), inset 0 1px 1px rgba(255, 255, 255, 0.35);
+ box-shadow: 0 2px 8px rgba(26, 115, 232, 0.35), inset 0 1px 1px rgb(var(--app-highlight-rgb, 255 255 255) / 0.35);
.tab-icon,
.tab-label,
@@ -124,3 +124,40 @@
}
}
}
+
+.theme-toggle {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 30px;
+ width: 30px;
+ height: 30px;
+ padding: 0;
+ border: 0;
+ border-radius: 50%;
+ background: transparent;
+ color: var(--app-text-secondary, #475569);
+ cursor: pointer;
+
+ .material-symbols-outlined { font-size: 16px; }
+ &:hover {
+ background: var(--app-surface-raised, #eef2f7);
+ color: var(--app-primary-text, #1a73e8);
+ }
+}
+
+.floating-nav-switcher .nav-tab-btn:focus-visible,
+.theme-toggle:focus-visible {
+ outline: 2px solid var(--app-primary-text, #1a73e8);
+ outline-offset: 2px;
+}
+
+@media (max-width: 600px) {
+ .floating-nav-switcher {
+ left: 12px;
+ max-width: calc(100vw - 24px);
+ .nav-tab-btn { padding: 0 10px; }
+ .nav-tab-btn .tab-label { display: none; }
+ .app-version { padding: 0 6px; }
+ }
+}
diff --git a/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts b/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts
index c6239e9..06426f6 100644
--- a/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts
+++ b/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-import { Component, ChangeDetectionStrategy } from '@angular/core';
+import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { version } from '../../../../package.json';
+import { ThemeService } from '../../services/theme.service';
@Component({
selector: 'app-nav-switcher',
@@ -30,6 +31,7 @@ import { version } from '../../../../package.json';
routerLinkActive="active"
[routerLinkActiveOptions]="{exact: true}"
class="nav-tab-btn"
+ aria-label="新任务 / 首页"
title="返回首页开始新任务"
>
add_task
@@ -39,15 +41,22 @@ import { version } from '../../../../package.json';
routerLink="/workspace"
routerLinkActive="active"
class="nav-tab-btn"
+ aria-label="工作区"
title="打开工作区"
>
space_dashboard
工作区
-
+
extension插件
v{{version}}
+
`,
changeDetection: ChangeDetectionStrategy.Eager,
@@ -55,4 +64,5 @@ import { version } from '../../../../package.json';
})
export class NavSwitcherComponent {
readonly version = version;
+ readonly theme = inject(ThemeService);
}
diff --git a/apps/showcase_ui/src/app/components/plugin-tool-result/plugin-tool-result.component.spec.ts b/apps/showcase_ui/src/app/components/plugin-tool-result/plugin-tool-result.component.spec.ts
new file mode 100644
index 0000000..ca77fe5
--- /dev/null
+++ b/apps/showcase_ui/src/app/components/plugin-tool-result/plugin-tool-result.component.spec.ts
@@ -0,0 +1,68 @@
+import { TestBed } from '@angular/core/testing';
+import { PluginToolResultComponent } from './plugin-tool-result.component';
+import { PluginToolView } from '../../utils/plugin-tool-view.util';
+
+describe('readable plugin receipts', () => {
+ const makeView = (result: string): PluginToolView => ({
+ title: '观察电脑窗口', kind: '辅助观察', auxiliary: true, status: '已执行', args: [],
+ result, rawResult: JSON.stringify({ result, call_id: 'original-call' }, null, 2),
+ facts: [{ key: '窗口', value: 'Demo' }, { key: '截图', value: '1280 × 720' }]
+ });
+ const mount = async (view: PluginToolView) => {
+ await TestBed.configureTestingModule({ imports: [PluginToolResultComponent] }).compileComponents();
+ const fixture = TestBed.createComponent(PluginToolResultComponent);
+ fixture.componentRef.setInput('view', view);
+ fixture.detectChanges();
+ return fixture;
+ };
+
+ it('keeps a long control tree and the original payload folded and fully accessible', async () => {
+ const fullText = Array.from({ length: 200 }, (_, i) => `- [${i}] Button "Item ${i}"`).join('\n');
+ const view = makeView(fullText);
+ const fixture = await mount(view);
+ const root: HTMLElement = fixture.nativeElement;
+ expect(root.querySelector('dl')!.textContent).toContain('1280 × 720');
+ expect(root.querySelector('.receipt-preview')!.textContent!.length).toBeLessThan(1100);
+ const textDetails = root.querySelector('.receipt-text')!;
+ const rawDetails = root.querySelector('.receipt-raw')!;
+ expect(textDetails.open).toBeFalse();
+ expect(rawDetails.open).toBeFalse();
+ textDetails.querySelector('summary')!.click();
+ expect(textDetails.open).toBeTrue();
+ expect(textDetails.querySelector('pre')!.textContent).toBe(fullText);
+ expect(rawDetails.querySelector('pre')!.textContent).toBe(view.rawResult);
+ });
+
+ it('renders untrusted tool text as text and keeps short results readable', async () => {
+ const text = '
\n已观察,未验证业务结果';
+ const fixture = await mount(makeView(text));
+ const root: HTMLElement = fixture.nativeElement;
+ expect(root.querySelector('.receipt-preview')!.textContent).toBe(text);
+ expect(root.querySelector('img')).toBeNull();
+ expect(root.querySelector('.receipt-text')).toBeNull();
+ });
+
+ it('reflects a new receipt without resetting an open raw payload', async () => {
+ const fixture = await mount(makeView('执行中'));
+ const raw = fixture.nativeElement.querySelector('.receipt-raw') as HTMLDetailsElement;
+ raw.querySelector('summary')!.click();
+ fixture.componentRef.setInput('view', makeView('已返回新的窗口观察'));
+ fixture.detectChanges();
+ expect(raw.open).toBeTrue();
+ expect(fixture.nativeElement.querySelector('.receipt-preview').textContent).toBe('已返回新的窗口观察');
+ expect(raw.querySelector('pre')!.textContent).toContain('已返回新的窗口观察');
+ });
+
+ it('keeps long window labels and unbroken receipt text inside a narrow container', async () => {
+ const view = makeView('long-unbroken-result'.repeat(200));
+ view.facts[0].value = 'VeryLongWindowTitle'.repeat(30);
+ const fixture = await mount(view);
+ const root = fixture.nativeElement as HTMLElement;
+ root.style.width = '240px';
+ fixture.detectChanges();
+ expect(root.clientWidth).toBe(240);
+ expect(root.scrollWidth).toBeLessThanOrEqual(root.clientWidth);
+ const label = root.querySelector('dd')!;
+ expect(label.scrollWidth).toBeLessThanOrEqual(label.clientWidth);
+ });
+});
diff --git a/apps/showcase_ui/src/app/components/plugin-tool-result/plugin-tool-result.component.ts b/apps/showcase_ui/src/app/components/plugin-tool-result/plugin-tool-result.component.ts
new file mode 100644
index 0000000..01d8af6
--- /dev/null
+++ b/apps/showcase_ui/src/app/components/plugin-tool-result/plugin-tool-result.component.ts
@@ -0,0 +1,56 @@
+import { Component, computed, input } from '@angular/core';
+import { PluginToolView } from '../../utils/plugin-tool-view.util';
+
+/** Readable presentation only: the full receipt and model context stay intact. */
+@Component({
+ selector: 'app-plugin-tool-result',
+ standalone: true,
+ template: `
+ @if (view().facts.length) {
+
+ @for (fact of view().facts; track fact.key) {
+ - {{ fact.key }}
- {{ fact.value }}
+ }
+
+ }
+ @if (view().result) {
+ {{ preview() }}
+ @if (isLong()) {
+
+ 完整文本与控件树 {{ view().result.length }} 字符
+ {{ view().result }}
+
+ }
+ }
+ @if (view().rawResult) {
+
+ 原始返回 payload 完整保留
+ {{ view().rawResult }}
+
+ }
+ `,
+ styles: [`
+ :host { display: block; min-width: 0; width: 100%; user-select: text; cursor: auto; }
+ .receipt-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr)); gap: 8px 16px; margin: 0 0 12px; }
+ .receipt-facts div { min-width: 0; }
+ dt { font-size: 11px; color: var(--app-text-muted, #64748b); }
+ dd { margin: 3px 0 0; font-size: 12px; color: var(--app-text-control, #334155); overflow-wrap: anywhere; }
+ pre { margin: 0; padding: 10px 12px; border-radius: 6px; font: 12px/1.6 Consolas, monospace; color: var(--app-text-control, #334155); background: var(--app-surface-soft, #f8fafc); white-space: pre-wrap; overflow-wrap: anywhere; }
+ .receipt-preview { max-height: 180px; overflow: auto; }
+ .receipt-detail { margin-top: 10px; border: 1px solid var(--app-border, #e2e8f0); border-radius: 6px; min-width: 0; }
+ summary { padding: 9px 10px; font-size: 12px; color: var(--app-text-secondary, #475569); cursor: pointer; overflow-wrap: anywhere; }
+ summary span { margin-left: 8px; font-size: 11px; color: var(--app-text-muted, #64748b); }
+ summary:focus-visible { outline: 2px solid var(--app-primary-text, #1a73e8); outline-offset: 2px; }
+ .receipt-detail pre { max-height: 400px; overflow: auto; border-top: 1px solid var(--app-border, #e2e8f0); border-radius: 0 0 6px 6px; }
+ `]
+})
+export class PluginToolResultComponent {
+ view = input.required();
+ private readonly previewLimit = 1000;
+ preview = computed(() => {
+ const text = this.view().result;
+ const excerpt = text.split('\n').slice(0, 7).join('\n').slice(0, this.previewLimit);
+ return excerpt.length < text.length ? `${excerpt}\n…` : text;
+ });
+ isLong = computed(() => this.preview() !== this.view().result);
+}
diff --git a/apps/showcase_ui/src/app/components/report-markdown/report-markdown.component.spec.ts b/apps/showcase_ui/src/app/components/report-markdown/report-markdown.component.spec.ts
index 344e940..f650333 100644
--- a/apps/showcase_ui/src/app/components/report-markdown/report-markdown.component.spec.ts
+++ b/apps/showcase_ui/src/app/components/report-markdown/report-markdown.component.spec.ts
@@ -4,7 +4,7 @@ import { ReportMarkdownComponent } from './report-markdown.component';
describe('报告 Markdown 展示', () => {
it('renders report headings, emphasis, lists, tables and code in both preview and full view', () => {
const fixture = TestBed.createComponent(ReportMarkdownComponent);
- fixture.componentRef.setInput('content', '### 双端布局报告\n- **手机**:`/zh`\n\n| 区域 | 结论 |\n| --- | --- |\n| 页底 | 已确认 |\n\n```text\n<原始证据>\n```');
+ fixture.componentRef.setInput('content', '### 双端布局报告\n- **手机**:`/zh`\n- [x] 已检查\n- [/] 进行中\n\n| 区域 | 结论 |\n| --- | ---: |\n| 页底 | 已确认 |\n\n```text\n<原始证据>\n```');
fixture.componentRef.setInput('compact', true);
fixture.detectChanges();
const view: HTMLElement = fixture.nativeElement;
@@ -12,6 +12,9 @@ describe('报告 Markdown 展示', () => {
expect(view.querySelector('li strong')?.textContent).toBe('手机');
expect(view.querySelector('li code')?.textContent).toBe('/zh');
expect(view.querySelector('td')?.textContent).toBe('页底');
+ expect(view.querySelector('td.md-align-right')?.textContent).toBe('已确认');
+ expect(view.querySelector('[aria-label="已完成"]')).not.toBeNull();
+ expect(view.querySelector('[aria-label="进行中"]')).not.toBeNull();
expect(view.querySelector('pre code')?.textContent).toBe('<原始证据>');
expect(view.querySelector('.compact')).not.toBeNull();
fixture.componentRef.setInput('compact', false);
diff --git a/apps/showcase_ui/src/app/components/report-markdown/report-markdown.component.ts b/apps/showcase_ui/src/app/components/report-markdown/report-markdown.component.ts
index 7d261ab..334c684 100644
--- a/apps/showcase_ui/src/app/components/report-markdown/report-markdown.component.ts
+++ b/apps/showcase_ui/src/app/components/report-markdown/report-markdown.component.ts
@@ -1,7 +1,7 @@
import { Component, computed, input } from '@angular/core';
import { renderMarkdownToHtml } from '../../utils/markdown-parser.util';
-/** Shared, sanitized presentation for report prose (not the editable task plan). */
+/** Shared presentation for notes, plans and reports; Angular sanitizes the HTML. */
@Component({
selector: 'app-report-markdown',
standalone: true,
@@ -14,21 +14,21 @@ import { renderMarkdownToHtml } from '../../utils/markdown-parser.util';
> div, p { margin: 4px 0; }
> :first-child { margin-top: 0; }
> :last-child { margin-bottom: 0; }
- h1,h2,h3,h4,h5,h6 { margin: 16px 0 6px; font-weight: 600; line-height: 1.5; color: #0f172a; }
+ h1,h2,h3,h4,h5,h6 { margin: 16px 0 6px; font-weight: 600; line-height: 1.5; color: var(--app-text-strong, #0f172a); }
h1 { font-size: 18px; } h2 { font-size: 16px; } h3,h4,h5,h6 { font-size: 14px; }
strong { font-weight: 600; }
ul,ol { margin: 6px 0; padding-left: 24px; }
li { margin: 4px 0; }
- blockquote { margin: 10px 0; padding: 6px 12px; border-left: 3px solid #93c5fd; background: #f8fafc; }
- code { font-family: Consolas, monospace; font-size: .92em; border-radius: 4px; padding: 1px 4px; background: #f1f5f9; }
- pre { max-width: 100%; overflow: auto; padding: 10px 12px; border-radius: 6px; background: #f1f5f9; white-space: pre; }
+ blockquote { margin: 10px 0; padding: 6px 12px; border-left: 3px solid var(--app-primary-border, #93c5fd); background: var(--app-surface-soft, #f8fafc); }
+ code { font-family: Consolas, monospace; font-size: .92em; border-radius: 4px; padding: 1px 4px; background: var(--app-surface-soft, #f1f5f9); }
+ pre { max-width: 100%; overflow: auto; padding: 10px 12px; border-radius: 6px; background: var(--app-surface-soft, #f1f5f9); white-space: pre; }
pre code { padding: 0; background: none; }
.md-table-scroll { max-width: 100%; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 12px; }
- th,td { border: 1px solid #cbd5e1; padding: 7px 9px; text-align: left; vertical-align: top; }
- th { background: #eff6ff; font-weight: 600; }
+ th,td { border: 1px solid var(--app-border-control, #cbd5e1); padding: 7px 9px; text-align: left; vertical-align: top; }
+ th { background: var(--app-primary-soft, #eff6ff); font-weight: 600; }
.md-para-gap { height: 4px; margin: 0; }
- hr { border: 0; border-top: 1px solid #e2e8f0; margin: 12px 0; }
+ hr { border: 0; border-top: 1px solid var(--app-border, #e2e8f0); margin: 12px 0; }
}
`]
})
diff --git a/apps/showcase_ui/src/app/components/setting-slider/setting-slider.component.ts b/apps/showcase_ui/src/app/components/setting-slider/setting-slider.component.ts
index cf8fe10..ba3c4ab 100644
--- a/apps/showcase_ui/src/app/components/setting-slider/setting-slider.component.ts
+++ b/apps/showcase_ui/src/app/components/setting-slider/setting-slider.component.ts
@@ -12,13 +12,13 @@ import { Component, computed, input, output } from '@angular/core';
@if(hint()){{{hint()}}
}
`,
styles: [`
- :host{display:block;min-width:0;padding:14px 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:14px;font-size:13px;color:#334155}
+ :host{display:block;min-width:0;padding:14px 16px;background:var(--app-surface-soft, #f8fafc);border:1px solid var(--app-border, #e2e8f0);border-radius:14px;font-size:13px;color:var(--app-text-control, #334155)}
.slider-heading,.slider-ends{display:flex;align-items:center;justify-content:space-between;gap:10px}
- .slider-heading{font-weight:600}output{color:#4f46e5;font-size:12px;white-space:nowrap}.slider-ends{font-size:11px;color:#64748b}
- input{appearance:none;display:block;box-sizing:border-box;width:100%;height:6px;border:0;border-radius:10px;margin:20px 0 12px;background:linear-gradient(to right,#6366f1 var(--fill),#dbe3ee var(--fill));cursor:pointer}
- input::-webkit-slider-thumb{appearance:none;width:18px;height:18px;border-radius:50%;background:#fff;border:2px solid #6366f1;box-shadow:0 2px 6px #33415526}
- input::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:#fff;border:2px solid #6366f1}
- input:focus-visible{outline:2px solid #a5b4fc;outline-offset:5px}input:disabled{opacity:.5;cursor:default}p{margin:10px 0 0;font-size:12px;line-height:1.6;color:#64748b}
+ .slider-heading{font-weight:600}output{color:var(--app-exploration-text, #4f46e5);font-size:12px;white-space:nowrap}.slider-ends{font-size:11px;color:var(--app-text-muted, #64748b)}
+ input{appearance:none;display:block;box-sizing:border-box;width:100%;height:6px;border:0;border-radius:10px;margin:20px 0 12px;background:linear-gradient(to right,#6366f1 var(--fill),var(--app-primary-soft, #dbe3ee) var(--fill));cursor:pointer}
+ input::-webkit-slider-thumb{appearance:none;width:18px;height:18px;border-radius:50%;background:var(--app-surface, #fff);border:2px solid #6366f1;box-shadow:0 2px 6px #33415526}
+ input::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:var(--app-surface, #fff);border:2px solid #6366f1}
+ input:focus-visible{outline:2px solid var(--app-primary-border, #a5b4fc);outline-offset:5px}input:disabled{opacity:.5;cursor:default}p{margin:10px 0 0;font-size:12px;line-height:1.6;color:var(--app-text-muted, #64748b)}
`]
})
export class SettingSliderComponent {
diff --git a/apps/showcase_ui/src/app/components/task-batch-filter/task-batch-filter.component.ts b/apps/showcase_ui/src/app/components/task-batch-filter/task-batch-filter.component.ts
index 63f8922..f805bfc 100644
--- a/apps/showcase_ui/src/app/components/task-batch-filter/task-batch-filter.component.ts
+++ b/apps/showcase_ui/src/app/components/task-batch-filter/task-batch-filter.component.ts
@@ -12,7 +12,7 @@ import { AgentService } from '../../services/agent.service';
@if(agent.batchFilter() && !['__batch__','__single__','__exploration__'].includes(agent.batchFilter()) && !hasSelectedBatch()){}
@if(agent.batchFilter()){}
}`,
- styles: [`:host{display:block;margin:0 0 12px}.batch-filter{display:flex;align-items:flex-end;gap:8px;color:#475569;font-size:12px}label{flex:1;min-width:0}select{display:block;box-sizing:border-box;width:100%;height:34px;margin-top:5px;border:1px solid #cbd5e1;border-radius:7px;padding:0 9px;background:#fff;color:#1e293b;font:inherit}option{background:#fff;color:#1e293b}button{height:34px;white-space:nowrap;border:1px solid #cbd5e1;border-radius:7px;padding:0 9px;background:#fff;color:#475569;font:inherit;cursor:pointer}select:focus-visible,button:focus-visible{outline:2px solid #3b82f6;outline-offset:2px}.filter-error{font-size:12px;color:#b91c1c;margin-top:5px}.filter-error button{height:auto;padding:2px 6px}`]
+ styles: [`:host{display:block;margin:0 0 12px}.batch-filter{display:flex;align-items:flex-end;gap:8px;color:var(--app-text-secondary, #475569);font-size:12px}label{flex:1;min-width:0}select{display:block;box-sizing:border-box;width:100%;height:34px;margin-top:5px;border:1px solid var(--app-border-control, #cbd5e1);border-radius:7px;padding:0 9px;background:var(--app-surface, #fff);color:var(--app-text-control, #1e293b);font:inherit}option{background:var(--app-surface, #fff);color:var(--app-text-control, #1e293b)}button{height:34px;white-space:nowrap;border:1px solid var(--app-border-control, #cbd5e1);border-radius:7px;padding:0 9px;background:var(--app-surface, #fff);color:var(--app-text-secondary, #475569);font:inherit;cursor:pointer}select:focus-visible,button:focus-visible{outline:2px solid #3b82f6;outline-offset:2px}.filter-error{font-size:12px;color:var(--app-error-text, #b91c1c);margin-top:5px}.filter-error button{height:auto;padding:2px 6px}`]
})
export class TaskBatchFilterComponent {
agent = inject(AgentService); private http = inject(HttpClient);
diff --git a/apps/showcase_ui/src/app/components/task-history/task-history.component.ts b/apps/showcase_ui/src/app/components/task-history/task-history.component.ts
index 7e0d943..4adf722 100644
--- a/apps/showcase_ui/src/app/components/task-history/task-history.component.ts
+++ b/apps/showcase_ui/src/app/components/task-history/task-history.component.ts
@@ -25,9 +25,9 @@ import { AgentService } from '../../services/agent.service';
h3{display:flex;align-items:center;gap:6px;font-size:14px;margin:14px 0 8px}
.query-status{margin-left:auto;font-size:11px;font-weight:400;white-space:nowrap}
small{opacity:.6}
- input,select,button{font:inherit;color:inherit;border:1px solid #64748b55;border-radius:6px;padding:8px;background:transparent;box-sizing:border-box}
+ input,select,button{font:inherit;color:inherit;border:1px solid rgb(var(--app-border-control-rgb, 100 116 139) / 0.3333);border-radius:6px;padding:8px;background:transparent;box-sizing:border-box}
input{width:100%}
- select option{background:white;color:#1e293b}
+ select option{background:var(--app-surface, white);color:var(--app-text-control, #1e293b)}
.filters{display:flex;gap:6px;flex-wrap:wrap;margin:8px 0}
.filters label{flex:1;min-width:110px}
.filters input,.filters select{display:block;width:100%}
@@ -35,26 +35,26 @@ import { AgentService } from '../../services/agent.service';
.row{display:flex;flex-direction:column;gap:6px;width:100%;text-align:left;cursor:pointer;margin:5px 0;overflow-wrap:anywhere}
.row strong{font-weight:600;display:flex;align-items:center;min-height:28px;padding-left:33px}
.row.selected{border-color:#60a5fa;background:#3b82f615}
- .history-item{position:relative;border:1px solid #cbd5e1;border-left:3px solid #94a3b8;border-radius:8px;margin:6px 0;padding:8px;background:#fff}
+ .history-item{position:relative;border:1px solid var(--app-border-control, #cbd5e1);border-left:3px solid var(--app-border-control, #94a3b8);border-radius:8px;margin:6px 0;padding:8px;background:var(--app-surface, #fff)}
.history-item.status-completed,.history-item.status-success{border-left-color:#16a34a}
- .history-item.status-failed{border-left-color:#dc2626;background:#fffafa}
- .history-item.status-cancelled,.history-item.status-interrupted{border-left-color:#d97706;background:#fffdf8}
- .history-item.status-skipped{border-left-color:#64748b}
- .history-item:has(.row.selected){border-color:#60a5fa;background:#eff6ff}
+ .history-item.status-failed{border-left-color:#dc2626;background:var(--app-error-soft, #fffafa)}
+ .history-item.status-cancelled,.history-item.status-interrupted{border-left-color:#d97706;background:var(--app-warning-soft, #fffdf8)}
+ .history-item.status-skipped{border-left-color:var(--app-border-control, #64748b)}
+ .history-item:has(.row.selected){border-color:#60a5fa;background:var(--app-primary-soft, #eff6ff)}
.history-item .row{border:0;padding:0 35px 0 0;margin:0 0 4px}
.history-item .row.selected{background:transparent}
.history-heading{position:relative;min-width:0}
.history-heading>app-favorite-button{position:absolute;left:0;top:0;z-index:1}
.history-heading>.row{min-width:0;width:100%}
- .history-meta{display:flex;gap:5px;align-items:center;flex-wrap:wrap;color:#475569;font-size:11px}
- .history-status{display:inline-flex;align-items:center;box-sizing:border-box;height:20px;line-height:14px;border-radius:5px;padding:0 6px;background:#f1f5f9;color:#334155}
- .status-completed .history-status,.status-success .history-status{background:#dcfce7;color:#166534}
- .status-failed .history-status{background:#fee2e2;color:#991b1b}
- .status-cancelled .history-status,.status-interrupted .history-status{background:#fef3c7;color:#92400e}
- .history-error{font-size:11px;color:#b91c1c;line-height:1.4}
- .delete-history{position:absolute;right:5px;top:8px;border:0;padding:3px;color:#64748b;cursor:pointer;font-size:12px}
+ .history-meta{display:flex;gap:5px;align-items:center;flex-wrap:wrap;color:var(--app-text-secondary, #475569);font-size:11px}
+ .history-status{display:inline-flex;align-items:center;box-sizing:border-box;height:20px;line-height:14px;border-radius:5px;padding:0 6px;background:var(--app-surface-soft, #f1f5f9);color:var(--app-text-control, #334155)}
+ .status-completed .history-status,.status-success .history-status{background:var(--app-success-soft, #dcfce7);color:var(--app-success-text, #166534)}
+ .status-failed .history-status{background:var(--app-error-soft, #fee2e2);color:var(--app-error-text, #991b1b)}
+ .status-cancelled .history-status,.status-interrupted .history-status{background:var(--app-warning-soft, #fef3c7);color:var(--app-warning-text, #92400e)}
+ .history-error{font-size:11px;color:var(--app-error-text, #b91c1c);line-height:1.4}
+ .delete-history{position:absolute;right:5px;top:8px;border:0;padding:3px;color:var(--app-text-muted, #64748b);cursor:pointer;font-size:12px}
.delete-history .material-symbols-outlined{font-size:16px}
- .delete-history:hover{color:#b91c1c}
+ .delete-history:hover{color:var(--app-error-text, #b91c1c)}
.pager{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:12px 0}
button:disabled{opacity:.4}
p{opacity:.7}
diff --git a/apps/showcase_ui/src/app/components/task-id-copy/task-id-copy.component.ts b/apps/showcase_ui/src/app/components/task-id-copy/task-id-copy.component.ts
index 4c86b0e..0442fab 100644
--- a/apps/showcase_ui/src/app/components/task-id-copy/task-id-copy.component.ts
+++ b/apps/showcase_ui/src/app/components/task-id-copy/task-id-copy.component.ts
@@ -19,7 +19,7 @@ import { Component, DestroyRef, Input, inject, signal } from '@angular/core';
padding: 3px 0; cursor: pointer; user-select: text; border-radius: 3px; }
.value { min-width: 0; overflow-wrap: anywhere; user-select: text; }
.material-symbols-outlined { flex-shrink: 0; font-size: 14px; user-select: none; }
- .copy-target:hover { color: #2563eb; }
+ .copy-target:hover { color: var(--app-primary-text, #2563eb); }
.copy-target:focus-visible { outline: 2px solid #60a5fa; outline-offset: 2px; }
.feedback { display: block; line-height: 1.6; user-select: text; }
.feedback:empty { display: none; }
diff --git a/apps/showcase_ui/src/app/components/task-lifecycle/task-lifecycle.component.spec.ts b/apps/showcase_ui/src/app/components/task-lifecycle/task-lifecycle.component.spec.ts
new file mode 100644
index 0000000..fb793eb
--- /dev/null
+++ b/apps/showcase_ui/src/app/components/task-lifecycle/task-lifecycle.component.spec.ts
@@ -0,0 +1,33 @@
+import { TestBed } from '@angular/core/testing';
+import { TaskLifecycleComponent, durationText } from './task-lifecycle.component';
+
+describe('TaskLifecycleComponent', () => {
+ it('distinguishes missing time, zero and longer durations', () => {
+ expect(durationText(null)).toBe('未记录');
+ expect(durationText(0)).toBe('0 秒');
+ expect(durationText(125.7)).toBe('2 分 5 秒');
+ });
+
+ it('shows final review separately and never turns execution completion into a passed verdict', () => {
+ const fixture = TestBed.createComponent(TaskLifecycleComponent);
+ fixture.componentRef.setInput('value', {
+ schema_version:1, status:'completed', phase:'finished', observed_at:100,
+ durations:{queued:60, resource_wait:5, execution:100, final_review:20, paused:null}
+ });
+ fixture.detectChanges();
+ const text = fixture.nativeElement.textContent;
+ expect(text).toContain('执行已结束');
+ expect(text).toContain('1 分 0 秒');
+ expect(text).toContain('最终验收');
+ expect(text).toContain('20 秒');
+ expect(text).toContain('未记录');
+ expect(text).toContain('执行完成不等于验证通过');
+ expect(fixture.nativeElement.querySelector('app-test-result-badge')).toBeNull();
+ });
+
+ it('does not invent lifecycle data for an older backend', () => {
+ const fixture = TestBed.createComponent(TaskLifecycleComponent);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.querySelector('details')).toBeNull();
+ });
+});
diff --git a/apps/showcase_ui/src/app/components/task-lifecycle/task-lifecycle.component.ts b/apps/showcase_ui/src/app/components/task-lifecycle/task-lifecycle.component.ts
new file mode 100644
index 0000000..1b3aa87
--- /dev/null
+++ b/apps/showcase_ui/src/app/components/task-lifecycle/task-lifecycle.component.ts
@@ -0,0 +1,50 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import { DatePipe } from '@angular/common';
+import { TaskLifecycle } from '../../core/models/session.model';
+
+export function durationText(value: number | null | undefined): string {
+ if (value == null || !Number.isFinite(value)) return '未记录';
+ const seconds = Math.max(0, Math.floor(value));
+ return seconds < 60 ? `${seconds} 秒` : `${Math.floor(seconds / 60)} 分 ${seconds % 60} 秒`;
+}
+
+@Component({
+ selector: 'app-task-lifecycle', standalone: true, imports: [DatePipe],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `@if (value(); as progress) {
+
+ }`,
+ styles: [`
+ :host { display: block; }
+ .lifecycle { margin: 8px 0; padding: 10px 12px; border: 1px solid var(--app-border-control, #cbd5e1); border-radius: 8px; color: var(--app-text-control, #1e293b); background: var(--app-surface, #fff); font-size: 12px; }
+ summary { cursor: pointer; font-weight: 600; }
+ dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin: 12px 0; }
+ dt, p, small { color: var(--app-text-muted, #64748b); }
+ dd { margin: 4px 0 0; font-variant-numeric: tabular-nums; }
+ p { margin: 8px 0; line-height: 1.6; overflow-wrap: anywhere; }
+ `]
+})
+export class TaskLifecycleComponent {
+ value = input