@alpaca-editor/core 1.0.4063 → 1.0.4064
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/config/config.js +3 -21
- package/dist/config/config.js.map +1 -1
- package/dist/editor/ConfirmationDialog.js +1 -1
- package/dist/editor/ConfirmationDialog.js.map +1 -1
- package/dist/editor/ContextMenu.js +1 -0
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/FieldListField.js +2 -2
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/FieldListFieldWithFallbacks.js +1 -1
- package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
- package/dist/editor/MainLayout.d.ts +2 -0
- package/dist/editor/MainLayout.js +8 -0
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/ScrollingContentTree.js +9 -4
- package/dist/editor/ScrollingContentTree.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.js +382 -3
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +44 -3
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/client/EditorClient.js +54 -16
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +4 -2
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +1 -1
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/VersionSelector.js +1 -1
- package/dist/editor/menubar/VersionSelector.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +2 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +7 -0
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +2 -1
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/reviews/Comment.js +57 -9
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentEditor.js +2 -2
- package/dist/editor/reviews/CommentEditor.js.map +1 -1
- package/dist/editor/reviews/CommentPopover.js +1 -1
- package/dist/editor/reviews/CommentPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.js +5 -5
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +41 -0
- package/dist/editor/services/agentService.js +10 -0
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +3 -2
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/SidebarView.d.ts +2 -1
- package/dist/editor/sidebar/SidebarView.js +2 -2
- package/dist/editor/sidebar/SidebarView.js.map +1 -1
- package/dist/editor/ui/PerfectTree.js +1 -1
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/ui/SimpleTabs.js +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/NewPage.js +2 -2
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/styles.css +6 -0
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/config/config.tsx +5 -20
- package/src/editor/ConfirmationDialog.tsx +4 -1
- package/src/editor/ContextMenu.tsx +1 -0
- package/src/editor/FieldListField.tsx +13 -11
- package/src/editor/FieldListFieldWithFallbacks.tsx +1 -0
- package/src/editor/MainLayout.tsx +11 -0
- package/src/editor/ScrollingContentTree.tsx +10 -5
- package/src/editor/ai/AgentTerminal.tsx +555 -1
- package/src/editor/ai/Agents.tsx +64 -8
- package/src/editor/client/EditorClient.tsx +98 -29
- package/src/editor/client/editContext.ts +5 -2
- package/src/editor/media-selector/TreeSelector.tsx +1 -0
- package/src/editor/menubar/VersionSelector.tsx +4 -1
- package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +15 -2
- package/src/editor/page-viewer/EditorForm.tsx +13 -0
- package/src/editor/page-viewer/PageViewerFrame.tsx +2 -1
- package/src/editor/reviews/Comment.tsx +65 -9
- package/src/editor/reviews/CommentEditor.tsx +8 -1
- package/src/editor/reviews/CommentPopover.tsx +1 -0
- package/src/editor/reviews/CommentView.tsx +24 -3
- package/src/editor/services/agentService.ts +58 -0
- package/src/editor/sidebar/ComponentTree.tsx +6 -2
- package/src/editor/sidebar/SidebarView.tsx +8 -7
- package/src/editor/ui/PerfectTree.tsx +2 -1
- package/src/editor/ui/SimpleTabs.tsx +1 -1
- package/src/index.ts +0 -2
- package/src/revision.ts +2 -2
- package/src/splash-screen/NewPage.tsx +2 -2
- package/src/types.ts +2 -1
- package/styles.css +0 -2
- package/dist/fonts/index.d.ts +0 -4
- package/dist/fonts/index.js +0 -9
- package/dist/fonts/index.js.map +0 -1
- package/src/fonts/Geist-Black.woff2 +0 -0
- package/src/fonts/Geist-Bold.woff2 +0 -0
- package/src/fonts/Geist-ExtraBold.woff2 +0 -0
- package/src/fonts/Geist-ExtraLight.woff2 +0 -0
- package/src/fonts/Geist-Light.woff2 +0 -0
- package/src/fonts/Geist-Medium.woff2 +0 -0
- package/src/fonts/Geist-Regular.woff2 +0 -0
- package/src/fonts/Geist-SemiBold.woff2 +0 -0
- package/src/fonts/Geist-Thin.woff2 +0 -0
- package/src/fonts/Geist[wght].woff2 +0 -0
- package/src/fonts/index.ts +0 -10
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import React, { useEffect, useState, useRef, useCallback } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Send,
|
|
4
|
+
Bot,
|
|
5
|
+
AlertCircle,
|
|
6
|
+
Loader2,
|
|
7
|
+
User,
|
|
8
|
+
X,
|
|
9
|
+
FileText,
|
|
10
|
+
Puzzle,
|
|
11
|
+
Type,
|
|
12
|
+
Plus,
|
|
13
|
+
MessageSquare,
|
|
14
|
+
} from "lucide-react";
|
|
3
15
|
import { DancingDots } from "./DancingDots";
|
|
4
16
|
import {
|
|
5
17
|
AgentChatMessage,
|
|
@@ -10,6 +22,8 @@ import {
|
|
|
10
22
|
StartAgentRequest,
|
|
11
23
|
Agent,
|
|
12
24
|
AgentDetails,
|
|
25
|
+
updateAgentMetadata,
|
|
26
|
+
AgentMetadata,
|
|
13
27
|
} from "../services/agentService";
|
|
14
28
|
import { useEditContext } from "../client/editContext";
|
|
15
29
|
import { Textarea } from "../../components/ui/textarea";
|
|
@@ -17,6 +31,8 @@ import { Button } from "../../components/ui/button";
|
|
|
17
31
|
import { AiResponseMessage } from "./AiResponseMessage";
|
|
18
32
|
import { AgentCostDisplay } from "./AgentCostDisplay";
|
|
19
33
|
import { Message } from "./AiTerminal";
|
|
34
|
+
import { getComponentById } from "../componentTreeHelper";
|
|
35
|
+
import { Comment } from "../../types";
|
|
20
36
|
|
|
21
37
|
// Simple user message component
|
|
22
38
|
const UserMessage = ({ message }: { message: AgentChatMessage }) => {
|
|
@@ -160,7 +176,19 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
|
|
|
160
176
|
const [isLoading, setIsLoading] = useState(false);
|
|
161
177
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
162
178
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
179
|
+
const [agentMetadata, setAgentMetadata] = useState<AgentMetadata | null>(
|
|
180
|
+
null,
|
|
181
|
+
);
|
|
163
182
|
const [isWaitingForResponse, setIsWaitingForResponse] = useState(false);
|
|
183
|
+
const [resolvedPageName, setResolvedPageName] = useState<string | undefined>(
|
|
184
|
+
undefined,
|
|
185
|
+
);
|
|
186
|
+
const [resolvedComponentName, setResolvedComponentName] = useState<
|
|
187
|
+
string | undefined
|
|
188
|
+
>(undefined);
|
|
189
|
+
const [resolvedFieldName, setResolvedFieldName] = useState<
|
|
190
|
+
string | undefined
|
|
191
|
+
>(undefined);
|
|
164
192
|
const [promptHistory, setPromptHistory] = useState<string[]>(() => {
|
|
165
193
|
if (typeof window !== "undefined") {
|
|
166
194
|
return JSON.parse(
|
|
@@ -732,6 +760,37 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
|
|
|
732
760
|
setMessages([]);
|
|
733
761
|
setError(null);
|
|
734
762
|
setIsLoading(false);
|
|
763
|
+
|
|
764
|
+
// Initialize local context for a brand-new agent (not yet persisted)
|
|
765
|
+
const item = editContext?.currentItemDescriptor;
|
|
766
|
+
const initialLocalContext: AgentMetadata | null = item
|
|
767
|
+
? {
|
|
768
|
+
additionalData: {
|
|
769
|
+
context: {
|
|
770
|
+
pages: [
|
|
771
|
+
{
|
|
772
|
+
id: item.id,
|
|
773
|
+
language: item.language,
|
|
774
|
+
version: item.version,
|
|
775
|
+
name: editContext?.contentEditorItem?.name,
|
|
776
|
+
},
|
|
777
|
+
],
|
|
778
|
+
componentIds: editContext?.selection?.length
|
|
779
|
+
? editContext.selection
|
|
780
|
+
: undefined,
|
|
781
|
+
field:
|
|
782
|
+
editContext?.focusedField &&
|
|
783
|
+
editContext.currentItemDescriptor
|
|
784
|
+
? {
|
|
785
|
+
fieldId: editContext.focusedField.fieldId,
|
|
786
|
+
itemId: editContext.focusedField.item.id,
|
|
787
|
+
}
|
|
788
|
+
: undefined,
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
}
|
|
792
|
+
: null;
|
|
793
|
+
if (initialLocalContext) setAgentMetadata(initialLocalContext);
|
|
735
794
|
return;
|
|
736
795
|
}
|
|
737
796
|
|
|
@@ -743,6 +802,20 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
|
|
|
743
802
|
setAgent(agentData);
|
|
744
803
|
setMessages(agentData.messages || []);
|
|
745
804
|
|
|
805
|
+
// Parse metadata from DB if present (do not seed for existing agents)
|
|
806
|
+
const parsedMeta: AgentMetadata | null = (() => {
|
|
807
|
+
try {
|
|
808
|
+
return agentData.metadata
|
|
809
|
+
? (JSON.parse(agentData.metadata) as AgentMetadata)
|
|
810
|
+
: null;
|
|
811
|
+
} catch {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
})();
|
|
815
|
+
|
|
816
|
+
// For existing agents, use database metadata or none
|
|
817
|
+
setAgentMetadata(parsedMeta);
|
|
818
|
+
|
|
746
819
|
// Connect to stream if agent is running (handle both string and numeric status)
|
|
747
820
|
const isRunning =
|
|
748
821
|
agentData.status === "running" || (agentData.status as any) === 1;
|
|
@@ -846,14 +919,32 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
|
|
|
846
919
|
|
|
847
920
|
setMessages((prev) => [...prev, userMessage]);
|
|
848
921
|
|
|
922
|
+
const metaCtx = (agentMetadata as any)?.additionalData?.context as
|
|
923
|
+
| { componentIds?: string[] }
|
|
924
|
+
| undefined;
|
|
925
|
+
const selectionFromCtx = metaCtx?.componentIds?.length
|
|
926
|
+
? metaCtx.componentIds
|
|
927
|
+
: undefined;
|
|
928
|
+
const effectiveSelection = selectionFromCtx?.length
|
|
929
|
+
? selectionFromCtx
|
|
930
|
+
: editContext.selection && editContext.selection.length > 0
|
|
931
|
+
? editContext.selection
|
|
932
|
+
: undefined;
|
|
933
|
+
|
|
849
934
|
const request: StartAgentRequest = {
|
|
850
935
|
agentId: agent.id,
|
|
851
936
|
message: prompt.trim(),
|
|
852
937
|
sessionId: editContext.sessionId,
|
|
853
938
|
profileId: "default", // TODO: Get from context or settings
|
|
939
|
+
profile: "default",
|
|
854
940
|
itemid: editContext.currentItemDescriptor?.id || "",
|
|
855
941
|
language: editContext.currentItemDescriptor?.language || "en",
|
|
856
942
|
version: editContext.currentItemDescriptor?.version || 1,
|
|
943
|
+
selection: effectiveSelection,
|
|
944
|
+
selectedText: editContext.selectedRange?.text || undefined,
|
|
945
|
+
addSelectedComponents: !!effectiveSelection?.length,
|
|
946
|
+
addContextContent: false,
|
|
947
|
+
addAllContent: false,
|
|
857
948
|
};
|
|
858
949
|
|
|
859
950
|
console.log("Starting agent:", request);
|
|
@@ -940,6 +1031,311 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
|
|
|
940
1031
|
}
|
|
941
1032
|
};
|
|
942
1033
|
|
|
1034
|
+
// Context info bar helpers - must be declared before any early returns to keep hooks order stable
|
|
1035
|
+
const removeContextKey = useCallback(
|
|
1036
|
+
async (
|
|
1037
|
+
key: "pages" | "componentIds" | "field" | "comment",
|
|
1038
|
+
index?: number,
|
|
1039
|
+
) => {
|
|
1040
|
+
if (!agent?.id) return;
|
|
1041
|
+
const current = agentMetadata || {};
|
|
1042
|
+
const next: AgentMetadata = {
|
|
1043
|
+
...current,
|
|
1044
|
+
additionalData: {
|
|
1045
|
+
...(current.additionalData || {}),
|
|
1046
|
+
context: {
|
|
1047
|
+
...((current.additionalData &&
|
|
1048
|
+
(current.additionalData as any).context) ||
|
|
1049
|
+
{}),
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
} as AgentMetadata;
|
|
1053
|
+
|
|
1054
|
+
if (next.additionalData && (next.additionalData as any).context) {
|
|
1055
|
+
const ctx = (next.additionalData as any).context;
|
|
1056
|
+
if (key === "pages" && typeof index === "number" && ctx.pages) {
|
|
1057
|
+
ctx.pages.splice(index, 1);
|
|
1058
|
+
if (ctx.pages.length === 0) delete ctx.pages;
|
|
1059
|
+
} else if (
|
|
1060
|
+
key === "componentIds" &&
|
|
1061
|
+
typeof index === "number" &&
|
|
1062
|
+
ctx.componentIds
|
|
1063
|
+
) {
|
|
1064
|
+
ctx.componentIds.splice(index, 1);
|
|
1065
|
+
if (ctx.componentIds.length === 0) delete ctx.componentIds;
|
|
1066
|
+
} else if (key === "field") {
|
|
1067
|
+
delete ctx.field;
|
|
1068
|
+
} else if (key === "comment") {
|
|
1069
|
+
delete ctx.comment;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
try {
|
|
1074
|
+
// For new (not yet persisted) agents, only update local state
|
|
1075
|
+
if (agent.status === "new") {
|
|
1076
|
+
setAgentMetadata(next);
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Persisted agents: update server and local cache
|
|
1081
|
+
await updateAgentMetadata(agent.id, next);
|
|
1082
|
+
setAgentMetadata(next);
|
|
1083
|
+
setAgent((prev) =>
|
|
1084
|
+
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
1085
|
+
);
|
|
1086
|
+
} catch (e) {
|
|
1087
|
+
console.error("Failed to update agent metadata", e);
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
[agent?.id, agentMetadata],
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
const addPagesToContext = async () => {
|
|
1094
|
+
if (!agent?.id || !editContext?.currentItemDescriptor) return;
|
|
1095
|
+
|
|
1096
|
+
// Add the current page (currentItemDescriptor represents the current page)
|
|
1097
|
+
// Note: Currently only supports adding the current page. To support multiple pages,
|
|
1098
|
+
// we'd need a page selection mechanism in the UI (e.g., a page picker dialog)
|
|
1099
|
+
const item = editContext.currentItemDescriptor;
|
|
1100
|
+
const pageToAdd = {
|
|
1101
|
+
id: item.id,
|
|
1102
|
+
language: item.language,
|
|
1103
|
+
version: item.version,
|
|
1104
|
+
name: editContext.contentEditorItem?.name,
|
|
1105
|
+
path: agent?.itemPath,
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
const current = agentMetadata || {};
|
|
1109
|
+
const currentPages =
|
|
1110
|
+
((current.additionalData as any)?.context?.pages as Array<any>) || [];
|
|
1111
|
+
|
|
1112
|
+
// Check if this page is already in context
|
|
1113
|
+
const existingPageIds = new Set(
|
|
1114
|
+
currentPages.map((p: any) => `${p.id}-${p.language}-${p.version}`),
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
if (
|
|
1118
|
+
existingPageIds.has(
|
|
1119
|
+
`${pageToAdd.id}-${pageToAdd.language}-${pageToAdd.version}`,
|
|
1120
|
+
)
|
|
1121
|
+
) {
|
|
1122
|
+
return; // Page already exists
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
const next: AgentMetadata = {
|
|
1126
|
+
...current,
|
|
1127
|
+
additionalData: {
|
|
1128
|
+
...(current.additionalData || {}),
|
|
1129
|
+
context: {
|
|
1130
|
+
...((current.additionalData &&
|
|
1131
|
+
(current.additionalData as any).context) ||
|
|
1132
|
+
{}),
|
|
1133
|
+
pages: [...currentPages, pageToAdd],
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
} as AgentMetadata;
|
|
1137
|
+
|
|
1138
|
+
try {
|
|
1139
|
+
if (agent.status === "new") {
|
|
1140
|
+
setAgentMetadata(next);
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
await updateAgentMetadata(agent.id, next);
|
|
1144
|
+
setAgentMetadata(next);
|
|
1145
|
+
setAgent((prev) =>
|
|
1146
|
+
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
1147
|
+
);
|
|
1148
|
+
} catch (e) {
|
|
1149
|
+
console.error("Failed to update agent metadata (add pages)", e);
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const addSelectedComponentsToContext = async () => {
|
|
1154
|
+
if (!agent?.id || !editContext?.selection?.length) return;
|
|
1155
|
+
|
|
1156
|
+
const current = agentMetadata || {};
|
|
1157
|
+
const currentComponentIds =
|
|
1158
|
+
((current.additionalData as any)?.context?.componentIds as string[]) ||
|
|
1159
|
+
[];
|
|
1160
|
+
|
|
1161
|
+
// Merge with existing components, avoiding duplicates
|
|
1162
|
+
const existingIds = new Set(currentComponentIds);
|
|
1163
|
+
const newComponentIds = editContext.selection.filter(
|
|
1164
|
+
(id) => !existingIds.has(id),
|
|
1165
|
+
);
|
|
1166
|
+
|
|
1167
|
+
if (newComponentIds.length === 0) return; // No new components to add
|
|
1168
|
+
|
|
1169
|
+
const next: AgentMetadata = {
|
|
1170
|
+
...current,
|
|
1171
|
+
additionalData: {
|
|
1172
|
+
...(current.additionalData || {}),
|
|
1173
|
+
context: {
|
|
1174
|
+
...((current.additionalData &&
|
|
1175
|
+
(current.additionalData as any).context) ||
|
|
1176
|
+
{}),
|
|
1177
|
+
componentIds: [...currentComponentIds, ...newComponentIds],
|
|
1178
|
+
},
|
|
1179
|
+
},
|
|
1180
|
+
} as AgentMetadata;
|
|
1181
|
+
|
|
1182
|
+
try {
|
|
1183
|
+
if (agent.status === "new") {
|
|
1184
|
+
setAgentMetadata(next);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
await updateAgentMetadata(agent.id, next);
|
|
1188
|
+
setAgentMetadata(next);
|
|
1189
|
+
setAgent((prev) =>
|
|
1190
|
+
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
1191
|
+
);
|
|
1192
|
+
} catch (e) {
|
|
1193
|
+
console.error("Failed to update agent metadata (add components)", e);
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
const addCommentToContext = async (comment: Comment) => {
|
|
1198
|
+
if (!agent?.id) return;
|
|
1199
|
+
|
|
1200
|
+
const selectedText = (() => {
|
|
1201
|
+
if (
|
|
1202
|
+
typeof comment.rangeStart === "number" &&
|
|
1203
|
+
typeof comment.rangeEnd === "number" &&
|
|
1204
|
+
comment.fieldValue
|
|
1205
|
+
) {
|
|
1206
|
+
try {
|
|
1207
|
+
return comment.fieldValue.substring(
|
|
1208
|
+
Math.max(0, comment.rangeStart),
|
|
1209
|
+
Math.max(comment.rangeStart, comment.rangeEnd),
|
|
1210
|
+
);
|
|
1211
|
+
} catch {
|
|
1212
|
+
return undefined;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return undefined;
|
|
1216
|
+
})();
|
|
1217
|
+
|
|
1218
|
+
const current = agentMetadata || {};
|
|
1219
|
+
const next: AgentMetadata = {
|
|
1220
|
+
...current,
|
|
1221
|
+
additionalData: {
|
|
1222
|
+
...(current.additionalData || {}),
|
|
1223
|
+
context: {
|
|
1224
|
+
...((current.additionalData &&
|
|
1225
|
+
(current.additionalData as any).context) ||
|
|
1226
|
+
{}),
|
|
1227
|
+
comment: {
|
|
1228
|
+
id: comment.id,
|
|
1229
|
+
text: comment.text,
|
|
1230
|
+
fieldName: comment.fieldName,
|
|
1231
|
+
itemName: comment.itemName,
|
|
1232
|
+
author: comment.author,
|
|
1233
|
+
selectedText,
|
|
1234
|
+
rangeStart: comment.rangeStart,
|
|
1235
|
+
rangeEnd: comment.rangeEnd,
|
|
1236
|
+
},
|
|
1237
|
+
},
|
|
1238
|
+
},
|
|
1239
|
+
} as AgentMetadata;
|
|
1240
|
+
|
|
1241
|
+
try {
|
|
1242
|
+
if (agent.status === "new") {
|
|
1243
|
+
setAgentMetadata(next);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
await updateAgentMetadata(agent.id, next);
|
|
1247
|
+
setAgentMetadata(next);
|
|
1248
|
+
setAgent((prev) =>
|
|
1249
|
+
prev ? { ...prev, metadata: JSON.stringify(next) } : prev,
|
|
1250
|
+
);
|
|
1251
|
+
} catch (e) {
|
|
1252
|
+
console.error("Failed to update agent metadata (add comment)", e);
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
// Resolve display names when metadata or editor state changes
|
|
1257
|
+
useEffect(() => {
|
|
1258
|
+
const metaCtx = (agentMetadata as any)?.additionalData?.context as
|
|
1259
|
+
| {
|
|
1260
|
+
pages?: Array<{
|
|
1261
|
+
id: string;
|
|
1262
|
+
language: string;
|
|
1263
|
+
version: number;
|
|
1264
|
+
name?: string;
|
|
1265
|
+
}>;
|
|
1266
|
+
componentIds?: string[];
|
|
1267
|
+
field?: { fieldId: string; itemId: string };
|
|
1268
|
+
}
|
|
1269
|
+
| undefined;
|
|
1270
|
+
if (!metaCtx) {
|
|
1271
|
+
setResolvedPageName(undefined);
|
|
1272
|
+
setResolvedComponentName(undefined);
|
|
1273
|
+
setResolvedFieldName(undefined);
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// Page names (for now, just resolve the first page)
|
|
1278
|
+
if (metaCtx.pages?.length) {
|
|
1279
|
+
const firstPage = metaCtx.pages[0];
|
|
1280
|
+
if (firstPage) {
|
|
1281
|
+
let name: string | undefined = firstPage.name;
|
|
1282
|
+
if (
|
|
1283
|
+
!name &&
|
|
1284
|
+
editContext?.contentEditorItem?.descriptor.id === firstPage.id &&
|
|
1285
|
+
editContext?.contentEditorItem?.name
|
|
1286
|
+
) {
|
|
1287
|
+
name = editContext.contentEditorItem.name;
|
|
1288
|
+
} else if (!name && agent?.itemPath) {
|
|
1289
|
+
name = agent.itemPath.split("/").pop() || undefined;
|
|
1290
|
+
}
|
|
1291
|
+
setResolvedPageName(name);
|
|
1292
|
+
} else {
|
|
1293
|
+
setResolvedPageName(undefined);
|
|
1294
|
+
}
|
|
1295
|
+
} else setResolvedPageName(undefined);
|
|
1296
|
+
|
|
1297
|
+
// Component names (for now, just resolve the first component)
|
|
1298
|
+
if (metaCtx.componentIds?.length && editContext?.page) {
|
|
1299
|
+
const firstComponentId = metaCtx.componentIds[0];
|
|
1300
|
+
if (firstComponentId) {
|
|
1301
|
+
try {
|
|
1302
|
+
const comp = getComponentById(firstComponentId, editContext.page);
|
|
1303
|
+
setResolvedComponentName(comp?.name);
|
|
1304
|
+
} catch {
|
|
1305
|
+
setResolvedComponentName(undefined);
|
|
1306
|
+
}
|
|
1307
|
+
} else {
|
|
1308
|
+
setResolvedComponentName(undefined);
|
|
1309
|
+
}
|
|
1310
|
+
} else setResolvedComponentName(undefined);
|
|
1311
|
+
|
|
1312
|
+
// Field name (async)
|
|
1313
|
+
(async () => {
|
|
1314
|
+
if (metaCtx.field && editContext?.itemsRepository) {
|
|
1315
|
+
try {
|
|
1316
|
+
const field = await editContext.itemsRepository.getField({
|
|
1317
|
+
item: {
|
|
1318
|
+
id: metaCtx.field.itemId,
|
|
1319
|
+
language: editContext.currentItemDescriptor?.language || "en",
|
|
1320
|
+
version: editContext.currentItemDescriptor?.version || 0,
|
|
1321
|
+
},
|
|
1322
|
+
fieldId: metaCtx.field.fieldId,
|
|
1323
|
+
});
|
|
1324
|
+
setResolvedFieldName(field?.name);
|
|
1325
|
+
} catch {
|
|
1326
|
+
setResolvedFieldName(undefined);
|
|
1327
|
+
}
|
|
1328
|
+
} else setResolvedFieldName(undefined);
|
|
1329
|
+
})();
|
|
1330
|
+
}, [
|
|
1331
|
+
agentMetadata,
|
|
1332
|
+
editContext?.page,
|
|
1333
|
+
editContext?.contentEditorItem,
|
|
1334
|
+
agent?.itemPath,
|
|
1335
|
+
editContext?.currentItemDescriptor,
|
|
1336
|
+
editContext?.itemsRepository,
|
|
1337
|
+
]);
|
|
1338
|
+
|
|
943
1339
|
if (isLoading) {
|
|
944
1340
|
return (
|
|
945
1341
|
<div className="flex h-full items-center justify-center">
|
|
@@ -954,6 +1350,161 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
|
|
|
954
1350
|
// Calculate total token usage for cost display
|
|
955
1351
|
const totalTokens = calculateTotalTokens(messages);
|
|
956
1352
|
|
|
1353
|
+
const renderContextInfoBar = () => {
|
|
1354
|
+
const ctx = (agentMetadata as any)?.additionalData?.context as
|
|
1355
|
+
| {
|
|
1356
|
+
pages?: Array<{
|
|
1357
|
+
id: string;
|
|
1358
|
+
language: string;
|
|
1359
|
+
version: number;
|
|
1360
|
+
path?: string;
|
|
1361
|
+
name?: string;
|
|
1362
|
+
}>;
|
|
1363
|
+
componentIds?: string[];
|
|
1364
|
+
field?: { fieldId: string; itemId: string; name?: string };
|
|
1365
|
+
}
|
|
1366
|
+
| undefined;
|
|
1367
|
+
|
|
1368
|
+
// Check if we have context chips or add buttons to show
|
|
1369
|
+
const hasContext =
|
|
1370
|
+
ctx && (ctx.pages?.length || ctx.componentIds?.length || ctx.field);
|
|
1371
|
+
|
|
1372
|
+
// Check if current page is already in context
|
|
1373
|
+
const currentPageAlreadyAdded =
|
|
1374
|
+
editContext?.currentItemDescriptor &&
|
|
1375
|
+
ctx?.pages?.some(
|
|
1376
|
+
(page) =>
|
|
1377
|
+
page.id === editContext.currentItemDescriptor?.id &&
|
|
1378
|
+
page.language === editContext.currentItemDescriptor?.language &&
|
|
1379
|
+
page.version === editContext.currentItemDescriptor?.version,
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
// Check if current selection components are already in context
|
|
1383
|
+
const selectedComponentsAlreadyAdded =
|
|
1384
|
+
editContext?.selection?.length &&
|
|
1385
|
+
editContext.selection.every((selectedId) =>
|
|
1386
|
+
ctx?.componentIds?.includes(selectedId),
|
|
1387
|
+
);
|
|
1388
|
+
|
|
1389
|
+
const canAddPage =
|
|
1390
|
+
!!editContext?.currentItemDescriptor && !currentPageAlreadyAdded;
|
|
1391
|
+
const canAddSelection =
|
|
1392
|
+
!!editContext?.selection?.length && !selectedComponentsAlreadyAdded;
|
|
1393
|
+
|
|
1394
|
+
if (!hasContext && !canAddPage && !canAddSelection) return null;
|
|
1395
|
+
|
|
1396
|
+
const Chip = ({
|
|
1397
|
+
icon,
|
|
1398
|
+
label,
|
|
1399
|
+
onRemove,
|
|
1400
|
+
}: {
|
|
1401
|
+
icon: React.ReactNode;
|
|
1402
|
+
label: string;
|
|
1403
|
+
onRemove: () => void;
|
|
1404
|
+
}) => (
|
|
1405
|
+
<span className="inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-100 px-2 py-0.5 text-xs text-gray-700">
|
|
1406
|
+
{icon}
|
|
1407
|
+
<span className="max-w-[240px] truncate" title={label}>
|
|
1408
|
+
{label}
|
|
1409
|
+
</span>
|
|
1410
|
+
<button
|
|
1411
|
+
onClick={onRemove}
|
|
1412
|
+
className="ml-0.5 rounded p-0.5 hover:bg-gray-200"
|
|
1413
|
+
aria-label="Remove context"
|
|
1414
|
+
>
|
|
1415
|
+
<X className="h-3 w-3" strokeWidth={1} />
|
|
1416
|
+
</button>
|
|
1417
|
+
</span>
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
const chips: React.ReactNode[] = [];
|
|
1421
|
+
|
|
1422
|
+
// Render page chips
|
|
1423
|
+
if (ctx?.pages?.length) {
|
|
1424
|
+
ctx.pages.forEach((page, index) => {
|
|
1425
|
+
const pageLabelName =
|
|
1426
|
+
page.name ||
|
|
1427
|
+
resolvedPageName ||
|
|
1428
|
+
(agent?.itemPath ? agent.itemPath.split("/").pop() : undefined);
|
|
1429
|
+
const pageLabel = `${pageLabelName || page.path || page.id} (${page.language}/${page.version})`;
|
|
1430
|
+
chips.push(
|
|
1431
|
+
<Chip
|
|
1432
|
+
key={`page-${index}`}
|
|
1433
|
+
icon={
|
|
1434
|
+
<FileText className="h-3 w-3 text-gray-500" strokeWidth={1} />
|
|
1435
|
+
}
|
|
1436
|
+
label={`Page: ${pageLabel}`}
|
|
1437
|
+
onRemove={() => removeContextKey("pages", index)}
|
|
1438
|
+
/>,
|
|
1439
|
+
);
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// Render component chips
|
|
1444
|
+
if (ctx?.componentIds?.length) {
|
|
1445
|
+
ctx.componentIds.forEach((componentId, index) => {
|
|
1446
|
+
let compLabel = componentId;
|
|
1447
|
+
if (index === 0 && resolvedComponentName) {
|
|
1448
|
+
compLabel = resolvedComponentName;
|
|
1449
|
+
} else if (editContext?.page) {
|
|
1450
|
+
try {
|
|
1451
|
+
const comp = getComponentById(componentId, editContext.page);
|
|
1452
|
+
compLabel = comp?.name || componentId;
|
|
1453
|
+
} catch {
|
|
1454
|
+
// Keep componentId as fallback
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
chips.push(
|
|
1458
|
+
<Chip
|
|
1459
|
+
key={`component-${index}`}
|
|
1460
|
+
icon={<Puzzle className="h-3 w-3 text-gray-500" strokeWidth={1} />}
|
|
1461
|
+
label={`Component: ${compLabel}`}
|
|
1462
|
+
onRemove={() => removeContextKey("componentIds", index)}
|
|
1463
|
+
/>,
|
|
1464
|
+
);
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// Render field chip
|
|
1469
|
+
if (ctx?.field) {
|
|
1470
|
+
const fieldLabel = `${ctx.field.name || resolvedFieldName || ctx.field.fieldId}`;
|
|
1471
|
+
chips.push(
|
|
1472
|
+
<Chip
|
|
1473
|
+
key="field"
|
|
1474
|
+
icon={<Type className="h-3 w-3 text-gray-500" strokeWidth={1} />}
|
|
1475
|
+
label={`Field: ${fieldLabel}`}
|
|
1476
|
+
onRemove={() => removeContextKey("field")}
|
|
1477
|
+
/>,
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
return (
|
|
1482
|
+
<div className="border-t border-gray-100 px-4 py-2">
|
|
1483
|
+
<div className="flex items-center justify-between text-xs text-gray-600">
|
|
1484
|
+
<div className="flex flex-wrap items-center gap-2">{chips}</div>
|
|
1485
|
+
<div className="ml-2 flex flex-wrap items-center gap-2">
|
|
1486
|
+
{canAddPage && (
|
|
1487
|
+
<Button size="sm" variant="outline" onClick={addPagesToContext}>
|
|
1488
|
+
<Plus className="mr-1 h-3 w-3" strokeWidth={1} /> Add current
|
|
1489
|
+
item
|
|
1490
|
+
</Button>
|
|
1491
|
+
)}
|
|
1492
|
+
{canAddSelection && (
|
|
1493
|
+
<Button
|
|
1494
|
+
size="sm"
|
|
1495
|
+
variant="outline"
|
|
1496
|
+
onClick={addSelectedComponentsToContext}
|
|
1497
|
+
>
|
|
1498
|
+
<Plus className="mr-1 h-3 w-3" strokeWidth={1} /> Add selected
|
|
1499
|
+
components
|
|
1500
|
+
</Button>
|
|
1501
|
+
)}
|
|
1502
|
+
</div>
|
|
1503
|
+
</div>
|
|
1504
|
+
</div>
|
|
1505
|
+
);
|
|
1506
|
+
};
|
|
1507
|
+
|
|
957
1508
|
return (
|
|
958
1509
|
<div className="flex h-full flex-col">
|
|
959
1510
|
{/* Messages */}
|
|
@@ -1052,6 +1603,9 @@ export function AgentTerminal({ agentStub }: { agentStub: Agent }) {
|
|
|
1052
1603
|
</div>
|
|
1053
1604
|
)}
|
|
1054
1605
|
|
|
1606
|
+
{/* Context Info Bar */}
|
|
1607
|
+
{renderContextInfoBar()}
|
|
1608
|
+
|
|
1055
1609
|
{/* Input */}
|
|
1056
1610
|
<div className="border-t border-gray-200 p-4">
|
|
1057
1611
|
<div className="flex items-stretch gap-2">
|