@alpaca-editor/core 1.0.4184 → 1.0.4185

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.
Files changed (43) hide show
  1. package/dist/agents-view/AgentCard.d.ts +12 -0
  2. package/dist/agents-view/AgentCard.js +30 -0
  3. package/dist/agents-view/AgentCard.js.map +1 -0
  4. package/dist/agents-view/AgentsView.d.ts +2 -2
  5. package/dist/agents-view/AgentsView.js +102 -85
  6. package/dist/agents-view/AgentsView.js.map +1 -1
  7. package/dist/agents-view/ProfileAgentsGroup.d.ts +17 -0
  8. package/dist/agents-view/ProfileAgentsGroup.js +13 -0
  9. package/dist/agents-view/ProfileAgentsGroup.js.map +1 -0
  10. package/dist/editor/ai/AgentProfilesOverview.js +1 -1
  11. package/dist/editor/ai/AgentProfilesOverview.js.map +1 -1
  12. package/dist/editor/commands/componentCommands.js +19 -1
  13. package/dist/editor/commands/componentCommands.js.map +1 -1
  14. package/dist/editor/page-editor-chrome/FrameMenu.js +7 -3
  15. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  16. package/dist/editor/page-viewer/PageViewerFrame.js +7 -28
  17. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  18. package/dist/editor/reviews/Comment.js +56 -4
  19. package/dist/editor/reviews/Comment.js.map +1 -1
  20. package/dist/editor/services/agentService.d.ts +19 -0
  21. package/dist/editor/services/agentService.js +39 -0
  22. package/dist/editor/services/agentService.js.map +1 -1
  23. package/dist/editor/services/serviceHelper.d.ts +1 -0
  24. package/dist/editor/services/serviceHelper.js +58 -4
  25. package/dist/editor/services/serviceHelper.js.map +1 -1
  26. package/dist/revision.d.ts +2 -2
  27. package/dist/revision.js +2 -2
  28. package/dist/styles.css +4 -6
  29. package/dist/tour/default-tour.js +24 -9
  30. package/dist/tour/default-tour.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/agents-view/AgentCard.tsx +162 -0
  33. package/src/agents-view/AgentsView.tsx +218 -253
  34. package/src/agents-view/ProfileAgentsGroup.tsx +123 -0
  35. package/src/editor/ai/AgentProfilesOverview.tsx +1 -2
  36. package/src/editor/commands/componentCommands.tsx +19 -1
  37. package/src/editor/page-editor-chrome/FrameMenu.tsx +9 -3
  38. package/src/editor/page-viewer/PageViewerFrame.tsx +7 -36
  39. package/src/editor/reviews/Comment.tsx +85 -4
  40. package/src/editor/services/agentService.ts +77 -0
  41. package/src/editor/services/serviceHelper.ts +92 -28
  42. package/src/revision.ts +2 -2
  43. package/src/tour/default-tour.tsx +63 -48
