@alpaca-editor/core 1.0.4065 → 1.0.4066
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/editor/ImageEditButton.js +10 -3
- package/dist/editor/ImageEditButton.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +3 -2
- package/dist/editor/ai/AgentTerminal.js +386 -94
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +36 -19
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +6 -1
- package/dist/editor/ai/AiResponseMessage.js +63 -3
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.js +27 -2
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/client/EditorClient.js +15 -0
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +2 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/commands/componentCommands.js +56 -6
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js +6 -4
- package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlightings.js +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlightings.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +6 -8
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/reviews/Comment.js +3 -58
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +2 -3
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/Comments.js +4 -0
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/commentAi.d.ts +7 -0
- package/dist/editor/reviews/commentAi.js +86 -0
- package/dist/editor/reviews/commentAi.js.map +1 -0
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +10 -0
- package/package.json +1 -1
- package/src/editor/ImageEditButton.tsx +36 -8
- package/src/editor/ai/AgentTerminal.tsx +436 -65
- package/src/editor/ai/Agents.tsx +193 -140
- package/src/editor/ai/AiResponseMessage.tsx +106 -2
- package/src/editor/ai/AiTerminal.tsx +27 -0
- package/src/editor/client/EditorClient.tsx +23 -0
- package/src/editor/client/editContext.ts +2 -0
- package/src/editor/commands/componentCommands.tsx +60 -12
- package/src/editor/page-editor-chrome/CommentHighlighting.tsx +6 -4
- package/src/editor/page-editor-chrome/CommentHighlightings.tsx +3 -1
- package/src/editor/page-editor-chrome/FrameMenu.tsx +6 -8
- package/src/editor/reviews/Comment.tsx +4 -66
- package/src/editor/reviews/CommentDisplayPopover.tsx +2 -3
- package/src/editor/reviews/Comments.tsx +12 -0
- package/src/editor/reviews/commentAi.ts +106 -0
- package/src/revision.ts +2 -2
- package/dist/editor/ai/AiPromptPopover.d.ts +0 -7
- package/dist/editor/ai/AiPromptPopover.js +0 -111
- package/dist/editor/ai/AiPromptPopover.js.map +0 -1
- package/src/editor/ai/AiPromptPopover.tsx +0 -206
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState, useRef, useCallback } from "react";
|
|
3
|
-
import { Send, Bot, AlertCircle, Loader2, User, X, FileText, Puzzle, Type, Plus, } from "lucide-react";
|
|
3
|
+
import { Send, Bot, AlertCircle, Loader2, User, X, FileText, Puzzle, Type, Plus, Wand2, Square, } from "lucide-react";
|
|
4
4
|
import { DancingDots } from "./DancingDots";
|
|
5
5
|
import { getAgent, startAgent, connectToAgentStream, updateAgentMetadata, } from "../services/agentService";
|
|
6
6
|
import { useEditContext } from "../client/editContext";
|
|
@@ -9,6 +9,8 @@ import { Button } from "../../components/ui/button";
|
|
|
9
9
|
import { AiResponseMessage } from "./AiResponseMessage";
|
|
10
10
|
import { AgentCostDisplay } from "./AgentCostDisplay";
|
|
11
11
|
import { getComponentById } from "../componentTreeHelper";
|
|
12
|
+
import { loadAiProfiles } from "../services/aiService";
|
|
13
|
+
import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
|
|
12
14
|
// Simple user message component
|
|
13
15
|
const UserMessage = ({ message }) => {
|
|
14
16
|
return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx(User, { className: "h-6 w-6 text-blue-600", strokeWidth: 1 }) }), _jsxs("div", { className: "min-w-0 flex-1 select-text", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium text-gray-900", children: "You" }), message.createdDate && (_jsx("span", { className: "text-xs text-gray-400", children: new Date(message.createdDate).toLocaleTimeString() }))] }), _jsx("div", { className: "prose prose-sm max-w-none text-sm text-gray-700 select-text", children: message.content })] })] }));
|
|
@@ -100,7 +102,7 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
|
100
102
|
// interface AgentTerminalProps {
|
|
101
103
|
// agentStub: Agent;
|
|
102
104
|
// }
|
|
103
|
-
export function AgentTerminal({ agentStub }) {
|
|
105
|
+
export function AgentTerminal({ agentStub, initialMetadata, }) {
|
|
104
106
|
const editContext = useEditContext();
|
|
105
107
|
const [agent, setAgent] = useState(undefined);
|
|
106
108
|
const [messages, setMessages] = useState([]);
|
|
@@ -110,6 +112,36 @@ export function AgentTerminal({ agentStub }) {
|
|
|
110
112
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
111
113
|
const [agentMetadata, setAgentMetadata] = useState(null);
|
|
112
114
|
const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
|
|
115
|
+
const isWaitingRef = useRef(false);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
isWaitingRef.current = isWaitingForResponse;
|
|
118
|
+
}, [isWaitingForResponse]);
|
|
119
|
+
// Dots visibility controlled by 1s idle timer since last incremental update
|
|
120
|
+
const [showDots, setShowDots] = useState(false);
|
|
121
|
+
const dotsTimeoutRef = useRef(null);
|
|
122
|
+
const hasActiveStreaming = useCallback(() => {
|
|
123
|
+
const current = messagesRef.current || [];
|
|
124
|
+
return current.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
125
|
+
}, []);
|
|
126
|
+
const resetDotsTimer = useCallback(() => {
|
|
127
|
+
if (dotsTimeoutRef.current) {
|
|
128
|
+
clearTimeout(dotsTimeoutRef.current);
|
|
129
|
+
dotsTimeoutRef.current = null;
|
|
130
|
+
}
|
|
131
|
+
const waiting = isWaitingRef.current;
|
|
132
|
+
const streaming = hasActiveStreaming();
|
|
133
|
+
if (!waiting && !streaming) {
|
|
134
|
+
setShowDots(false);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
setShowDots(false);
|
|
138
|
+
dotsTimeoutRef.current = setTimeout(() => {
|
|
139
|
+
// Re-check conditions after 1s of inactivity
|
|
140
|
+
const stillWaiting = isWaitingRef.current;
|
|
141
|
+
const stillStreaming = hasActiveStreaming();
|
|
142
|
+
setShowDots(stillWaiting || stillStreaming);
|
|
143
|
+
}, 1000);
|
|
144
|
+
}, [hasActiveStreaming]);
|
|
113
145
|
const [resolvedPageName, setResolvedPageName] = useState(undefined);
|
|
114
146
|
const [resolvedComponentName, setResolvedComponentName] = useState(undefined);
|
|
115
147
|
const [resolvedFieldName, setResolvedFieldName] = useState(undefined);
|
|
@@ -120,9 +152,26 @@ export function AgentTerminal({ agentStub }) {
|
|
|
120
152
|
return [];
|
|
121
153
|
});
|
|
122
154
|
const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1);
|
|
155
|
+
const [showPredefined, setShowPredefined] = useState(false);
|
|
156
|
+
const [profiles, setProfiles] = useState([]);
|
|
157
|
+
const [activeProfile, setActiveProfile] = useState(undefined);
|
|
158
|
+
const [selectedModelId, setSelectedModelId] = useState(undefined);
|
|
123
159
|
useEffect(() => {
|
|
124
160
|
localStorage.setItem("editor.agent.promptHistory", JSON.stringify(promptHistory));
|
|
125
161
|
}, [promptHistory]);
|
|
162
|
+
// Clear idle timer on unmount
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
return () => {
|
|
165
|
+
if (dotsTimeoutRef.current) {
|
|
166
|
+
clearTimeout(dotsTimeoutRef.current);
|
|
167
|
+
dotsTimeoutRef.current = null;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}, []);
|
|
171
|
+
// Whenever waiting state changes, restart idle timer logic
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
resetDotsTimer();
|
|
174
|
+
}, [isWaitingForResponse, resetDotsTimer]);
|
|
126
175
|
useEffect(() => {
|
|
127
176
|
// Keep messagesRef synchronized with messages state
|
|
128
177
|
messagesRef.current = messages;
|
|
@@ -200,6 +249,8 @@ export function AgentTerminal({ agentStub }) {
|
|
|
200
249
|
}
|
|
201
250
|
// Clear waiting state when first content chunk arrives
|
|
202
251
|
setIsWaitingForResponse(false);
|
|
252
|
+
// Any content chunk is an incremental update -> reset idle timer
|
|
253
|
+
resetDotsTimer();
|
|
203
254
|
// Always call setMessages and handle all logic in the callback with latest messages
|
|
204
255
|
setMessages((prev) => {
|
|
205
256
|
// Find existing message by messageId in the latest messages
|
|
@@ -316,6 +367,8 @@ export function AgentTerminal({ agentStub }) {
|
|
|
316
367
|
messagesRef.current = updated;
|
|
317
368
|
return updated;
|
|
318
369
|
});
|
|
370
|
+
// Tool call activity counts as activity; keep dots hidden for 1s
|
|
371
|
+
resetDotsTimer();
|
|
319
372
|
}
|
|
320
373
|
}, [createNewStreamMessage]);
|
|
321
374
|
const handleToolResult = useCallback((message, agentData) => {
|
|
@@ -391,7 +444,9 @@ export function AgentTerminal({ agentStub }) {
|
|
|
391
444
|
messagesRef.current = updated;
|
|
392
445
|
return updated;
|
|
393
446
|
});
|
|
394
|
-
|
|
447
|
+
// Tool result activity; reset idle timer
|
|
448
|
+
resetDotsTimer();
|
|
449
|
+
}, [resetDotsTimer]);
|
|
395
450
|
// Connect to agent stream for real-time updates
|
|
396
451
|
const connectToStream = useCallback(async (agentData) => {
|
|
397
452
|
const currentAgent = agentData || agent;
|
|
@@ -424,67 +479,81 @@ export function AgentTerminal({ agentStub }) {
|
|
|
424
479
|
console.log("💾 Stream completed for message:", completedMessageId, "messages count:", messages.length);
|
|
425
480
|
// Mark the specific message as completed by messageId
|
|
426
481
|
if (completedMessageId) {
|
|
427
|
-
setMessages((prev) =>
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (data.numCachedTokens !== undefined) {
|
|
444
|
-
updatedMessage.cachedInputTokens =
|
|
445
|
-
data.numCachedTokens;
|
|
446
|
-
}
|
|
447
|
-
// Update total tokens used
|
|
448
|
-
updatedMessage.tokensUsed =
|
|
449
|
-
(updatedMessage.inputTokens || 0) +
|
|
450
|
-
(updatedMessage.outputTokens || 0);
|
|
451
|
-
// Handle content that might only be sent in the completed event
|
|
452
|
-
if (data.deltaContent && data.deltaContent.trim()) {
|
|
453
|
-
if (!data.isIncremental) {
|
|
454
|
-
// Non-incremental: replace the entire content
|
|
455
|
-
updatedMessage.content = data.deltaContent;
|
|
482
|
+
setMessages((prev) => {
|
|
483
|
+
const updated = prev.map((msg) => {
|
|
484
|
+
if (msg.id === completedMessageId) {
|
|
485
|
+
const updatedMessage = {
|
|
486
|
+
...msg,
|
|
487
|
+
isCompleted: true,
|
|
488
|
+
messageType: "completed",
|
|
489
|
+
};
|
|
490
|
+
// Update token usage data if provided in the completed event
|
|
491
|
+
if (message.data) {
|
|
492
|
+
const data = message.data;
|
|
493
|
+
if (data.numInputTokens !== undefined) {
|
|
494
|
+
updatedMessage.inputTokens = data.numInputTokens;
|
|
495
|
+
}
|
|
496
|
+
if (data.numOutputTokens !== undefined) {
|
|
497
|
+
updatedMessage.outputTokens = data.numOutputTokens;
|
|
456
498
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
499
|
+
if (data.numCachedTokens !== undefined) {
|
|
500
|
+
updatedMessage.cachedInputTokens =
|
|
501
|
+
data.numCachedTokens;
|
|
502
|
+
}
|
|
503
|
+
// Update total tokens used
|
|
504
|
+
updatedMessage.tokensUsed =
|
|
505
|
+
(updatedMessage.inputTokens || 0) +
|
|
506
|
+
(updatedMessage.outputTokens || 0);
|
|
507
|
+
// Handle content that might only be sent in the completed event
|
|
508
|
+
if (data.deltaContent && data.deltaContent.trim()) {
|
|
509
|
+
if (!data.isIncremental) {
|
|
510
|
+
// Non-incremental: replace the entire content
|
|
511
|
+
updatedMessage.content = data.deltaContent;
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
// Incremental: append to existing content
|
|
515
|
+
updatedMessage.content =
|
|
516
|
+
(updatedMessage.content || "") +
|
|
517
|
+
data.deltaContent;
|
|
518
|
+
}
|
|
462
519
|
}
|
|
463
520
|
}
|
|
521
|
+
return updatedMessage;
|
|
464
522
|
}
|
|
465
|
-
return
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
523
|
+
return msg;
|
|
524
|
+
});
|
|
525
|
+
messagesRef.current = updated;
|
|
526
|
+
return updated;
|
|
527
|
+
});
|
|
469
528
|
}
|
|
470
529
|
else {
|
|
471
530
|
// Fallback: Mark any streaming messages as completed (old behavior)
|
|
472
531
|
console.warn("⚠️ No messageId in completed event, falling back to marking all streaming messages as completed");
|
|
473
|
-
setMessages((prev) =>
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
532
|
+
setMessages((prev) => {
|
|
533
|
+
const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
|
|
534
|
+
? {
|
|
535
|
+
...msg,
|
|
536
|
+
isCompleted: true,
|
|
537
|
+
messageType: "completed",
|
|
538
|
+
}
|
|
539
|
+
: msg);
|
|
540
|
+
messagesRef.current = updated;
|
|
541
|
+
return updated;
|
|
542
|
+
});
|
|
480
543
|
}
|
|
544
|
+
// Ensure waiting state is cleared when stream completes
|
|
545
|
+
setIsWaitingForResponse(false);
|
|
481
546
|
shouldCreateNewMessage.current = false;
|
|
547
|
+
// Streaming finished; update indicator
|
|
548
|
+
resetDotsTimer();
|
|
482
549
|
break;
|
|
483
550
|
case "error":
|
|
484
551
|
console.error("❌ Stream error:", message.error);
|
|
485
552
|
setError(message.error || "Stream error occurred");
|
|
486
553
|
setIsWaitingForResponse(false);
|
|
487
554
|
shouldCreateNewMessage.current = false;
|
|
555
|
+
// Error ends streaming; update indicator
|
|
556
|
+
resetDotsTimer();
|
|
488
557
|
break;
|
|
489
558
|
default:
|
|
490
559
|
console.warn("❓ Unhandled message type:", {
|
|
@@ -508,6 +577,8 @@ export function AgentTerminal({ agentStub }) {
|
|
|
508
577
|
}
|
|
509
578
|
finally {
|
|
510
579
|
setIsConnecting(false);
|
|
580
|
+
// Guard: clear waiting state if connection finished without content
|
|
581
|
+
setIsWaitingForResponse(false);
|
|
511
582
|
}
|
|
512
583
|
}, [agent?.id, handleContentChunk, handleToolCall, handleToolResult]);
|
|
513
584
|
// Load agent data and messages
|
|
@@ -545,35 +616,51 @@ export function AgentTerminal({ agentStub }) {
|
|
|
545
616
|
setError(null);
|
|
546
617
|
setIsLoading(false);
|
|
547
618
|
// Initialize local context for a brand-new agent (not yet persisted)
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
name: editContext?.contentEditorItem?.name,
|
|
559
|
-
},
|
|
560
|
-
],
|
|
561
|
-
componentIds: editContext?.selection?.length
|
|
562
|
-
? editContext.selection
|
|
563
|
-
: undefined,
|
|
564
|
-
field: editContext?.focusedField &&
|
|
565
|
-
editContext.currentItemDescriptor
|
|
566
|
-
? {
|
|
567
|
-
fieldId: editContext.focusedField.fieldId,
|
|
568
|
-
itemId: editContext.focusedField.item.id,
|
|
569
|
-
}
|
|
570
|
-
: undefined,
|
|
571
|
-
},
|
|
572
|
-
},
|
|
619
|
+
// Prefer explicitly provided metadata; otherwise seed with page, selected componentIds, and focused field if available
|
|
620
|
+
if (initialMetadata) {
|
|
621
|
+
setAgentMetadata(initialMetadata);
|
|
622
|
+
// If an initial prompt is provided via metadata, seed the input once
|
|
623
|
+
try {
|
|
624
|
+
const maybePrompt = initialMetadata?.additionalData
|
|
625
|
+
?.initialPrompt;
|
|
626
|
+
if (typeof maybePrompt === "string" && maybePrompt.trim()) {
|
|
627
|
+
setPrompt(maybePrompt);
|
|
628
|
+
}
|
|
573
629
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
630
|
+
catch { }
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
const item = editContext?.currentItemDescriptor;
|
|
634
|
+
const initialLocalContext = item
|
|
635
|
+
? {
|
|
636
|
+
additionalData: {
|
|
637
|
+
context: {
|
|
638
|
+
pages: [
|
|
639
|
+
{
|
|
640
|
+
id: item.id,
|
|
641
|
+
language: item.language,
|
|
642
|
+
version: item.version,
|
|
643
|
+
name: editContext?.contentEditorItem?.name,
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
componentIds: editContext?.selection?.length
|
|
647
|
+
? editContext.selection
|
|
648
|
+
: undefined,
|
|
649
|
+
field: editContext?.focusedField?.fieldId &&
|
|
650
|
+
editContext.focusedField?.item?.id
|
|
651
|
+
? {
|
|
652
|
+
fieldId: editContext.focusedField.fieldId,
|
|
653
|
+
itemId: editContext.focusedField.item.id,
|
|
654
|
+
name: editContext.focusedField.fieldName,
|
|
655
|
+
}
|
|
656
|
+
: undefined,
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
}
|
|
660
|
+
: null;
|
|
661
|
+
if (initialLocalContext)
|
|
662
|
+
setAgentMetadata(initialLocalContext);
|
|
663
|
+
}
|
|
577
664
|
return;
|
|
578
665
|
}
|
|
579
666
|
setIsLoading(true);
|
|
@@ -639,6 +726,66 @@ export function AgentTerminal({ agentStub }) {
|
|
|
639
726
|
useEffect(() => {
|
|
640
727
|
loadAgent();
|
|
641
728
|
}, [loadAgent]);
|
|
729
|
+
// Focus prompt when requested globally (from AI command)
|
|
730
|
+
useEffect(() => {
|
|
731
|
+
const focusHandler = () => {
|
|
732
|
+
try {
|
|
733
|
+
if (textareaRef.current) {
|
|
734
|
+
textareaRef.current.focus();
|
|
735
|
+
// Move caret to end
|
|
736
|
+
const value = textareaRef.current.value || "";
|
|
737
|
+
textareaRef.current.selectionStart = value.length;
|
|
738
|
+
textareaRef.current.selectionEnd = value.length;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
catch { }
|
|
742
|
+
};
|
|
743
|
+
window.addEventListener("editor:focusAgentPrompt", focusHandler);
|
|
744
|
+
return () => window.removeEventListener("editor:focusAgentPrompt", focusHandler);
|
|
745
|
+
}, []);
|
|
746
|
+
// Load AI profiles for predefined prompts
|
|
747
|
+
useEffect(() => {
|
|
748
|
+
let cancelled = false;
|
|
749
|
+
(async () => {
|
|
750
|
+
try {
|
|
751
|
+
if (!editContext?.currentItemDescriptor)
|
|
752
|
+
return;
|
|
753
|
+
const fetched = await loadAiProfiles(editContext.currentItemDescriptor);
|
|
754
|
+
if (cancelled)
|
|
755
|
+
return;
|
|
756
|
+
setProfiles(fetched || []);
|
|
757
|
+
}
|
|
758
|
+
catch (e) {
|
|
759
|
+
console.error("Failed to load AI profiles", e);
|
|
760
|
+
}
|
|
761
|
+
})();
|
|
762
|
+
return () => {
|
|
763
|
+
cancelled = true;
|
|
764
|
+
};
|
|
765
|
+
}, [editContext?.currentItemDescriptor]);
|
|
766
|
+
// Select active profile based on agent.profileId or default to first
|
|
767
|
+
useEffect(() => {
|
|
768
|
+
if (!profiles || profiles.length === 0)
|
|
769
|
+
return;
|
|
770
|
+
const candidate = agent?.profileId
|
|
771
|
+
? (profiles.find((p) => p.id === agent?.profileId) ??
|
|
772
|
+
profiles[0])
|
|
773
|
+
: profiles[0];
|
|
774
|
+
if (candidate && (!activeProfile || activeProfile.id !== candidate.id)) {
|
|
775
|
+
setActiveProfile(candidate);
|
|
776
|
+
}
|
|
777
|
+
}, [profiles, agent?.profileId]);
|
|
778
|
+
// Update selected model when the active profile changes
|
|
779
|
+
useEffect(() => {
|
|
780
|
+
if (!activeProfile)
|
|
781
|
+
return;
|
|
782
|
+
const agentModelId = agent?.model;
|
|
783
|
+
const availableModelIds = (activeProfile.models || []).map((m) => m.id);
|
|
784
|
+
const nextModelId = agentModelId && availableModelIds.includes(agentModelId)
|
|
785
|
+
? agentModelId
|
|
786
|
+
: activeProfile.defaultModelId || activeProfile.models?.[0]?.id;
|
|
787
|
+
setSelectedModelId(nextModelId || undefined);
|
|
788
|
+
}, [activeProfile?.id]);
|
|
642
789
|
// Cleanup stream connection when component unmounts or agent changes
|
|
643
790
|
useEffect(() => {
|
|
644
791
|
return () => {
|
|
@@ -699,8 +846,9 @@ export function AgentTerminal({ agentStub }) {
|
|
|
699
846
|
agentId: agent.id,
|
|
700
847
|
message: prompt.trim(),
|
|
701
848
|
sessionId: editContext.sessionId,
|
|
702
|
-
profileId: "default",
|
|
703
|
-
profile: "default",
|
|
849
|
+
profileId: activeProfile?.id || "default",
|
|
850
|
+
profile: activeProfile?.name || "default",
|
|
851
|
+
model: selectedModelId,
|
|
704
852
|
itemid: editContext.currentItemDescriptor?.id || "",
|
|
705
853
|
language: editContext.currentItemDescriptor?.language || "en",
|
|
706
854
|
version: editContext.currentItemDescriptor?.version || 1,
|
|
@@ -713,6 +861,8 @@ export function AgentTerminal({ agentStub }) {
|
|
|
713
861
|
console.log("Starting agent:", request);
|
|
714
862
|
// Set waiting state to show dancing dots immediately
|
|
715
863
|
setIsWaitingForResponse(true);
|
|
864
|
+
// Start idle timer; dots appear only if no chunks for >1s
|
|
865
|
+
resetDotsTimer();
|
|
716
866
|
// Re-enable auto-scroll when user submits a new message
|
|
717
867
|
setShouldAutoScroll(true);
|
|
718
868
|
// No need to create a temporary message - the stream will create messages as needed
|
|
@@ -784,6 +934,79 @@ export function AgentTerminal({ agentStub }) {
|
|
|
784
934
|
}
|
|
785
935
|
}
|
|
786
936
|
};
|
|
937
|
+
// Send a message programmatically (used by quick-action buttons)
|
|
938
|
+
const sendQuickMessage = async (text) => {
|
|
939
|
+
if (!text.trim() || isSubmitting || !editContext)
|
|
940
|
+
return;
|
|
941
|
+
try {
|
|
942
|
+
setIsSubmitting(true);
|
|
943
|
+
setError(null);
|
|
944
|
+
const agentId = agent?.id;
|
|
945
|
+
if (!agentId)
|
|
946
|
+
return;
|
|
947
|
+
const userMessage = {
|
|
948
|
+
id: `user-${Date.now()}`,
|
|
949
|
+
agentId,
|
|
950
|
+
messageIndex: messages.length,
|
|
951
|
+
role: "user",
|
|
952
|
+
content: text.trim(),
|
|
953
|
+
name: "user",
|
|
954
|
+
messageType: "user",
|
|
955
|
+
isCompleted: true,
|
|
956
|
+
model: "",
|
|
957
|
+
tokensUsed: 0,
|
|
958
|
+
inputTokens: 0,
|
|
959
|
+
outputTokens: 0,
|
|
960
|
+
cachedInputTokens: 0,
|
|
961
|
+
inputTokenCost: 0,
|
|
962
|
+
outputTokenCost: 0,
|
|
963
|
+
cachedInputTokenCost: 0,
|
|
964
|
+
totalCost: 0,
|
|
965
|
+
currency: "USD",
|
|
966
|
+
createdDate: new Date().toISOString(),
|
|
967
|
+
};
|
|
968
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
969
|
+
const metaCtx = agentMetadata?.additionalData?.context;
|
|
970
|
+
const selectionFromCtx = metaCtx?.componentIds?.length
|
|
971
|
+
? metaCtx.componentIds
|
|
972
|
+
: undefined;
|
|
973
|
+
const effectiveSelection = selectionFromCtx?.length
|
|
974
|
+
? selectionFromCtx
|
|
975
|
+
: editContext.selection && editContext.selection.length > 0
|
|
976
|
+
? editContext.selection
|
|
977
|
+
: undefined;
|
|
978
|
+
const request = {
|
|
979
|
+
agentId: agent.id,
|
|
980
|
+
message: text.trim(),
|
|
981
|
+
sessionId: editContext.sessionId,
|
|
982
|
+
profileId: activeProfile?.id || "default",
|
|
983
|
+
profile: activeProfile?.name || "default",
|
|
984
|
+
model: selectedModelId,
|
|
985
|
+
itemid: editContext.currentItemDescriptor?.id || "",
|
|
986
|
+
language: editContext.currentItemDescriptor?.language || "en",
|
|
987
|
+
version: editContext.currentItemDescriptor?.version || 1,
|
|
988
|
+
selection: effectiveSelection,
|
|
989
|
+
selectedText: editContext.selectedRange?.text || undefined,
|
|
990
|
+
addSelectedComponents: !!effectiveSelection?.length,
|
|
991
|
+
addContextContent: false,
|
|
992
|
+
addAllContent: false,
|
|
993
|
+
};
|
|
994
|
+
setIsWaitingForResponse(true);
|
|
995
|
+
resetDotsTimer();
|
|
996
|
+
setShouldAutoScroll(true);
|
|
997
|
+
await startAgent(request);
|
|
998
|
+
await connectToStream();
|
|
999
|
+
}
|
|
1000
|
+
catch (err) {
|
|
1001
|
+
console.error("Failed to submit quick message:", err);
|
|
1002
|
+
setError("Failed to submit prompt");
|
|
1003
|
+
setIsWaitingForResponse(false);
|
|
1004
|
+
setMessages((prev) => prev.slice(0, -1));
|
|
1005
|
+
}
|
|
1006
|
+
finally {
|
|
1007
|
+
setIsSubmitting(false);
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
787
1010
|
// Context info bar helpers - must be declared before any early returns to keep hooks order stable
|
|
788
1011
|
const removeContextKey = useCallback(async (key, index) => {
|
|
789
1012
|
if (!agent?.id)
|
|
@@ -1046,11 +1269,58 @@ export function AgentTerminal({ agentStub }) {
|
|
|
1046
1269
|
editContext?.currentItemDescriptor,
|
|
1047
1270
|
editContext?.itemsRepository,
|
|
1048
1271
|
]);
|
|
1272
|
+
// Stop current execution/stream safely
|
|
1273
|
+
const handleStop = useCallback(() => {
|
|
1274
|
+
try {
|
|
1275
|
+
setIsWaitingForResponse(false);
|
|
1276
|
+
if (abortControllerRef.current) {
|
|
1277
|
+
abortControllerRef.current.abort();
|
|
1278
|
+
abortControllerRef.current = null;
|
|
1279
|
+
}
|
|
1280
|
+
setIsConnecting(false);
|
|
1281
|
+
setIsSubmitting(false);
|
|
1282
|
+
// Mark any in-progress streaming messages as completed in UI
|
|
1283
|
+
setMessages((prev) => {
|
|
1284
|
+
const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
|
|
1285
|
+
? { ...msg, isCompleted: true, messageType: "completed" }
|
|
1286
|
+
: msg);
|
|
1287
|
+
messagesRef.current = updated;
|
|
1288
|
+
return updated;
|
|
1289
|
+
});
|
|
1290
|
+
// Update indicator state
|
|
1291
|
+
resetDotsTimer();
|
|
1292
|
+
}
|
|
1293
|
+
catch (e) {
|
|
1294
|
+
console.error("Failed to stop agent execution", e);
|
|
1295
|
+
}
|
|
1296
|
+
}, [resetDotsTimer]);
|
|
1297
|
+
// Ensure waiting state resets when no active work remains
|
|
1298
|
+
useEffect(() => {
|
|
1299
|
+
if (isSubmitting || isConnecting)
|
|
1300
|
+
return;
|
|
1301
|
+
const streaming = hasActiveStreaming();
|
|
1302
|
+
if (!streaming && isWaitingForResponse) {
|
|
1303
|
+
setIsWaitingForResponse(false);
|
|
1304
|
+
resetDotsTimer();
|
|
1305
|
+
}
|
|
1306
|
+
}, [
|
|
1307
|
+
isSubmitting,
|
|
1308
|
+
isConnecting,
|
|
1309
|
+
messages,
|
|
1310
|
+
hasActiveStreaming,
|
|
1311
|
+
isWaitingForResponse,
|
|
1312
|
+
resetDotsTimer,
|
|
1313
|
+
]);
|
|
1049
1314
|
if (isLoading) {
|
|
1050
1315
|
return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), "Loading agent..."] }) }));
|
|
1051
1316
|
}
|
|
1052
1317
|
// Calculate total token usage for cost display
|
|
1053
1318
|
const totalTokens = calculateTotalTokens(messages);
|
|
1319
|
+
// Determine if the agent is actively executing (submitting, connecting, waiting, or streaming)
|
|
1320
|
+
const isExecuting = isSubmitting ||
|
|
1321
|
+
isConnecting ||
|
|
1322
|
+
isWaitingForResponse ||
|
|
1323
|
+
hasActiveStreaming();
|
|
1054
1324
|
const renderContextInfoBar = () => {
|
|
1055
1325
|
const ctx = agentMetadata?.additionalData?.context;
|
|
1056
1326
|
// Check if we have context chips or add buttons to show
|
|
@@ -1105,26 +1375,48 @@ export function AgentTerminal({ agentStub }) {
|
|
|
1105
1375
|
}
|
|
1106
1376
|
return (_jsx("div", { className: "border-t border-gray-100 px-4 py-2", children: _jsxs("div", { className: "flex items-center justify-between text-xs text-gray-600", children: [_jsx("div", { className: "flex flex-wrap items-center gap-2", children: chips }), _jsxs("div", { className: "ml-2 flex flex-wrap items-center gap-2", children: [canAddPage && (_jsxs(Button, { size: "sm", variant: "outline", onClick: addPagesToContext, children: [_jsx(Plus, { className: "mr-1 h-3 w-3", strokeWidth: 1 }), " Add current item"] })), canAddSelection && (_jsxs(Button, { size: "sm", variant: "outline", onClick: addSelectedComponentsToContext, children: [_jsx(Plus, { className: "mr-1 h-3 w-3", strokeWidth: 1 }), " Add selected components"] }))] })] }) }));
|
|
1107
1377
|
};
|
|
1108
|
-
return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-sm font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-sm text-red-700", children: error })] })] }) })), messages.length === 0 && !error && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "text-center", children: [_jsx(Bot, { className: "mx-auto mb-4 h-12 w-12 text-gray-400", strokeWidth: 1 }), _jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }) })),
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1378
|
+
return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-sm font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-sm text-red-700", children: error })] })] }) })), messages.length === 0 && !error && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "text-center", children: [_jsx(Bot, { className: "mx-auto mb-4 h-12 w-12 text-gray-400", strokeWidth: 1 }), _jsx("h3", { className: "mb-2 text-lg font-medium text-gray-900", children: "Start a conversation" }), _jsx("p", { className: "mb-4 text-sm text-gray-500", children: "Send a message to begin working with your AI agent." }), _jsx("div", { className: "text-xs text-gray-400", children: "Your agent can help with content editing, research, and automation tasks." })] }) })), _jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: groupConsecutiveMessages(messages).map((group, groupIndex) => {
|
|
1379
|
+
if (group.type === "user" && group.messages[0]) {
|
|
1380
|
+
// Render user message
|
|
1381
|
+
return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
|
|
1382
|
+
}
|
|
1383
|
+
else {
|
|
1384
|
+
// Render bundled assistant messages
|
|
1385
|
+
// Check if this group contains any streaming message
|
|
1386
|
+
const hasStreamingMessage = group.messages.some((msg) => !msg.isCompleted && msg.messageType === "streaming");
|
|
1387
|
+
const convertedMessages = convertAgentMessagesToAiFormat(group.messages);
|
|
1388
|
+
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isSubmitting && !isConnecting, editOperations: [], error: error || undefined, onQuickAction: (action) => {
|
|
1389
|
+
const text = (action.prompt ||
|
|
1390
|
+
action.value ||
|
|
1391
|
+
action.label ||
|
|
1392
|
+
"").trim();
|
|
1393
|
+
if (!text)
|
|
1394
|
+
return;
|
|
1395
|
+
// Stop any current execution before sending the next message
|
|
1396
|
+
if (isExecuting) {
|
|
1397
|
+
try {
|
|
1398
|
+
handleStop();
|
|
1399
|
+
}
|
|
1400
|
+
catch { }
|
|
1401
|
+
}
|
|
1402
|
+
sendQuickMessage(text);
|
|
1403
|
+
} }, groupIndex));
|
|
1404
|
+
}
|
|
1405
|
+
}) }), showDots && _jsx(DancingDots, {}), _jsx("div", { ref: messagesEndRef })] }), renderContextInfoBar(), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
|
|
1123
1406
|
setPrompt(e.target.value);
|
|
1124
1407
|
// Reset history index when user starts typing
|
|
1125
1408
|
if (currentHistoryIndex !== -1) {
|
|
1126
1409
|
setCurrentHistoryIndex(-1);
|
|
1127
1410
|
}
|
|
1128
|
-
}, onKeyDown: handleKeyPress, placeholder: "Type your message... (Enter to send, Ctrl+Enter for new line)", className: "resize-nones min-h-[80px] flex-1 text-xs", disabled: isSubmitting })
|
|
1411
|
+
}, onKeyDown: handleKeyPress, placeholder: "Type your message... (Enter to send, Ctrl+Enter for new line)", className: "resize-nones min-h-[80px] flex-1 text-xs", disabled: isSubmitting }) }), _jsxs("div", { className: "flex items-stretch justify-between gap-2", children: [_jsxs("div", { className: "mt-2 flex items-center justify-start gap-2", children: [profiles?.length > 0 && (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: activeProfile?.id || "", onChange: (e) => {
|
|
1412
|
+
const p = profiles.find((x) => x.id === e.target.value);
|
|
1413
|
+
if (p)
|
|
1414
|
+
setActiveProfile(p);
|
|
1415
|
+
}, title: "Profile", "aria-label": "Profile", children: profiles.map((p) => (_jsx("option", { value: p.id, children: p.name }, p.id))) })), activeProfile?.models?.length ? (_jsx("select", { className: "h-5 rounded border px-1.5 text-[10px] text-gray-500", value: selectedModelId || "", onChange: (e) => setSelectedModelId(e.target.value), title: "Model", "aria-label": "Model", children: activeProfile.models.map((m) => (_jsx("option", { value: m.id, children: m.name }, m.id))) })) : null, activeProfile?.prompts?.length ? (_jsxs(Popover, { open: showPredefined, onOpenChange: setShowPredefined, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { className: "rounded p-1 hover:bg-gray-100", onClick: () => { }, title: "Predefined prompts", "aria-label": "Predefined prompts", children: _jsx(Wand2, { className: "h-3 w-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: activeProfile.prompts.map((p, index) => (_jsx("div", { className: "cursor-pointer rounded p-1.5 text-xs text-gray-700 hover:bg-gray-100", onClick: () => {
|
|
1416
|
+
setPrompt(p.prompt);
|
|
1417
|
+
setShowPredefined(false);
|
|
1418
|
+
if (textareaRef.current)
|
|
1419
|
+
textareaRef.current.focus();
|
|
1420
|
+
}, children: p.title }, index))) }) })] })) : null, _jsx(AgentCostDisplay, { totalTokens: totalTokens })] }), _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer self-end rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] })] }));
|
|
1129
1421
|
}
|
|
1130
1422
|
//# sourceMappingURL=AgentTerminal.js.map
|