fix: UX audit improvements - dark theme, structured errors, heartbeat, health check
Phase 1 - UI Theme Consistency: - Chat messages now use consistent dark theme (removed jarring white bg) - Plan cards restyled with rounded borders, fills, colored status badges - Timeline entries use humanist sans-serif instead of monospace - Error messages now structured (icon + title + detail + action link) - Input area unified with dark theme cohesion Phase 2 - UX Flow: - Added contextual placeholder text per agent mode in textarea - Added visual mode indicator badge (Chat/Planning/Writing) - Simplified welcome screen (single 'Continue' + collapsible history) - Added slash command/mention discovery hint in empty input - Added write confirmation when editor has existing content - Added 30s streaming heartbeat (reassurance when model is slow) Phase 3 - Error Handling: - Added DB table health check on sidebar init - Improved 'no API key' error with settings link - Shows in-chat warning when provider fallback triggers - Auto-fallback to registry fallback model on unavailability - isLoading always resets via try/finally pattern
This commit is contained in:
23
TASKLIST_AUDIT_FIXES.md
Normal file
23
TASKLIST_AUDIT_FIXES.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Audit Fix Tasklist
|
||||||
|
|
||||||
|
## Phase 1: UI Theme Consistency & Polish
|
||||||
|
- [x] 1.1 Make chat messages use dark theme consistently (remove white bg)
|
||||||
|
- [x] 1.2 Restyle plan cards (remove dashed wireframe look, add fills/icons/status colors)
|
||||||
|
- [x] 1.3 Fix timeline entry typography (remove monospace, use humanist font)
|
||||||
|
- [x] 1.4 Structure error messages (icon + title + collapsible detail + action)
|
||||||
|
- [x] 1.5 Polish input area cohesion (unify focus bar + mode + textarea)
|
||||||
|
|
||||||
|
## Phase 2: UX Flow Improvements
|
||||||
|
- [x] 2.1 Add contextual placeholder text per agent mode in textarea
|
||||||
|
- [x] 2.2 Add visual mode indicator badge in chat area
|
||||||
|
- [x] 2.3 Simplify welcome screen (reduce session list noise)
|
||||||
|
- [x] 2.4 Add slash command discovery hint in empty input
|
||||||
|
- [x] 2.5 Add confirmation before writing over existing content
|
||||||
|
- [x] 2.6 Add streaming timeout heartbeat (30s no-data reassurance)
|
||||||
|
|
||||||
|
## Phase 3: Error Handling Hardening
|
||||||
|
- [x] 3.1 Add DB table health check on sidebar init
|
||||||
|
- [x] 3.2 Improve "no API key" error with settings link
|
||||||
|
- [x] 3.3 Show in-chat warning when provider fallback triggers
|
||||||
|
- [x] 3.4 Auto-fallback to registry fallback model on unavailability
|
||||||
|
- [x] 3.5 Ensure isLoading always resets on all error paths
|
||||||
@@ -144,10 +144,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #ffffff;
|
background: #1a1d23;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
border: 1px solid #dcdcde;
|
border: 1px solid #2d3139;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-messages-inner {
|
.wpaw-messages-inner {
|
||||||
@@ -163,24 +163,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-messages-inner::-webkit-scrollbar-track {
|
.wpaw-messages-inner::-webkit-scrollbar-track {
|
||||||
background: #f0f0f1;
|
background: #1a1d23;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-messages-inner::-webkit-scrollbar-thumb {
|
.wpaw-messages-inner::-webkit-scrollbar-thumb {
|
||||||
background: #c3c4c7;
|
background: #3d4450;
|
||||||
border-radius: 0;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-messages-inner::-webkit-scrollbar-thumb:hover {
|
.wpaw-messages-inner::-webkit-scrollbar-thumb:hover {
|
||||||
background: #8c8f94;
|
background: #525b6b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-input-area {
|
.wpaw-input-area {
|
||||||
background: #f6f7f7;
|
background: #1e2128;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: 1px solid #dcdcde;
|
border: 1px solid #2d3139;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
|
|
||||||
.wpaw-input-label {
|
.wpaw-input-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #5f6b7a;
|
color: #8b95a5;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -202,24 +202,24 @@
|
|||||||
|
|
||||||
.wpaw-mode-select {
|
.wpaw-mode-select {
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
border-radius: 2px;
|
border-radius: 6px;
|
||||||
border: 1px solid #8c8f94;
|
border: 1px solid #3d4450;
|
||||||
background: #fff;
|
background: #252830;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: #1d2227;
|
color: #c8cdd5;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.1s ease;
|
transition: border-color 0.1s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-mode-select:hover {
|
.wpaw-mode-select:hover {
|
||||||
border-color: #2271b1;
|
border-color: #5b8def;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-mode-select:focus {
|
.wpaw-mode-select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #2271b1;
|
border-color: #5b8def;
|
||||||
box-shadow: 0 0 0 1px #2271b1;
|
box-shadow: 0 0 0 1px #5b8def;
|
||||||
}
|
}
|
||||||
|
|
||||||
#agentMode {
|
#agentMode {
|
||||||
@@ -239,9 +239,10 @@
|
|||||||
.wpaw-message {
|
.wpaw-message {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-radius: 0;
|
border-radius: 8px;
|
||||||
background: #fff;
|
background: #252830;
|
||||||
border: 1px solid #dcdcde;
|
border: 1px solid #2d3139;
|
||||||
|
color: #e0e4ea;
|
||||||
animation: messageSlide 0.2s ease;
|
animation: messageSlide 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,32 +259,71 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-message-user {
|
.wpaw-message-user {
|
||||||
background: #fff;
|
background: #2a3040;
|
||||||
border-left: 3px solid #2271b1;
|
border-left: none;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
border-radius: 8px;
|
border-radius: 12px 12px 4px 12px;
|
||||||
border: 1px solid #4c4c4c;
|
border: 1px solid #3b4560;
|
||||||
}
|
color: #e8ecf2;
|
||||||
|
|
||||||
.dark-theme .wpaw-message-user {
|
|
||||||
background: #252830;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-message-error {
|
.wpaw-message-error {
|
||||||
background: rgb(214, 54, 56, 0.05);
|
background: rgba(220, 38, 38, 0.08);
|
||||||
/* border-left: 3px solid #d63638; */
|
border: 1px solid rgba(220, 38, 38, 0.25);
|
||||||
/* border-color: #8a1e1e; */
|
border-left: 3px solid #ef4444;
|
||||||
border: unset;
|
border-radius: 8px;
|
||||||
color: #d63638;
|
color: #fca5a5;
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-message-error .wpaw-error-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #fca5a5;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-message-error .wpaw-error-detail {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #d4a0a0;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-message-error details {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-message-error details summary {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #e87171;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-message-error details[open] summary {
|
||||||
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-message-error button.is-secondary {
|
.wpaw-message-error button.is-secondary {
|
||||||
background: #8a1e1e;
|
background: rgba(220, 38, 38, 0.15);
|
||||||
color: white;
|
color: #fca5a5;
|
||||||
border: unset !important;
|
border: 1px solid rgba(220, 38, 38, 0.4) !important;
|
||||||
box-shadow: unset !important;
|
box-shadow: none !important;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-message-error button.is-secondary:hover {
|
||||||
|
background: rgba(220, 38, 38, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Research message styling */
|
/* Research message styling */
|
||||||
@@ -384,39 +424,48 @@
|
|||||||
|
|
||||||
.wpaw-plan-card,
|
.wpaw-plan-card,
|
||||||
.wpaw-edit-plan {
|
.wpaw-edit-plan {
|
||||||
border: 2px dashed #4c4c4c;
|
background: #1e2530;
|
||||||
padding: 12px;
|
border: 1px solid #2d3a4a;
|
||||||
border-radius: 0;
|
padding: 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-plan-card:hover {
|
.wpaw-plan-card:hover {
|
||||||
border-color: #8c8f94;
|
border-color: #3d5070;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-plan-title {
|
.wpaw-plan-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
color: #e8ecf2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-plan-config-summary {
|
.wpaw-plan-section-status {
|
||||||
margin-bottom: 12px;
|
font-size: 11px;
|
||||||
padding: 8px 10px;
|
text-transform: uppercase;
|
||||||
background: #1a1a1a;
|
letter-spacing: 0.05em;
|
||||||
border: 1px solid #3c3c3c;
|
padding: 2px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 10px;
|
||||||
font-size: 12px;
|
margin-top: 2px;
|
||||||
line-height: 1.6;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-config-summary-item {
|
.wpaw-plan-section.pending .wpaw-plan-section-status {
|
||||||
color: #b0b0b0;
|
color: #94a3b8;
|
||||||
font-family: ui-monospace, monospace;
|
background: rgba(148, 163, 184, 0.1);
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-config-summary-item:last-child {
|
.wpaw-plan-section.done .wpaw-plan-section-status {
|
||||||
margin-bottom: 0;
|
color: #4ade80;
|
||||||
|
background: rgba(74, 222, 128, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-plan-section.in_progress .wpaw-plan-section-status {
|
||||||
|
color: #60a5fa;
|
||||||
|
background: rgba(96, 165, 250, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-plan-sections,
|
.wpaw-plan-sections,
|
||||||
@@ -449,25 +498,6 @@ input.wpaw-plan-section-check:checked::before {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-plan-section-title {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-plan-section-desc {
|
|
||||||
color: #6c6c6c;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-plan-section-status {
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: #94a3b8;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-clear-context {
|
.wpaw-clear-context {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
background: #f6f7f7;
|
background: #f6f7f7;
|
||||||
@@ -584,20 +614,22 @@ input.wpaw-plan-section-check:checked::before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-ai-response pre {
|
.wpaw-ai-response pre {
|
||||||
background: #f1f5f9;
|
background: #1a1d23;
|
||||||
padding: 10px;
|
padding: 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
font-family: "Courier New", monospace;
|
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
border: 1px solid #2d3139;
|
||||||
|
color: #c8cdd5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-ai-response code {
|
.wpaw-ai-response code {
|
||||||
background: #e2e8f0;
|
background: #2d3139;
|
||||||
color: #1f2937;
|
color: #a5d6ff;
|
||||||
padding: 2px 4px;
|
padding: 2px 5px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-family: "Courier New", monospace;
|
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,14 +646,6 @@ input.wpaw-plan-section-check:checked::before {
|
|||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-plan-section.done .wpaw-plan-section-status {
|
|
||||||
color: #15803d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-plan-section.in_progress .wpaw-plan-section-status {
|
|
||||||
color: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Outline Version Tracking & Inline Editing */
|
/* Outline Version Tracking & Inline Editing */
|
||||||
.wpaw-plan-header {
|
.wpaw-plan-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -857,12 +881,12 @@ input.wpaw-plan-section-check:checked::before {
|
|||||||
|
|
||||||
.wpaw-response {
|
.wpaw-response {
|
||||||
margin: 0 0 12px 0;
|
margin: 0 0 12px 0;
|
||||||
border-left: 2px solid #e0e6ed;
|
border-left: 2px solid #3d4450;
|
||||||
color: #1f2937;
|
color: #dce0e8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-theme .wpaw-response {
|
.dark-theme .wpaw-response {
|
||||||
color: #cecece;
|
color: #dce0e8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-ai-response .wpaw-response {
|
.wpaw-ai-response .wpaw-response {
|
||||||
@@ -943,70 +967,6 @@ input.wpaw-plan-section-check:checked::before {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-response-content {
|
|
||||||
line-height: 1.6;
|
|
||||||
word-wrap: break-word;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content>* {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content p {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content h1,
|
|
||||||
.wpaw-response-content h2,
|
|
||||||
.wpaw-response-content h3,
|
|
||||||
.wpaw-response-content h4,
|
|
||||||
.wpaw-response-content h5,
|
|
||||||
.wpaw-response-content h6 {
|
|
||||||
margin: 12px 0 6px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content ul,
|
|
||||||
.wpaw-response-content ol {
|
|
||||||
margin: 6px 0 10px 18px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content ul li {
|
|
||||||
list-style: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content li {
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content li p {
|
|
||||||
margin: 4px 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark-theme .wpaw-response-content *:is(h1, h2, h3, h4, h5, h6) {
|
|
||||||
color: #cecece;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content table {
|
|
||||||
border: 1px solid;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wpaw-response-content table th,
|
|
||||||
.wpaw-response-content table td {
|
|
||||||
border: 1px solid;
|
|
||||||
padding: 5px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Timeline Progress */
|
/* Timeline Progress */
|
||||||
.wpaw-timeline-entry {
|
.wpaw-timeline-entry {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1133,19 +1093,19 @@ input.wpaw-plan-section-check:checked::before {
|
|||||||
|
|
||||||
.wpaw-timeline-content {
|
.wpaw-timeline-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||||
color: #334155;
|
color: #c8cdd5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-timeline-message {
|
.wpaw-timeline-message {
|
||||||
font-size: 12px;
|
font-size: 12.5px;
|
||||||
color: #334155;
|
color: #c8cdd5;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-theme .wpaw-timeline-message {
|
.dark-theme .wpaw-timeline-message {
|
||||||
color: white;
|
color: #e0e4ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wpaw-timeline-complete {
|
.wpaw-timeline-complete {
|
||||||
@@ -4589,3 +4549,237 @@ input.wpaw-plan-section-check:checked::before {
|
|||||||
.wpaw-provider-info:has(.wpaw-fallback) {
|
.wpaw-provider-info:has(.wpaw-fallback) {
|
||||||
color: #f59e0b;
|
color: #f59e0b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
AUDIT FIXES: Mode Indicator Badge
|
||||||
|
=========================== */
|
||||||
|
.wpaw-mode-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-mode-badge.mode-chat {
|
||||||
|
background: rgba(96, 165, 250, 0.12);
|
||||||
|
color: #60a5fa;
|
||||||
|
border: 1px solid rgba(96, 165, 250, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-mode-badge.mode-planning {
|
||||||
|
background: rgba(251, 191, 36, 0.12);
|
||||||
|
color: #fbbf24;
|
||||||
|
border: 1px solid rgba(251, 191, 36, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-mode-badge.mode-writing {
|
||||||
|
background: rgba(74, 222, 128, 0.12);
|
||||||
|
color: #4ade80;
|
||||||
|
border: 1px solid rgba(74, 222, 128, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
AUDIT FIXES: Streaming Heartbeat
|
||||||
|
=========================== */
|
||||||
|
.wpaw-heartbeat-notice {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
background: rgba(251, 191, 36, 0.08);
|
||||||
|
border: 1px solid rgba(251, 191, 36, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #fbbf24;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-heartbeat-notice .wpaw-heartbeat-icon {
|
||||||
|
animation: pulse-ring 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
AUDIT FIXES: Slash Command Hint
|
||||||
|
=========================== */
|
||||||
|
.wpaw-input-hint {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 12px;
|
||||||
|
right: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: #252830;
|
||||||
|
border: 1px solid #3d4450;
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #6b7a8d;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-input-hint kbd {
|
||||||
|
background: #3d4450;
|
||||||
|
color: #a0aec0;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
AUDIT FIXES: Provider Fallback Warning
|
||||||
|
=========================== */
|
||||||
|
.wpaw-provider-warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
background: rgba(251, 146, 60, 0.08);
|
||||||
|
border: 1px solid rgba(251, 146, 60, 0.2);
|
||||||
|
border-left: 3px solid #fb923c;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #fdba74;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-provider-warning a {
|
||||||
|
color: #fb923c;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
AUDIT FIXES: DB Health Notice
|
||||||
|
=========================== */
|
||||||
|
.wpaw-health-notice {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
background: rgba(220, 38, 38, 0.06);
|
||||||
|
border: 1px solid rgba(220, 38, 38, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-health-notice a {
|
||||||
|
color: #ef4444;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
AUDIT FIXES: Confirm Modal for Writing
|
||||||
|
=========================== */
|
||||||
|
.wpaw-write-confirm-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1200;
|
||||||
|
background: rgba(10, 16, 27, 0.75);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
animation: fadeIn 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-write-confirm-modal {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 380px;
|
||||||
|
background: #1e2530;
|
||||||
|
color: #e5e7eb;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-write-confirm-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-write-confirm-body {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-write-confirm-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
AUDIT FIXES: Response content dark theme
|
||||||
|
=========================== */
|
||||||
|
.wpaw-response-content {
|
||||||
|
line-height: 1.6;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
color: #dce0e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-response-content>* {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-response-content p {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-response-content p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plan section title in dark theme */
|
||||||
|
.wpaw-plan-section-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: #e0e4ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-plan-section-desc {
|
||||||
|
color: #8b95a5;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Config summary dark */
|
||||||
|
.wpaw-plan-config-summary {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #161a20;
|
||||||
|
border: 1px solid #2d3a4a;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-config-summary-item {
|
||||||
|
color: #9aa5b4;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wpaw-config-summary-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,21 +37,28 @@
|
|||||||
const cleanMessage = String(rawMessage || fallback).replace(/^API error:\s*/i, '').trim();
|
const cleanMessage = String(rawMessage || fallback).replace(/^API error:\s*/i, '').trim();
|
||||||
const lowerMessage = cleanMessage.toLowerCase();
|
const lowerMessage = cleanMessage.toLowerCase();
|
||||||
|
|
||||||
|
// Returns structured object { title, detail, actionUrl, actionLabel }
|
||||||
|
const structured = (title, detail, actionUrl, actionLabel) => {
|
||||||
|
return { title, detail, actionUrl: actionUrl || '', actionLabel: actionLabel || '' };
|
||||||
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
lowerMessage.includes('no allowed providers are available')
|
lowerMessage.includes('no allowed providers are available')
|
||||||
|| (lowerMessage.includes('allowed providers') && lowerMessage.includes('selected model'))
|
|| (lowerMessage.includes('allowed providers') && lowerMessage.includes('selected model'))
|
||||||
) {
|
) {
|
||||||
const routedProvider = settings?.openrouter_provider_slug && settings.openrouter_provider_slug !== 'auto'
|
const routedProvider = settings?.openrouter_provider_slug && settings.openrouter_provider_slug !== 'auto'
|
||||||
? ` Current pinned provider: ${settings.openrouter_provider_slug}.`
|
? ` Pinned: ${settings.openrouter_provider_slug}.`
|
||||||
: '';
|
: '';
|
||||||
|
return structured(
|
||||||
return 'The selected model is not available from the current OpenRouter provider routing settings.'
|
'Model unavailable from current provider',
|
||||||
+ routedProvider
|
`The pinned provider routing doesn't support this model.${routedProvider} Change provider routing or select a compatible model.`,
|
||||||
+ ' Open Settings -> WP Agentic Writer -> Models -> OpenRouter Provider Routing, then either choose a provider that supports this model, turn off "Only use this provider", enable fallback providers, or select a model from the pinned BYOK provider.';
|
settings?.settings_url || '',
|
||||||
|
'Open Settings'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanMessage.includes('429') || lowerMessage.includes('rate limit')) {
|
if (cleanMessage.includes('429') || lowerMessage.includes('rate limit')) {
|
||||||
return 'Rate limit exceeded. Please wait a moment and try again.';
|
return structured('Rate limit exceeded', 'The AI provider is throttling requests. Wait a moment and try again.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -59,18 +66,37 @@
|
|||||||
|| lowerMessage.includes('operation timed out')
|
|| lowerMessage.includes('operation timed out')
|
||||||
|| lowerMessage.includes('timed out after')
|
|| lowerMessage.includes('timed out after')
|
||||||
) {
|
) {
|
||||||
return 'The selected model/provider started the planning request but did not finish before the 2-minute timeout. Try a faster planning model, reduce the outline/article length, or switch OpenRouter Provider Routing back to Auto/fallback-capable routing before retrying.';
|
return structured(
|
||||||
|
'Request timed out',
|
||||||
|
'The model took too long to respond. Try a faster model, reduce content length, or check your provider routing.',
|
||||||
|
settings?.settings_url || '',
|
||||||
|
'Open Settings'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanMessage.startsWith('HTTP 401') || lowerMessage.includes('unauthorized')) {
|
if (cleanMessage.startsWith('HTTP 401') || lowerMessage.includes('unauthorized')) {
|
||||||
return 'The AI provider rejected the API key. Please check the OpenRouter/API key settings and try again.';
|
return structured(
|
||||||
|
'API key rejected',
|
||||||
|
'The provider rejected your API key. Check your key in settings.',
|
||||||
|
settings?.settings_url || '',
|
||||||
|
'Open Settings'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanMessage.startsWith('HTTP 402') || lowerMessage.includes('insufficient credits')) {
|
if (cleanMessage.startsWith('HTTP 402') || lowerMessage.includes('insufficient credits')) {
|
||||||
return 'The AI provider says the account has insufficient credits or quota. Please check your provider billing/BYOK setup.';
|
return structured('Insufficient credits', 'Your provider account has no remaining credits or quota.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Error: ${cleanMessage || fallback}`;
|
if (lowerMessage.includes('api key is not configured') || lowerMessage.includes('no_api_key')) {
|
||||||
|
return structured(
|
||||||
|
'API key not configured',
|
||||||
|
'Add your OpenRouter API key in plugin settings to start using AI features.',
|
||||||
|
settings?.settings_url || '',
|
||||||
|
'Configure API Key'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return structured(cleanMessage || fallback, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tab state
|
// Tab state
|
||||||
@@ -1857,10 +1883,29 @@
|
|||||||
let streamBuffer = '';
|
let streamBuffer = '';
|
||||||
let fullContent = '';
|
let fullContent = '';
|
||||||
let streamError = null;
|
let streamError = null;
|
||||||
|
let lastDataTime = Date.now();
|
||||||
|
let heartbeatShown = false;
|
||||||
|
|
||||||
|
// Heartbeat: show reassurance if no data for 30s
|
||||||
|
const heartbeatInterval = setInterval(() => {
|
||||||
|
if (Date.now() - lastDataTime > 30000 && !heartbeatShown) {
|
||||||
|
heartbeatShown = true;
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
role: 'system',
|
||||||
|
type: 'timeline',
|
||||||
|
status: 'active',
|
||||||
|
message: '⏳ Still waiting for response — the model is processing...',
|
||||||
|
timestamp: new Date()
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
|
lastDataTime = Date.now();
|
||||||
|
heartbeatShown = false;
|
||||||
|
|
||||||
streamBuffer += decoder.decode(value, { stream: true });
|
streamBuffer += decoder.decode(value, { stream: true });
|
||||||
const lines = streamBuffer.split('\n');
|
const lines = streamBuffer.split('\n');
|
||||||
@@ -1901,6 +1946,15 @@
|
|||||||
addFocusKeywordSuggestions(suggestions);
|
addFocusKeywordSuggestions(suggestions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (data.type === 'provider' && data.fallback_used) {
|
||||||
|
// Show in-chat provider fallback warning
|
||||||
|
setMessages(prev => [...prev, {
|
||||||
|
role: 'system',
|
||||||
|
type: 'timeline',
|
||||||
|
status: 'active',
|
||||||
|
message: `⚠️ ${data.selectedProvider || 'Selected provider'} unavailable — using ${data.provider || 'fallback'}`,
|
||||||
|
timestamp: new Date()
|
||||||
|
}]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
wpawLog.error('Failed to parse retry streaming data:', line, e);
|
wpawLog.error('Failed to parse retry streaming data:', line, e);
|
||||||
@@ -1911,6 +1965,9 @@
|
|||||||
throw streamError;
|
throw streamError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
clearInterval(heartbeatInterval);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = formatAiErrorMessage(error, 'Failed to chat');
|
const errorMsg = formatAiErrorMessage(error, 'Failed to chat');
|
||||||
setMessages(prev => [...prev, {
|
setMessages(prev => [...prev, {
|
||||||
@@ -2549,6 +2606,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const plan = currentPlanRef.current;
|
const plan = currentPlanRef.current;
|
||||||
|
|
||||||
|
// Confirmation: warn if editor already has content blocks
|
||||||
|
const existingBlocks = select('core/block-editor').getBlocks();
|
||||||
|
const hasExistingContent = existingBlocks.some(b =>
|
||||||
|
b.name !== 'core/paragraph' || (b.attributes?.content && b.attributes.content.trim().length > 0)
|
||||||
|
);
|
||||||
|
if (hasExistingContent && !options.skipConfirm) {
|
||||||
|
const pendingSections = Array.isArray(plan?.sections)
|
||||||
|
? plan.sections.filter((section) => section.status !== 'done').length
|
||||||
|
: 0;
|
||||||
|
const confirmed = window.confirm(
|
||||||
|
`This will write ${pendingSections} sections into the editor. Existing content will be preserved below the new content.\n\nContinue?`
|
||||||
|
);
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setAgentMode('writing');
|
setAgentMode('writing');
|
||||||
const pendingCount = Array.isArray(plan?.sections)
|
const pendingCount = Array.isArray(plan?.sections)
|
||||||
? plan.sections.filter((section) => section.status !== 'done').length
|
? plan.sections.filter((section) => section.status !== 'done').length
|
||||||
@@ -5583,23 +5658,32 @@
|
|||||||
|
|
||||||
// Render Welcome Screen (chatty, friendly)
|
// Render Welcome Screen (chatty, friendly)
|
||||||
const renderWelcomeScreen = () => {
|
const renderWelcomeScreen = () => {
|
||||||
|
const recentSession = availableSessions.length > 0 ? availableSessions[0] : null;
|
||||||
|
|
||||||
return wp.element.createElement('div', { className: 'wpaw-welcome-screen' },
|
return wp.element.createElement('div', { className: 'wpaw-welcome-screen' },
|
||||||
wp.element.createElement('div', { className: 'wpaw-welcome-content' },
|
wp.element.createElement('div', { className: 'wpaw-welcome-content' },
|
||||||
wp.element.createElement('span', {
|
wp.element.createElement('span', {
|
||||||
className: 'wpaw-welcome-icon',
|
className: 'wpaw-welcome-icon',
|
||||||
dangerouslySetInnerHTML: { __html: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M12 5a3 3 0 1 0-5.997.125a4 4 0 0 0-2.526 5.77a4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M9 13a4.5 4.5 0 0 0 3-4M6.003 5.125A3 3 0 0 0 6.401 6.5m-2.924 4.396a4 4 0 0 1 .585-.396M6 18a4 4 0 0 1-1.967-.516M12 13h4m-4 5h6a2 2 0 0 1 2 2v1M12 8h8m-4 0V5a2 2 0 0 1 2-2"/><circle cx="16" cy="13" r=".5"/><circle cx="18" cy="3" r=".5"/><circle cx="20" cy="21" r=".5"/><circle cx="20" cy="8" r=".5"/></g></svg>' }
|
dangerouslySetInnerHTML: { __html: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M12 5a3 3 0 1 0-5.997.125a4 4 0 0 0-2.526 5.77a4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M9 13a4.5 4.5 0 0 0 3-4M6.003 5.125A3 3 0 0 0 6.401 6.5m-2.924 4.396a4 4 0 0 1 .585-.396M6 18a4 4 0 0 1-1.967-.516M12 13h4m-4 5h6a2 2 0 0 1 2 2v1M12 8h8m-4 0V5a2 2 0 0 1 2-2"/><circle cx="16" cy="13" r=".5"/><circle cx="18" cy="3" r=".5"/><circle cx="20" cy="21" r=".5"/><circle cx="20" cy="8" r=".5"/></g></svg>' }
|
||||||
}),
|
}),
|
||||||
wp.element.createElement('h2', { className: 'wpaw-welcome-title' }, 'Welcome to Agentic Writer'),
|
wp.element.createElement('h2', { className: 'wpaw-welcome-title' }, 'Agentic Writer'),
|
||||||
wp.element.createElement('p', { className: 'wpaw-welcome-subtitle' }, "What's your concern today?"),
|
wp.element.createElement('p', { className: 'wpaw-welcome-subtitle' }, "What are we writing today?"),
|
||||||
availableSessions.length > 0 && wp.element.createElement('div', {
|
// Show single "Continue last conversation" button if available
|
||||||
className: 'wpaw-existing-sessions',
|
recentSession && wp.element.createElement('button', {
|
||||||
style: { marginBottom: '16px' }
|
className: 'wpaw-welcome-pill',
|
||||||
|
style: { width: '100%', marginBottom: '12px' },
|
||||||
|
disabled: isSessionActionLoading,
|
||||||
|
onClick: () => openSessionById(recentSession.session_id || '')
|
||||||
|
}, `↩ Continue: ${getSessionDisplayTitle(recentSession, 0)}`),
|
||||||
|
// Show older sessions in collapsible
|
||||||
|
availableSessions.length > 1 && wp.element.createElement('details', {
|
||||||
|
style: { marginBottom: '12px', width: '100%' }
|
||||||
},
|
},
|
||||||
wp.element.createElement('div', {
|
wp.element.createElement('summary', {
|
||||||
style: { fontSize: '12px', opacity: 0.8, marginBottom: '8px' }
|
style: { fontSize: '12px', color: '#8b95a5', cursor: 'pointer', marginBottom: '8px' }
|
||||||
}, 'Continue a previous conversation'),
|
}, `${availableSessions.length - 1} more session${availableSessions.length > 2 ? 's' : ''}`),
|
||||||
wp.element.createElement('div', { className: 'wpaw-session-list' },
|
wp.element.createElement('div', { className: 'wpaw-session-list' },
|
||||||
...availableSessions.map((session, idx) =>
|
...availableSessions.slice(1).map((session, idx) =>
|
||||||
wp.element.createElement('div', {
|
wp.element.createElement('div', {
|
||||||
key: session.session_id || idx,
|
key: session.session_id || idx,
|
||||||
className: 'wpaw-welcome-pill',
|
className: 'wpaw-welcome-pill',
|
||||||
@@ -5624,14 +5708,9 @@
|
|||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
cursor: isSessionActionLoading ? 'wait' : 'pointer'
|
cursor: isSessionActionLoading ? 'wait' : 'pointer'
|
||||||
},
|
},
|
||||||
onClick: () => {
|
onClick: () => openSessionById(session.session_id || '')
|
||||||
openSessionById(session.session_id || '');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
wp.element.createElement('div', null, getSessionDisplayTitle(session, idx)),
|
wp.element.createElement('div', null, getSessionDisplayTitle(session, idx + 1)),
|
||||||
// wp.element.createElement('div', { style: { opacity: 0.55, fontSize: '10px', marginTop: '2px' } },
|
|
||||||
// getSessionDebugMeta(session)
|
|
||||||
// ),
|
|
||||||
wp.element.createElement('div', { style: { opacity: 0.7, fontSize: '11px' } },
|
wp.element.createElement('div', { style: { opacity: 0.7, fontSize: '11px' } },
|
||||||
`${Number(session?.message_count ?? (Array.isArray(session?.messages) ? session.messages.length : 0))} msgs`
|
`${Number(session?.message_count ?? (Array.isArray(session?.messages) ? session.messages.length : 0))} msgs`
|
||||||
)
|
)
|
||||||
@@ -5652,19 +5731,13 @@
|
|||||||
}, '×')
|
}, '×')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
wp.element.createElement('button', {
|
|
||||||
className: 'wpaw-welcome-pill',
|
|
||||||
style: { width: '100%' },
|
|
||||||
disabled: isSessionActionLoading,
|
|
||||||
onClick: startNewConversation
|
|
||||||
}, '+ Start New Conversation')
|
|
||||||
),
|
),
|
||||||
// Focus keyword input
|
// Focus keyword input
|
||||||
wp.element.createElement('input', {
|
wp.element.createElement('input', {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
className: 'wpaw-welcome-input',
|
className: 'wpaw-welcome-input',
|
||||||
placeholder: 'Your focus keyword (optional)',
|
placeholder: 'Focus keyword (optional)',
|
||||||
value: welcomeKeywordInput,
|
value: welcomeKeywordInput,
|
||||||
onChange: (e) => setWelcomeKeywordInput(e.target.value),
|
onChange: (e) => setWelcomeKeywordInput(e.target.value),
|
||||||
onKeyDown: (e) => {
|
onKeyDown: (e) => {
|
||||||
@@ -5682,14 +5755,14 @@
|
|||||||
wp.element.createElement('button', {
|
wp.element.createElement('button', {
|
||||||
className: 'wpaw-welcome-pill' + (welcomeStartMode === 'planning' ? ' active' : ''),
|
className: 'wpaw-welcome-pill' + (welcomeStartMode === 'planning' ? ' active' : ''),
|
||||||
onClick: () => setWelcomeStartMode('planning')
|
onClick: () => setWelcomeStartMode('planning')
|
||||||
}, '📝 Make an Outline')
|
}, '📝 Create Outline')
|
||||||
),
|
),
|
||||||
// Start button
|
// Start button
|
||||||
wp.element.createElement(Button, {
|
wp.element.createElement(Button, {
|
||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
onClick: handleWelcomeStart,
|
onClick: handleWelcomeStart,
|
||||||
className: 'wpaw-welcome-start-btn'
|
className: 'wpaw-welcome-start-btn'
|
||||||
}, 'Start')
|
}, 'Start Writing')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -6615,15 +6688,30 @@
|
|||||||
retryLastGeneration();
|
retryLastGeneration();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Support structured error objects { title, detail, actionUrl, actionLabel }
|
||||||
|
const errContent = message.content;
|
||||||
|
const isStructured = errContent && typeof errContent === 'object' && errContent.title;
|
||||||
|
|
||||||
return wp.element.createElement('div', {
|
return wp.element.createElement('div', {
|
||||||
key: `error-${index}`,
|
key: `error-${index}`,
|
||||||
className: 'wpaw-ai-item wpaw-message wpaw-message-error',
|
className: 'wpaw-ai-item wpaw-message wpaw-message-error',
|
||||||
},
|
},
|
||||||
wp.element.createElement('div', { className: 'wpaw-message-content' }, renderMessageContent(message.content, true)),
|
isStructured
|
||||||
|
? wp.element.createElement('div', null,
|
||||||
|
wp.element.createElement('div', { className: 'wpaw-error-title' }, '⚠ ', errContent.title),
|
||||||
|
errContent.detail && wp.element.createElement('div', { className: 'wpaw-error-detail' }, errContent.detail),
|
||||||
|
errContent.actionUrl && wp.element.createElement('a', {
|
||||||
|
href: errContent.actionUrl,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noopener',
|
||||||
|
style: { display: 'inline-block', marginTop: '8px', fontSize: '12px', color: '#fca5a5', textDecoration: 'underline' }
|
||||||
|
}, errContent.actionLabel || 'Open Settings')
|
||||||
|
)
|
||||||
|
: wp.element.createElement('div', { className: 'wpaw-message-content' }, renderMessageContent(errContent, true)),
|
||||||
message.canRetry && wp.element.createElement(Button, {
|
message.canRetry && wp.element.createElement(Button, {
|
||||||
isSecondary: true,
|
isSecondary: true,
|
||||||
onClick: handleRetry,
|
onClick: handleRetry,
|
||||||
}, 'Retry')
|
}, '↻ Retry')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7024,6 +7112,19 @@
|
|||||||
isRefinementLocked && wp.element.createElement('div', { className: 'wpaw-refinement-lock-banner' },
|
isRefinementLocked && wp.element.createElement('div', { className: 'wpaw-refinement-lock-banner' },
|
||||||
`Refining in progress — editing is temporarily locked. You can still scroll and review changes live (${refiningBlockIds.length} target block(s)).`
|
`Refining in progress — editing is temporarily locked. You can still scroll and review changes live (${refiningBlockIds.length} target block(s)).`
|
||||||
),
|
),
|
||||||
|
// Health Check Warnings
|
||||||
|
wpAgenticWriter.health && !wpAgenticWriter.health.ok && wpAgenticWriter.health.issues.map((issue, idx) =>
|
||||||
|
wp.element.createElement('div', { key: `health-${idx}`, className: 'wpaw-health-notice' },
|
||||||
|
'⚠️ ',
|
||||||
|
issue.message,
|
||||||
|
issue.actionUrl && wp.element.createElement('a', {
|
||||||
|
href: issue.actionUrl,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noopener',
|
||||||
|
style: { marginLeft: '8px' }
|
||||||
|
}, issue.actionLabel || 'Fix')
|
||||||
|
)
|
||||||
|
),
|
||||||
// Welcome Screen (first time)
|
// Welcome Screen (first time)
|
||||||
showWelcome && !isEditorLocked && renderWelcomeScreen(),
|
showWelcome && !isEditorLocked && renderWelcomeScreen(),
|
||||||
// Writing Mode Empty State
|
// Writing Mode Empty State
|
||||||
@@ -7037,8 +7138,20 @@
|
|||||||
),
|
),
|
||||||
// Context Indicator (moved above textarea) - hide when showing empty state or welcome
|
// Context Indicator (moved above textarea) - hide when showing empty state or welcome
|
||||||
!showWelcome && !shouldShowWritingEmptyState() && renderContextIndicator(),
|
!showWelcome && !shouldShowWritingEmptyState() && renderContextIndicator(),
|
||||||
|
// Mode Badge
|
||||||
|
!showWelcome && !shouldShowWritingEmptyState() && wp.element.createElement('div', {
|
||||||
|
className: `wpaw-mode-badge mode-${agentMode}`
|
||||||
|
},
|
||||||
|
agentMode === 'chat' ? '💬' : agentMode === 'planning' ? '📝' : '✍️',
|
||||||
|
agentMode === 'chat' ? 'Chat Mode' : agentMode === 'planning' ? 'Planning Mode' : 'Writing Mode'
|
||||||
|
),
|
||||||
// Command Input Area - hide when showing empty state or welcome
|
// Command Input Area - hide when showing empty state or welcome
|
||||||
!showWelcome && !shouldShowWritingEmptyState() && wp.element.createElement('div', { className: 'wpaw-command-area', style: { position: 'relative' } },
|
!showWelcome && !shouldShowWritingEmptyState() && wp.element.createElement('div', { className: 'wpaw-command-area', style: { position: 'relative' } },
|
||||||
|
// Slash command hint when input is empty
|
||||||
|
!input && !isLoading && wp.element.createElement('div', { className: 'wpaw-input-hint' },
|
||||||
|
'Type ', wp.element.createElement('kbd', null, '/'), ' for commands or ',
|
||||||
|
wp.element.createElement('kbd', null, '@'), ' to mention a block'
|
||||||
|
),
|
||||||
// Removed Toolbar from Top
|
// Removed Toolbar from Top
|
||||||
wp.element.createElement('div', {
|
wp.element.createElement('div', {
|
||||||
className: 'wpaw-command-input-wrapper' + (isTextareaExpanded ? ' expanded' : '')
|
className: 'wpaw-command-input-wrapper' + (isTextareaExpanded ? ' expanded' : '')
|
||||||
@@ -7051,8 +7164,10 @@
|
|||||||
onKeyDown: handleKeyDown,
|
onKeyDown: handleKeyDown,
|
||||||
rows: isTextareaExpanded ? 20 : 3,
|
rows: isTextareaExpanded ? 20 : 3,
|
||||||
placeholder: agentMode === 'planning'
|
placeholder: agentMode === 'planning'
|
||||||
? 'Describe what you want to write about...'
|
? 'Describe your article topic...'
|
||||||
: 'Ask me anything about your content...'
|
: agentMode === 'writing'
|
||||||
|
? 'Refine content — use @block to target specific sections...'
|
||||||
|
: 'Ask anything about your content, or type / for commands...'
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
showMentionAutocomplete && mentionOptions.length > 0 && wp.element.createElement('div', {
|
showMentionAutocomplete && mentionOptions.length > 0 && wp.element.createElement('div', {
|
||||||
|
|||||||
@@ -243,6 +243,9 @@ class WP_Agentic_Writer_Gutenberg_Sidebar {
|
|||||||
// Get settings for JS.
|
// Get settings for JS.
|
||||||
$settings = $this->get_settings_for_js();
|
$settings = $this->get_settings_for_js();
|
||||||
|
|
||||||
|
// Health check: verify DB table and API key exist
|
||||||
|
$health = $this->run_health_check();
|
||||||
|
|
||||||
// Localize script with data.
|
// Localize script with data.
|
||||||
$data = array(
|
$data = array(
|
||||||
'apiUrl' => rest_url( 'wp-agentic-writer/v1' ),
|
'apiUrl' => rest_url( 'wp-agentic-writer/v1' ),
|
||||||
@@ -252,11 +255,48 @@ class WP_Agentic_Writer_Gutenberg_Sidebar {
|
|||||||
'version' => WP_AGENTIC_WRITER_VERSION,
|
'version' => WP_AGENTIC_WRITER_VERSION,
|
||||||
'debug' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG,
|
'debug' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG,
|
||||||
'pluginUrl' => plugin_dir_url( dirname( __FILE__ ) ),
|
'pluginUrl' => plugin_dir_url( dirname( __FILE__ ) ),
|
||||||
|
'health' => $health,
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_localize_script( 'wp-agentic-writer-sidebar', 'wpAgenticWriter', $data );
|
wp_localize_script( 'wp-agentic-writer-sidebar', 'wpAgenticWriter', $data );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run health check for sidebar initialization.
|
||||||
|
*
|
||||||
|
* @since 0.2.4
|
||||||
|
* @return array Health status.
|
||||||
|
*/
|
||||||
|
private function run_health_check() {
|
||||||
|
global $wpdb;
|
||||||
|
$table_name = $wpdb->prefix . 'wpaw_conversations';
|
||||||
|
$table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name;
|
||||||
|
|
||||||
|
$settings = get_option( 'wp_agentic_writer_settings', array() );
|
||||||
|
$has_api_key = ! empty( $settings['openrouter_api_key'] );
|
||||||
|
|
||||||
|
$issues = array();
|
||||||
|
if ( ! $table_exists ) {
|
||||||
|
$issues[] = array(
|
||||||
|
'type' => 'db_table_missing',
|
||||||
|
'message' => 'Conversation table not found. Please deactivate and reactivate the plugin.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( ! $has_api_key ) {
|
||||||
|
$issues[] = array(
|
||||||
|
'type' => 'no_api_key',
|
||||||
|
'message' => 'API key not configured. Add your OpenRouter key in settings.',
|
||||||
|
'actionUrl' => admin_url( 'options-general.php?page=wp-agentic-writer-settings' ),
|
||||||
|
'actionLabel' => 'Open Settings',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'ok' => empty( $issues ),
|
||||||
|
'issues' => $issues,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get settings for JavaScript.
|
* Get settings for JavaScript.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -784,8 +784,22 @@ class WP_Agentic_Writer_OpenRouter_Provider implements WP_Agentic_Writer_AI_Prov
|
|||||||
// Validate model availability before making API call
|
// Validate model availability before making API call
|
||||||
$model_validation = $this->validate_model_availability( $model );
|
$model_validation = $this->validate_model_availability( $model );
|
||||||
if ( is_wp_error( $model_validation ) ) {
|
if ( is_wp_error( $model_validation ) ) {
|
||||||
|
// Auto-fallback: try registry fallback model instead of hard-failing
|
||||||
|
$fallback_model = WPAW_Model_Registry::get_fallback_model( $type );
|
||||||
|
if ( $fallback_model && $fallback_model !== $model ) {
|
||||||
|
$fallback_validation = $this->validate_model_availability( $fallback_model );
|
||||||
|
if ( true === $fallback_validation ) {
|
||||||
|
$model = $fallback_model;
|
||||||
|
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||||
|
error_log( "WPAW: Model unavailable, auto-fallback to: {$fallback_model}" );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return $model_validation;
|
return $model_validation;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return $model_validation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build request body.
|
// Build request body.
|
||||||
$body = array(
|
$body = array(
|
||||||
|
|||||||
Reference in New Issue
Block a user