@@ -0,0 +1,123 @@
1
+ import React from "react";
2
+ import {
3
+ Agent,
4
+ ProfileAgentsGroup as ProfileGroup,
5
+ } from "../editor/services/agentService";
6
+ import { ChevronDown, ChevronUp } from "lucide-react";
7
+ import { AgentCard } from "./AgentCard";
8
+ import { SecretAgentIcon } from "../editor/ui/Icons";
9
+
10
+ interface ProfileAgentsGroupProps {
11
+ profileGroup: ProfileGroup;
12
+ isExpanded: boolean;
13
+ isLoadingMore: boolean;
14
+ hasMore: boolean;
15
+ onToggle: () => void;
16
+ onLoadMore: () => void;
17
+ onAgentClick: (agent: Agent) => void;
18
+ onResumeAgent: (agent: Agent) => void;
19
+ onCloseAgent: (agentId: string, agentName: string) => void;
20
+ onDeleteAgent: (
21
+ agentId: string,
22
+ agentName: string,
23
+ event: React.MouseEvent,
24
+ ) => void;
25
+ formatDateTime: (dateString: string) => string;
26
+ }
27
+
28
+ export function ProfileAgentsGroup({
29
+ profileGroup,
30
+ isExpanded,
31
+ isLoadingMore,
32
+ hasMore,
33
+ onToggle,
34
+ onLoadMore,
35
+ onAgentClick,
36
+ onResumeAgent,
37
+ onCloseAgent,
38
+ onDeleteAgent,
39
+ formatDateTime,
40
+ }: ProfileAgentsGroupProps) {
41
+ return (
42
+ <div className="rounded-lg border border-gray-200 bg-white shadow-sm">
43
+ {/* Group Header */}
44
+ <button
45
+ onClick={onToggle}
46
+ className="flex w-full items-center gap-3 bg-gray-50 p-3 text-left transition-colors hover:bg-gray-100"
47
+ >
48
+ {/* Profile Icon */}
49
+ <div className="flex-shrink-0">
50
+ {profileGroup.profileSvgIcon ? (
51
+ <div
52
+ className="flex h-6 w-6 items-center justify-center text-gray-500 [&>svg]:h-full [&>svg]:w-full"
53
+ dangerouslySetInnerHTML={{
54
+ __html: profileGroup.profileSvgIcon,
55
+ }}
56
+ />
57
+ ) : (
58
+ <SecretAgentIcon
59
+ size={24}
60
+ strokeWidth={1}
61
+ className="text-gray-500"
62
+ />
63
+ )}
64
+ </div>
65
+
66
+ {/* Profile Name */}
67
+ <div className="min-w-0 flex-1">
68
+ <h3 className="truncate font-medium text-gray-900">
69
+ {profileGroup.profileName}
70
+ </h3>
71
+ </div>
72
+
73
+ {/* Closed Agents Count Badge */}
74
+ <div className="flex-shrink-0 rounded-full bg-gray-200 px-3 py-1 text-xs font-medium text-gray-700">
75
+ {profileGroup.totalClosedCount} closed agent
76
+ {profileGroup.totalClosedCount !== 1 ? "s" : ""}
77
+ </div>
78
+
79
+ {/* Chevron Icon */}
80
+ <div className="flex-shrink-0 text-gray-500">
81
+ {isExpanded ? (
82
+ <ChevronUp className="size-5" strokeWidth={1} />
83
+ ) : (
84
+ <ChevronDown className="size-5" strokeWidth={1} />
85
+ )}
86
+ </div>
87
+ </button>
88
+
89
+ {/* Expandable Content */}
90
+ {isExpanded && (
91
+ <div className="space-y-2 p-3">
92
+ {profileGroup.agents.map((agent) => (
93
+ <AgentCard
94
+ key={agent.id}
95
+ agent={agent}
96
+ onClick={onAgentClick}
97
+ onResume={onResumeAgent}
98
+ onClose={onCloseAgent}
99
+ onDelete={onDeleteAgent}
100
+ formatDateTime={formatDateTime}
101
+ />
102
+ ))}
103
+
104
+ {/* Load More Button */}
105
+ {hasMore && (
106
+ <div className="flex justify-center pt-2">
107
+ <button
108
+ onClick={(e) => {
109
+ e.stopPropagation();
110
+ onLoadMore();
111
+ }}
112
+ disabled={isLoadingMore}
113
+ className="rounded-lg border border-gray-300 bg-white px-6 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
114
+ >
115
+ {isLoadingMore ? "Loading..." : "Load More"}
116
+ </button>
117
+ </div>
118
+ )}
119
+ </div>
120
+ )}
121
+ </div>
122
+ );
123
+ }
@@ -42,7 +42,7 @@ export const AgentProfilesOverview: React.FC<AgentProfilesOverviewProps> = ({
42
42
  <button
43
43
  key={profile.id}
44
44
  onClick={() => onSelectProfile(profile.id)}
45
- className="group flex flex-col gap-3 rounded-lg border border-gray-200 bg-white p-4 text-left transition-all hover:border-blue-300 hover:shadow-md focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 focus:outline-none"
45
+ className="group flex flex-col gap-3 rounded-lg border border-gray-200 bg-white p-4 text-left transition-all hover:border-blue-300 hover:shadow-md"
46
46
  data-testid="agent-profile-card"
47
47
  data-profile-id={profile.id}
48
48
  >
@@ -78,4 +78,3 @@ export const AgentProfilesOverview: React.FC<AgentProfilesOverviewProps> = ({
78
78
  </div>
79
79
  );
80
80
  };
81
-
@@ -95,6 +95,9 @@ function getInsertCommand(
95
95
  if (!item) return null;
96
96
  if (!item.placeholders || item.placeholders.length === 0) return null;
97
97
  if (editContext.mode === "suggestions") return null;
98
+ // Don't show insert for layout components when showLayoutComponents is disabled
99
+ if (item.layoutId && !editContext.showLayoutComponents) return null;
100
+
98
101
  return {
99
102
  id: "insert",
100
103
  icon: <Plus size={defaultIconSize} />,
@@ -114,6 +117,10 @@ function getDuplicateCommand(
114
117
  if (components.length !== 1) return null;
115
118
  if (editContext.mode === "suggestions") return null;
116
119
 
120
+ const component = components[0];
121
+ // Don't show duplicate for layout components when showLayoutComponents is disabled
122
+ if (component.layoutId && !editContext.showLayoutComponents) return null;
123
+
117
124
  return {
118
125
  id: "duplicate",
119
126
  icon: <Copy size={defaultIconSize} />,
@@ -205,6 +212,9 @@ async function getDesignCommand(
205
212
  if (components.length !== 1 || isPlaceholder(components[0])) return null;
206
213
  const component = components[0];
207
214
  if (!component || !component.datasourceItem) return null;
215
+ // Don't show design for layout components when showLayoutComponents is disabled
216
+ if (component.layoutId && !editContext.showLayoutComponents) return null;
217
+
208
218
  const item = await editContext.itemsRepository.getItem(
209
219
  component.datasourceItem,
210
220
  );
@@ -243,6 +253,9 @@ async function getLinkToMasterCommand(
243
253
  ): Promise<ComponentCommand | null> {
244
254
  if (!components.length) return null;
245
255
  if (editContext.page?.item?.masterLanguages?.length === 0) return null;
256
+ // Don't show for layout components when showLayoutComponents is disabled
257
+ if (components.some((c) => c.layoutId && !editContext.showLayoutComponents))
258
+ return null;
246
259
 
247
260
  return getCheckboxCommand(
248
261
  components,
@@ -304,6 +317,9 @@ async function getInheritChildrenFromMasterCommand(
304
317
  if (editContext.page?.item?.masterLanguages?.length === 0) return null;
305
318
  if (components.find((x) => !x.placeholders || x.placeholders.length === 0))
306
319
  return null;
320
+ // Don't show for layout components when showLayoutComponents is disabled
321
+ if (components.some((c) => c.layoutId && !editContext.showLayoutComponents))
322
+ return null;
307
323
 
308
324
  return getCheckboxCommand(
309
325
  components,
@@ -345,7 +361,9 @@ function getDeleteCommand(
345
361
  ((!isPlaceholder(c) && !c.layoutId) ||
346
362
  c.layoutId === editContext.page?.item.id) &&
347
363
  !isLocked(c, editContext) &&
348
- editContext.page?.item.canWriteItem,
364
+ editContext.page?.item.canWriteItem &&
365
+ // When showLayoutComponents is disabled, layout components should not be editable
366
+ (editContext.showLayoutComponents || !c.layoutId),
349
367
  );
350
368
 
351
369
  if (applicableComponents.length === 0) return null;
@@ -186,7 +186,10 @@ export function FrameMenu({
186
186
  loadCommands();
187
187
  }, [component]);
188
188
 
189
- const isDraggable = true; // component.canBeMoved && !component.layoutId;
189
+ // Determine if draggable - layout components should not be draggable when showLayoutComponents is disabled
190
+ const isLayoutReadonlyCheck =
191
+ component.layoutId && !editContext.showLayoutComponents;
192
+ const isDraggable = component.canBeMoved && !isLayoutReadonlyCheck;
190
193
 
191
194
  const commandButtons = commands
192
195
  .filter(
@@ -257,11 +260,14 @@ export function FrameMenu({
257
260
  }
258
261
 
259
262
  const isShared = component.isShared;
260
- const isReadonly = editContext.mode === "preview" || compareView;
261
263
  const isLayout = component.layoutId;
264
+ // Layout components should be read-only when showLayoutComponents is disabled
265
+ const isLayoutReadonly = isLayout && !editContext.showLayoutComponents;
266
+ const isReadonly =
267
+ editContext.mode === "preview" || compareView || isLayoutReadonly;
262
268
 
263
269
  function getColor() {
264
- if (isReadonly) return "readonly";
270
+ if (isReadonly && !isLayoutReadonly) return "readonly";
265
271
  if (editContext?.mode === "suggestions") return "suggestions";
266
272
  if (isLayout) return "layout";
267
273
  if (isShared) return "shared";
@@ -487,18 +487,8 @@ export function PageViewerFrame({
487
487
 
488
488
  let componentId = findNearestEditableComponentId(target as HTMLElement);
489
489
 
490
- // If layout components are hidden, block selection on layout components
491
- if (
492
- componentId &&
493
- editContextRef.current &&
494
- editContextRef.current.page &&
495
- editContextRef.current.showLayoutComponents === false
496
- ) {
497
- const comp = getComponentById(componentId, editContextRef.current.page);
498
- if (comp?.layoutId) {
499
- componentId = undefined;
500
- }
501
- }
490
+ // Layout components can still be selected even when showLayoutComponents is false
491
+ // They will be displayed in read-only mode
502
492
  const currentOverlayName = editContextRef.current?.currentOverlay;
503
493
  const isGeneratorOverlay = !!(
504
494
  currentOverlayName &&
@@ -530,12 +520,7 @@ export function PageViewerFrame({
530
520
  for (const el of all) {
531
521
  const id = el.getAttribute("data-component-id");
532
522
  if (!id) continue;
533
- // Skip layout components when hidden
534
- if (
535
- editContextRef.current?.showLayoutComponents === false &&
536
- getComponentById(id, page)?.layoutId
537
- )
538
- continue;
523
+ // Layout components are now selectable even when showLayoutComponents is false
539
524
  orderedIds.push(id);
540
525
  }
541
526
  }
@@ -688,17 +673,8 @@ export function PageViewerFrame({
688
673
 
689
674
  let componentId = findNearestEditableComponentId(target as HTMLElement);
690
675
 
691
- if (
692
- componentId &&
693
- editContextRef.current &&
694
- editContextRef.current.page &&
695
- editContextRef.current.showLayoutComponents === false
696
- ) {
697
- const comp = getComponentById(componentId, editContextRef.current.page);
698
- if (comp?.layoutId) {
699
- componentId = undefined;
700
- }
701
- }
676
+ // Layout components can now be right-clicked even when showLayoutComponents is false
677
+ // Context menu will show limited/read-only actions
702
678
 
703
679
  if (componentId) {
704
680
  // Only change selection if right-clicking on a component that's not in the current selection
@@ -721,13 +697,8 @@ export function PageViewerFrame({
721
697
  .map((id) => getComponentById(id, pageViewContextRef.current!.page!))
722
698
  .filter((x) => x) as Component[];
723
699
 
724
- // If layout components are hidden, do not show context menu for them
725
- if (
726
- editContextRef.current?.showLayoutComponents === false &&
727
- selectedComponents.some((c) => c.layoutId)
728
- ) {
729
- return;
730
- }
700
+ // Context menu will now show for layout components even when showLayoutComponents is false
701
+ // Commands will be appropriately filtered/disabled
731
702
 
732
703
  const iframeRect = iframe.getBoundingClientRect();
733
704
  const adjustedEvent = new MouseEvent("contextmenu", {
@@ -12,6 +12,7 @@ import { openAiAgentForComment } from "./commentAi";
12
12
  import { useDebouncedCallback } from "use-debounce";
13
13
  import { CommentView } from "./CommentView";
14
14
  import { CommentEditor } from "./CommentEditor";
15
+ import { toast } from "sonner";
15
16
 
16
17
  export function Comment({
17
18
  comment,
@@ -71,8 +72,28 @@ export function Comment({
71
72
  )?.rawValue;
72
73
  }
73
74
 
74
- await createOrUpdateComment(comment);
75
+ const result = await createOrUpdateComment(comment);
76
+
77
+ if (result.type === "error" || result.type === "unauthorized") {
78
+ console.error("Failed to save comment:", result);
79
+ if (result.rawDetails) {
80
+ console.error(
81
+ "Full error details (including stack trace):\n",
82
+ result.rawDetails,
83
+ );
84
+ }
85
+ toast.error(
86
+ result.details || result.summary || "Failed to save comment",
87
+ );
88
+ return;
89
+ }
90
+
75
91
  setIsEditing(false);
92
+ } catch (error) {
93
+ console.error("Failed to save comment:", error);
94
+ toast.error(
95
+ error instanceof Error ? error.message : "Failed to save comment",
96
+ );
76
97
  } finally {
77
98
  setIsSaving(false);
78
99
  }
@@ -91,15 +112,75 @@ export function Comment({
91
112
  };
92
113
 
93
114
  const handleDelete = async () => {
94
- await deleteComment(comment);
115
+ try {
116
+ const result = await deleteComment(comment);
117
+
118
+ if (result.type === "error" || result.type === "unauthorized") {
119
+ console.error("Failed to delete comment:", result);
120
+ if (result.rawDetails) {
121
+ console.error(
122
+ "Full error details (including stack trace):\n",
123
+ result.rawDetails,
124
+ );
125
+ }
126
+ toast.error(
127
+ result.details || result.summary || "Failed to delete comment",
128
+ );
129
+ }
130
+ } catch (error) {
131
+ console.error("Failed to delete comment:", error);
132
+ toast.error(
133
+ error instanceof Error ? error.message : "Failed to delete comment",
134
+ );
135
+ }
95
136
  };
96
137
 
97
138
  const handleResolve = async () => {
98
- await resolveComment(comment);
139
+ try {
140
+ const result = await resolveComment(comment);
141
+
142
+ if (result.type === "error" || result.type === "unauthorized") {
143
+ console.error("Failed to resolve comment:", result);
144
+ if (result.rawDetails) {
145
+ console.error(
146
+ "Full error details (including stack trace):\n",
147
+ result.rawDetails,
148
+ );
149
+ }
150
+ toast.error(
151
+ result.details || result.summary || "Failed to resolve comment",
152
+ );
153
+ }
154
+ } catch (error) {
155
+ console.error("Failed to resolve comment:", error);
156
+ toast.error(
157
+ error instanceof Error ? error.message : "Failed to resolve comment",
158
+ );
159
+ }
99
160
  };
100
161
 
101
162
  const handleUnresolve = async () => {
102
- await unresolveComment(comment);
163
+ try {
164
+ const result = await unresolveComment(comment);
165
+
166
+ if (result.type === "error" || result.type === "unauthorized") {
167
+ console.error("Failed to unresolve comment:", result);
168
+ if (result.rawDetails) {
169
+ console.error(
170
+ "Full error details (including stack trace):\n",
171
+ result.rawDetails,
172
+ );
173
+ }
174
+ toast.error(
175
+ result.details || result.summary || "Failed to unresolve comment",
176
+ );
177
+ }
178
+ } catch (error) {
179
+ console.error("Failed to unresolve comment:", error);
180
+ toast.error(
181
+ error instanceof Error ? error.message : "Failed to unresolve comment",
182
+ );
183
+ }
103
184
  };
104
185
 
105
186
  const handleAiAction = async () =>
@@ -517,6 +517,19 @@ export interface GetAgentsResponse {
517
517
  hasMore: boolean;
518
518
  }
519
519
 
520
+ export interface ProfileAgentsGroup {
521
+ profileId: string | null;
522
+ profileName: string;
523
+ profileSvgIcon?: string;
524
+ totalClosedCount: number;
525
+ agents: Agent[];
526
+ }
527
+
528
+ export interface GetAgentsGroupedResponse {
529
+ activeAgents: Agent[];
530
+ closedAgentsByProfile: ProfileAgentsGroup[];
531
+ }
532
+
520
533
  /**
521
534
  * Gets all agents for the current user with pagination and search support
522
535
  */
@@ -550,6 +563,70 @@ export async function getActiveAgents(
550
563
  return result.data || { agents: [], totalCount: 0, hasMore: false };
551
564
  }
552
565
 
566
+ /**
567
+ * Gets all active agents and closed agents grouped by profile for the current user
568
+ */
569
+ export async function getAgentsGrouped(
570
+ searchTerm?: string,
571
+ includeShared: boolean = true,
572
+ includeOwned: boolean = true,
573
+ ): Promise<GetAgentsGroupedResponse> {
574
+ const queryParams = new URLSearchParams();
575
+
576
+ if (searchTerm) queryParams.append("searchTerm", searchTerm);
577
+ queryParams.append("includeShared", includeShared.toString());
578
+ queryParams.append("includeOwned", includeOwned.toString());
579
+
580
+ const queryString = queryParams.toString();
581
+ const url =
582
+ AGENT_BASE_URL +
583
+ "/getAgentsGroupedByProfile" +
584
+ (queryString ? `?${queryString}` : "");
585
+
586
+ const result = await get<GetAgentsGroupedResponse>(url);
587
+
588
+ if (result.type !== "success") {
589
+ throw new Error(
590
+ `Failed to get grouped agents: ${result.summary || "Unknown error"} ${result.details || ""}`,
591
+ );
592
+ }
593
+
594
+ return result.data || { activeAgents: [], closedAgentsByProfile: [] };
595
+ }
596
+
597
+ /**
598
+ * Gets more closed agents for a specific profile
599
+ */
600
+ export async function getClosedAgentsByProfile(
601
+ profileId: string,
602
+ skip: number,
603
+ limit: number,
604
+ searchTerm?: string,
605
+ ): Promise<GetAgentsResponse> {
606
+ const queryParams = new URLSearchParams();
607
+
608
+ queryParams.append("profileId", profileId);
609
+ queryParams.append("skip", skip.toString());
610
+ queryParams.append("limit", limit.toString());
611
+ if (searchTerm) queryParams.append("searchTerm", searchTerm);
612
+
613
+ const queryString = queryParams.toString();
614
+ const url =
615
+ AGENT_BASE_URL +
616
+ "/getClosedAgentsByProfile" +
617
+ (queryString ? `?${queryString}` : "");
618
+
619
+ const result = await get<GetAgentsResponse>(url);
620
+
621
+ if (result.type !== "success") {
622
+ throw new Error(
623
+ `Failed to get closed agents by profile: ${result.summary || "Unknown error"} ${result.details || ""}`,
624
+ );
625
+ }
626
+
627
+ return result.data || { agents: [], totalCount: 0, hasMore: false };
628
+ }
629
+
553
630
  /**
554
631
  * Gets all closed agents for the current user
555
632
  * @deprecated Use getActiveAgents() instead - the backend now returns all agents (including closed) by default
@@ -2,47 +2,102 @@ export type ExecutionResult<T> = {
2
2
  type: "success" | "error" | "unauthorized";
3
3
  summary?: string;
4
4
  details?: string;
5
+ rawDetails?: string; // Original unprocessed error details (for logging/debugging)
5
6
  response: Response;
6
7
  data?: T;
7
8
  };
8
9
 
10
+ /**
11
+ * Extracts a user-friendly error message from the response.
12
+ * Handles HTML error pages by extracting the title or exception details.
13
+ * Also extracts source file information if available.
14
+ */
15
+ function extractErrorMessage(message: string): string {
16
+ // Check if it's an HTML error page
17
+ if (message.includes("<!DOCTYPE html>") || message.includes("<html>")) {
18
+ let errorMessage = "";
19
+
20
+ // Try to extract the title (which usually contains the error message)
21
+ const titleMatch = message.match(/<title>(.*?)<\/title>/i);
22
+ if (titleMatch && titleMatch[1]) {
23
+ // Remove "Server Error" prefix if present
24
+ errorMessage = titleMatch[1].replace(
25
+ /^Server Error in '.*?' Application\.\s*/i,
26
+ "",
27
+ );
28
+ }
29
+
30
+ // If no title, try h2 tag (exception message)
31
+ if (!errorMessage) {
32
+ const h2Match = message.match(/<h2>\s*<i>(.*?)<\/i>\s*<\/h2>/i);
33
+ if (h2Match && h2Match[1]) {
34
+ errorMessage = h2Match[1];
35
+ }
36
+ }
37
+
38
+ // If no h2, try Exception Details
39
+ if (!errorMessage) {
40
+ const exceptionMatch = message.match(
41
+ /<b>\s*Exception Details:\s*<\/b>(.*?)(?:<br>|<\/)/i,
42
+ );
43
+ if (exceptionMatch && exceptionMatch[1]) {
44
+ errorMessage = exceptionMatch[1].replace(/<[^>]*>/g, "").trim();
45
+ }
46
+ }
47
+
48
+ // Try to extract source file and line information
49
+ const sourceMatch = message.match(
50
+ /<b>\s*Source File:\s*<\/b>\s*([^<]+)<b>\s*&nbsp;&nbsp;\s*Line:\s*<\/b>\s*(\d+)/i,
51
+ );
52
+ if (sourceMatch && sourceMatch[1] && sourceMatch[2]) {
53
+ const fileName = sourceMatch[1].trim().split("\\").pop();
54
+ errorMessage += ` (${fileName}:${sourceMatch[2]})`;
55
+ }
56
+
57
+ if (errorMessage) {
58
+ return errorMessage;
59
+ }
60
+
61
+ // If we can't parse it, just strip all HTML tags
62
+ const cleaned = message.replace(/<[^>]*>/g, "").trim();
63
+ // Limit length for toast display
64
+ return cleaned.length > 200 ? cleaned.substring(0, 200) + "..." : cleaned;
65
+ }
66
+
67
+ return message;
68
+ }
69
+
9
70
  export async function post<T>(
10
71
  url: string,
11
72
  body: any,
12
- session?: string
73
+ session?: string,
13
74
  ): Promise<ExecutionResult<T>> {
14
75
  if (session) url += "?sessionId=" + session;
15
76
 
16
- const response = await fetch(
17
- url,
18
- {
19
- method: "POST",
20
- body: JSON.stringify(body),
21
- credentials: "include",
22
- headers: {
23
- "Content-Type": "application/json",
24
- },
25
- }
26
- );
77
+ const response = await fetch(url, {
78
+ method: "POST",
79
+ body: JSON.stringify(body),
80
+ credentials: "include",
81
+ headers: {
82
+ "Content-Type": "application/json",
83
+ },
84
+ });
27
85
 
28
86
  return handleResponse<T>(response);
29
87
  }
30
88
 
31
89
  export async function get<T>(
32
90
  url: string,
33
- session?: string
91
+ session?: string,
34
92
  ): Promise<ExecutionResult<T>> {
35
93
  if (session) url += "?sessionId=" + session;
36
- const response = await fetch(
37
- url,
38
- {
39
- method: "GET",
40
- credentials: "include",
41
- headers: {
42
- "Content-Type": "application/json",
43
- },
44
- }
45
- );
94
+ const response = await fetch(url, {
95
+ method: "GET",
96
+ credentials: "include",
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ },
100
+ });
46
101
 
47
102
  return handleResponse<T>(response);
48
103
  }
@@ -54,17 +109,20 @@ async function handleResponse<T>(
54
109
  const data = await response.json();
55
110
  return {
56
111
  type: "error",
57
- summary: data.summary,
58
- details: data.details,
112
+ summary: data.summary ? extractErrorMessage(data.summary) : data.summary,
113
+ details: data.details ? extractErrorMessage(data.details) : data.details,
114
+ rawDetails: data.details || data.summary, // Preserve original for logging
59
115
  response,
60
116
  };
61
117
  }
62
118
 
63
119
  if (response.status === 500 || response.redirected) {
120
+ const rawText = await response.text();
64
121
  return {
65
122
  type: "error",
66
123
  summary: "Error",
67
- details: await response.text(),
124
+ details: extractErrorMessage(rawText),
125
+ rawDetails: rawText, // Preserve full HTML error with stack trace
68
126
  response,
69
127
  };
70
128
  }
@@ -77,9 +135,15 @@ async function handleResponse<T>(
77
135
  response,
78
136
  };
79
137
  }
80
-
138
+
81
139
  if (response.status !== 200) {
82
- return { type: "error", response, details: await response.text() };
140
+ const rawText = await response.text();
141
+ return {
142
+ type: "error",
143
+ response,
144
+ details: extractErrorMessage(rawText),
145
+ rawDetails: rawText, // Preserve original for logging
146
+ };
83
147
  }
84
148
 
85
149
  if (!response.headers.get("content-type")?.includes("application/json")) {
package/src/revision.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = "1.0.4184";
2
- export const buildDate = "2025-10-23 01:46:04";
1
+ export const version = "1.0.4185";
2
+ export const buildDate = "2025-10-23 17:45:06";