@autobe/ui 0.22.1 → 0.23.1

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 (128) hide show
  1. package/lib/components/AutoBeChatMain.d.ts +6 -4
  2. package/lib/components/AutoBeChatMain.js +204 -56
  3. package/lib/components/AutoBeChatMain.js.map +1 -1
  4. package/lib/components/AutoBeChatSidebar.d.ts +36 -0
  5. package/lib/components/AutoBeChatSidebar.js +227 -0
  6. package/lib/components/AutoBeChatSidebar.js.map +1 -0
  7. package/lib/components/AutoBeConfigButton.d.ts +15 -0
  8. package/lib/components/AutoBeConfigButton.js +33 -0
  9. package/lib/components/AutoBeConfigButton.js.map +1 -0
  10. package/lib/components/AutoBeConfigModal.d.ts +59 -0
  11. package/lib/components/AutoBeConfigModal.js +294 -0
  12. package/lib/components/AutoBeConfigModal.js.map +1 -0
  13. package/lib/components/AutoBeStatusButton.d.ts +12 -0
  14. package/lib/components/AutoBeStatusButton.js +29 -0
  15. package/lib/components/AutoBeStatusButton.js.map +1 -0
  16. package/lib/components/AutoBeStatusModal.js +35 -15
  17. package/lib/components/AutoBeStatusModal.js.map +1 -1
  18. package/lib/components/common/ActionButton.d.ts +16 -0
  19. package/lib/components/common/ActionButton.js +115 -0
  20. package/lib/components/common/ActionButton.js.map +1 -0
  21. package/lib/components/common/ActionButtonGroup.d.ts +13 -0
  22. package/lib/components/common/ActionButtonGroup.js +37 -0
  23. package/lib/components/common/ActionButtonGroup.js.map +1 -0
  24. package/lib/components/common/AutoBeConfigInput.d.ts +24 -0
  25. package/lib/components/common/AutoBeConfigInput.js +90 -0
  26. package/lib/components/common/AutoBeConfigInput.js.map +1 -0
  27. package/lib/components/common/CompactSessionIndicator.d.ts +16 -0
  28. package/lib/components/common/CompactSessionIndicator.js +46 -0
  29. package/lib/components/common/CompactSessionIndicator.js.map +1 -0
  30. package/lib/components/common/CompactSessionList.d.ts +22 -0
  31. package/lib/components/common/CompactSessionList.js +40 -0
  32. package/lib/components/common/CompactSessionList.js.map +1 -0
  33. package/lib/components/common/index.d.ts +6 -0
  34. package/lib/components/common/index.js +6 -0
  35. package/lib/components/common/index.js.map +1 -1
  36. package/lib/components/events/AutoBeEventGroupMovie.d.ts +6 -0
  37. package/lib/components/events/AutoBeEventGroupMovie.js +11 -0
  38. package/lib/components/events/AutoBeEventGroupMovie.js.map +1 -0
  39. package/lib/components/events/AutoBeEventMovie.js +5 -0
  40. package/lib/components/events/AutoBeEventMovie.js.map +1 -1
  41. package/lib/components/events/AutoBeValidateEventMovie.js +1 -3
  42. package/lib/components/events/AutoBeValidateEventMovie.js.map +1 -1
  43. package/lib/components/events/common/CollapsibleEventGroup.d.ts +1 -1
  44. package/lib/components/events/groups/ValidateEventGroup.d.ts +1 -1
  45. package/lib/components/events/utils/eventGrouper.js.map +1 -1
  46. package/lib/components/index.d.ts +6 -0
  47. package/lib/components/index.js +7 -0
  48. package/lib/components/index.js.map +1 -1
  49. package/lib/components/upload/AutoBeChatUploadBox.js +75 -33
  50. package/lib/components/upload/AutoBeChatUploadBox.js.map +1 -1
  51. package/lib/context/AutoBeAgentContext.d.ts +22 -11
  52. package/lib/context/AutoBeAgentContext.js +127 -11
  53. package/lib/context/AutoBeAgentContext.js.map +1 -1
  54. package/lib/context/AutoBeAgentSessionList.d.ts +12 -0
  55. package/lib/context/AutoBeAgentSessionList.js +37 -0
  56. package/lib/context/AutoBeAgentSessionList.js.map +1 -0
  57. package/lib/context/SearchParamsContext.d.ts +10 -0
  58. package/lib/context/SearchParamsContext.js +29 -0
  59. package/lib/context/SearchParamsContext.js.map +1 -0
  60. package/lib/index.d.ts +4 -0
  61. package/lib/index.js +4 -0
  62. package/lib/index.js.map +1 -1
  63. package/lib/structure/AutoBeListener.d.ts +6 -0
  64. package/lib/structure/AutoBeListener.js +21 -4
  65. package/lib/structure/AutoBeListener.js.map +1 -1
  66. package/lib/structure/IAutoBeAgentSessionStorageStrategy.d.ts +35 -0
  67. package/lib/structure/IAutoBeAgentSessionStorageStrategy.js +30 -0
  68. package/lib/structure/IAutoBeAgentSessionStorageStrategy.js.map +1 -0
  69. package/lib/structure/index.d.ts +1 -0
  70. package/lib/structure/index.js +1 -0
  71. package/lib/structure/index.js.map +1 -1
  72. package/lib/types/config.d.ts +26 -0
  73. package/lib/types/config.js +14 -0
  74. package/lib/types/config.js.map +1 -0
  75. package/lib/types/index.d.ts +1 -0
  76. package/lib/types/index.js +18 -0
  77. package/lib/types/index.js.map +1 -0
  78. package/lib/utils/__tests__/crypto.test.d.ts +1 -0
  79. package/lib/utils/__tests__/crypto.test.js +222 -0
  80. package/lib/utils/__tests__/crypto.test.js.map +1 -0
  81. package/lib/utils/__tests__/storage.test.d.ts +1 -0
  82. package/lib/utils/__tests__/storage.test.js +174 -0
  83. package/lib/utils/__tests__/storage.test.js.map +1 -0
  84. package/lib/utils/crypto.d.ts +18 -0
  85. package/lib/utils/crypto.js +84 -0
  86. package/lib/utils/crypto.js.map +1 -0
  87. package/lib/utils/index.d.ts +2 -0
  88. package/lib/utils/index.js +2 -0
  89. package/lib/utils/index.js.map +1 -1
  90. package/lib/utils/storage.d.ts +29 -0
  91. package/lib/utils/storage.js +93 -0
  92. package/lib/utils/storage.js.map +1 -0
  93. package/package.json +11 -3
  94. package/src/components/AutoBeChatMain.tsx +329 -131
  95. package/src/components/AutoBeChatSidebar.tsx +414 -0
  96. package/src/components/AutoBeConfigButton.tsx +83 -0
  97. package/src/components/AutoBeConfigModal.tsx +444 -0
  98. package/src/components/AutoBeStatusButton.tsx +75 -0
  99. package/src/components/AutoBeStatusModal.tsx +55 -54
  100. package/src/components/common/ActionButton.tsx +205 -0
  101. package/src/components/common/ActionButtonGroup.tsx +80 -0
  102. package/src/components/common/AutoBeConfigInput.tsx +185 -0
  103. package/src/components/common/CompactSessionIndicator.tsx +73 -0
  104. package/src/components/common/CompactSessionList.tsx +82 -0
  105. package/src/components/common/index.ts +6 -0
  106. package/src/components/events/AutoBeEventGroupMovie.tsx +18 -0
  107. package/src/components/events/AutoBeEventMovie.tsx +5 -0
  108. package/src/components/events/AutoBeValidateEventMovie.tsx +7 -9
  109. package/src/components/events/common/CollapsibleEventGroup.tsx +1 -1
  110. package/src/components/events/groups/ValidateEventGroup.tsx +1 -1
  111. package/src/components/events/utils/eventGrouper.tsx +2 -1
  112. package/src/components/index.ts +6 -0
  113. package/src/components/upload/AutoBeChatUploadBox.tsx +94 -44
  114. package/src/context/AutoBeAgentContext.tsx +201 -22
  115. package/src/context/AutoBeAgentSessionList.tsx +58 -0
  116. package/src/context/SearchParamsContext.tsx +49 -0
  117. package/src/index.ts +4 -0
  118. package/src/structure/AutoBeListener.ts +32 -6
  119. package/src/structure/IAutoBeAgentSessionStorageStrategy.ts +87 -0
  120. package/src/structure/index.ts +1 -0
  121. package/src/types/config.ts +44 -0
  122. package/src/types/index.ts +1 -0
  123. package/src/utils/__tests__/crypto.test.ts +286 -0
  124. package/src/utils/__tests__/storage.test.ts +229 -0
  125. package/src/utils/crypto.ts +95 -0
  126. package/src/utils/index.ts +2 -0
  127. package/src/utils/storage.ts +96 -0
  128. package/vitest.config.ts +15 -0
@@ -78,13 +78,11 @@ function getState(event: IAutoBeValidateEventMovieProps["event"]): IState {
78
78
  <br />
79
79
  {event.result.errors.length > 0 && (
80
80
  <>
81
- {event.result.errors
82
- .slice(0, 3)
83
- .map((error: any, idx: number) => (
84
- <div key={idx} style={{ marginTop: "0.25rem" }}>
85
- • {error.message}
86
- </div>
87
- ))}
81
+ {event.result.errors.slice(0, 3).map((error, idx) => (
82
+ <div key={idx} style={{ marginTop: "0.25rem" }}>
83
+ • {error.message}
84
+ </div>
85
+ ))}
88
86
  </>
89
87
  )}
90
88
  <br />
@@ -119,7 +117,7 @@ function getState(event: IAutoBeValidateEventMovieProps["event"]): IState {
119
117
  {event.result.type === "failure" &&
120
118
  event.result.diagnostics
121
119
  .slice(0, 3)
122
- .map((diagnostic: any, idx: number) => (
120
+ .map((diagnostic, idx) => (
123
121
  <div key={idx} style={{ marginTop: "0.25rem" }}>
124
122
  • {diagnostic.messageText}
125
123
  </div>
@@ -194,7 +192,7 @@ function getState(event: IAutoBeValidateEventMovieProps["event"]): IState {
194
192
  {event.result.type === "failure" &&
195
193
  event.result.diagnostics
196
194
  .slice(0, 2)
197
- .map((diagnostic: any, idx: number) => (
195
+ .map((diagnostic, idx) => (
198
196
  <div key={idx} style={{ marginTop: "0.25rem" }}>
199
197
  • {diagnostic.messageText}
200
198
  </div>
@@ -4,7 +4,7 @@ import { EventCard } from "./EventCard";
4
4
  import { EventContent } from "./EventContent";
5
5
  import { EventIcon, EventIconType } from "./EventIcon";
6
6
 
7
- export interface ICollapsibleEventGroupProps<T = any> {
7
+ export interface ICollapsibleEventGroupProps<T = unknown> {
8
8
  /** Array of events of the same type */
9
9
  events: T[];
10
10
  /** Title for the group */
@@ -11,7 +11,7 @@ import {
11
11
  import { AutoBeValidateEventMovie } from "../AutoBeValidateEventMovie";
12
12
  import { CollapsibleEventGroup } from "../common/CollapsibleEventGroup";
13
13
 
14
- type ValidateEvent =
14
+ export type ValidateEvent =
15
15
  | AutoBePrismaInsufficientEvent
16
16
  | AutoBePrismaValidateEvent
17
17
  | AutoBeInterfaceOperationsReviewEvent
@@ -2,6 +2,7 @@ import { AutoBeEvent } from "@autobe/interface";
2
2
  import { ReactNode } from "react";
3
3
 
4
4
  import { ValidateEventGroup } from "../groups";
5
+ import { ValidateEvent } from "../groups/ValidateEventGroup";
5
6
 
6
7
  /** Configuration for event grouping */
7
8
  export interface IEventGrouperConfig {
@@ -71,7 +72,7 @@ export function groupEvents(
71
72
  /** Groups events by their category */
72
73
  function groupEventsByCategory(events: AutoBeEvent[]) {
73
74
  const grouped = {
74
- validate: [] as any[],
75
+ validate: [] as ValidateEvent[],
75
76
  other: [] as AutoBeEvent[],
76
77
  };
77
78
 
@@ -1,5 +1,11 @@
1
+ export * from "./AutoBeConfigButton";
2
+ export * from "./AutoBeConfigModal";
3
+ export { createAutoBeConfigFields } from "./AutoBeConfigModal";
4
+ export type { AutoBeConfigKey } from "./AutoBeConfigModal";
5
+ export * from "./AutoBeStatusButton";
1
6
  export * from "./AutoBeStatusModal";
2
7
  export * from "./AutoBeChatMain";
8
+ export * from "./AutoBeChatSidebar";
3
9
  export * from "./AutoBeUserMessageMovie";
4
10
  export * from "./AutoBeAssistantMessageMovie";
5
11
  export * from "./common";
@@ -11,6 +11,7 @@ import {
11
11
  AutoBeFileUploadBox,
12
12
  AutoBeVoiceRecoderButton,
13
13
  } from ".";
14
+ import { useAutoBeAgent } from "../../context/AutoBeAgentContext";
14
15
  import { useMediaQuery } from "../../hooks/useMediaQuery";
15
16
  import { AutoBeFileUploader } from "../../utils";
16
17
 
@@ -29,7 +30,8 @@ export interface IAutoBeChatUploadConfig {
29
30
  }
30
31
 
31
32
  export const AutoBeChatUploadBox = (props: AutoBeChatUploadBox.IProps) => {
32
- const inputRef = useRef<HTMLTextAreaElement>(null);
33
+ const { listener } = useAutoBeAgent();
34
+ const inputRef = useRef<HTMLDivElement>(null);
33
35
  const fileInputRef = useRef<HTMLInputElement>(null);
34
36
 
35
37
  const [dragging, setDragging] = useState(false);
@@ -39,11 +41,29 @@ export const AutoBeChatUploadBox = (props: AutoBeChatUploadBox.IProps) => {
39
41
  const [extensionError, setExtensionError] = useState<ReactNode | null>(null);
40
42
 
41
43
  const [emptyText, setEmptyText] = useState(false);
44
+ const [isComposing, setIsComposing] = useState(false);
42
45
 
43
46
  const removeFile = (index: number) => {
44
47
  setBuckets(buckets.filter((_, i) => i !== index));
45
48
  };
46
49
 
50
+ useEffect(() => {
51
+ async function trackEnable(value: boolean) {
52
+ setEnabled(value);
53
+ }
54
+ listener?.onEnable(trackEnable);
55
+ return () => {
56
+ listener?.offEnable(trackEnable);
57
+ };
58
+ }, [listener]);
59
+
60
+ // Sync text state when cleared programmatically (like after sending message)
61
+ useEffect(() => {
62
+ if (inputRef.current && text === "" && inputRef.current.innerText !== "") {
63
+ inputRef.current.innerText = "";
64
+ }
65
+ }, [text]);
66
+
47
67
  const conversate = async () => {
48
68
  if (enabled === false) return;
49
69
 
@@ -60,7 +80,6 @@ export const AutoBeChatUploadBox = (props: AutoBeChatUploadBox.IProps) => {
60
80
  ...buckets.map(({ content }) => content),
61
81
  ] as AutoBeUserMessageContent[];
62
82
 
63
- setEnabled(false);
64
83
  setEmptyText(false);
65
84
  setText("");
66
85
  setBuckets([]);
@@ -72,7 +91,6 @@ export const AutoBeChatUploadBox = (props: AutoBeChatUploadBox.IProps) => {
72
91
  error instanceof Error ? error : new Error("Unknown error"),
73
92
  );
74
93
  }
75
- setEnabled(true);
76
94
  };
77
95
 
78
96
  const handleFileSelect = async (fileList: FileList | null) => {
@@ -272,47 +290,79 @@ export const AutoBeChatUploadBox = (props: AutoBeChatUploadBox.IProps) => {
272
290
  </div>
273
291
  )}
274
292
 
275
- <textarea
276
- ref={inputRef}
277
- style={{
278
- width: "100%",
279
- minHeight: "40px",
280
- maxHeight: "192px",
281
- padding: "8px 12px",
282
- border: `2px solid ${emptyText ? "#f44336" : dragging ? "#1976d2" : "#e0e0e0"}`,
283
- borderRadius: "8px",
284
- fontSize: "0.95rem",
285
- fontFamily: "inherit",
286
- resize: "none",
287
- outline: "none",
288
- color: dragging ? "#1976d2" : "inherit",
289
- backgroundColor: "transparent",
290
- transition: "border-color 0.2s",
291
- }}
292
- placeholder={
293
- emptyText
294
- ? "Cannot send empty message"
295
- : dragging
296
- ? "Drop files here..."
297
- : "Conversate with AI Chatbot"
298
- }
299
- value={text}
300
- onKeyDown={(e) => {
301
- if (e.key === "Enter" && !e.shiftKey) {
302
- e.preventDefault();
303
- if (enabled) void conversate();
304
- }
305
- }}
306
- onChange={(e) => setText(e.target.value)}
307
- onFocus={(e) => {
308
- e.currentTarget.style.borderColor = "#1976d2";
309
- }}
310
- onBlur={(e) => {
311
- e.currentTarget.style.borderColor = emptyText
312
- ? "#f44336"
313
- : "#e0e0e0";
314
- }}
315
- />
293
+ <div style={{ position: "relative" }}>
294
+ <div
295
+ ref={inputRef}
296
+ contentEditable={true}
297
+ style={{
298
+ width: "97%",
299
+ minHeight: "40px",
300
+ maxHeight: "192px",
301
+ padding: "8px 12px",
302
+ border: "none",
303
+ borderRadius: "8px",
304
+ fontSize: "0.95rem",
305
+ fontFamily: "inherit",
306
+ outline: "none",
307
+ boxShadow: "none",
308
+ backgroundColor: "transparent",
309
+ transition: "color 0.2s, opacity 0.2s",
310
+ overflowY: "auto",
311
+ wordBreak: "break-word",
312
+ whiteSpace: "pre-wrap",
313
+ lineHeight: "1.4",
314
+ // Custom scrollbar styles
315
+ scrollbarWidth: "thin",
316
+ scrollbarColor: "rgba(0, 0, 0, 0.3) transparent",
317
+ }}
318
+ onKeyDown={(e) => {
319
+ if (e.key === "Enter" && !e.shiftKey && !isComposing) {
320
+ e.preventDefault();
321
+ if (enabled) {
322
+ void conversate();
323
+ }
324
+ }
325
+ }}
326
+ onCompositionStart={() => {
327
+ setIsComposing(true);
328
+ }}
329
+ onCompositionEnd={() => {
330
+ setIsComposing(false);
331
+ }}
332
+ onInput={(e) => {
333
+ const target = e.target as HTMLDivElement;
334
+ const newText = target.innerText || "";
335
+ // Only update state if text actually changed to prevent cursor issues
336
+ if (newText !== text) {
337
+ setText(newText);
338
+ }
339
+ }}
340
+ suppressContentEditableWarning={true}
341
+ />
342
+ {!text && (
343
+ <div
344
+ style={{
345
+ position: "absolute",
346
+ top: "8px",
347
+ left: "12px",
348
+ right: "12px",
349
+ bottom: "8px",
350
+ color: "rgba(153, 153, 153, 0.6)",
351
+ fontSize: "0.95rem",
352
+ fontFamily: "inherit",
353
+ pointerEvents: "none",
354
+ lineHeight: "1.4",
355
+ padding: "2px 0",
356
+ }}
357
+ >
358
+ {emptyText
359
+ ? "Cannot send empty message"
360
+ : dragging
361
+ ? "Drop files here..."
362
+ : "Conversate with AI Chatbot"}
363
+ </div>
364
+ )}
365
+ </div>
316
366
 
317
367
  <input
318
368
  ref={fileInputRef}
@@ -1,68 +1,247 @@
1
1
  import {
2
- IAutoBePlaygroundHeader,
2
+ IAutoBeRpcListener,
3
3
  IAutoBeRpcService,
4
4
  IAutoBeTokenUsageJson,
5
5
  } from "@autobe/interface";
6
- import { ILlmSchema } from "@samchon/openapi";
7
- import { ReactNode, createContext, useContext, useState } from "react";
8
- import { useEffect } from "react";
6
+ import {
7
+ ReactNode,
8
+ createContext,
9
+ useCallback,
10
+ useContext,
11
+ useEffect,
12
+ useState,
13
+ } from "react";
14
+ import { Communicator } from "tgrid";
9
15
 
10
16
  import {
11
17
  AutoBeListener,
12
18
  AutoBeListenerState,
19
+ IAutoBeAgentSessionStorageStrategy,
13
20
  IAutoBeEventGroup,
14
21
  } from "../structure";
22
+ import { IAutoBeConfig } from "../types/config";
23
+ import { useAutoBeAgentSessionList } from "./AutoBeAgentSessionList";
24
+ import { useSearchParams } from "./SearchParamsContext";
25
+
26
+ export interface IAutoBeServiceData {
27
+ service: IAutoBeRpcService;
28
+ listener: AutoBeListener;
29
+ connector: Communicator<IAutoBeRpcListener, IAutoBeRpcService>;
30
+ close: () => void | Promise<void>;
31
+ sessionId: string;
32
+ }
33
+
34
+ export type AutoBeServiceFactory = (
35
+ config: IAutoBeConfig,
36
+ ) => Promise<IAutoBeServiceData>;
37
+
38
+ export type AutoBeConnectionStatus =
39
+ | "disconnected" // 연결되지 않음
40
+ | "connecting" // 연결 중
41
+ | "connected"; // 연결 완료 및 활성 상태
15
42
 
16
43
  interface AutoBeAgentContextType {
44
+ // Service state
45
+ connectionStatus: AutoBeConnectionStatus;
46
+
47
+ // Service data (available when ready)
17
48
  eventGroups: IAutoBeEventGroup[];
18
49
  tokenUsage: IAutoBeTokenUsageJson | null;
19
- state: AutoBeListenerState;
20
- header: IAutoBePlaygroundHeader<ILlmSchema.Model>;
21
- service: IAutoBeRpcService;
22
- listener: AutoBeListener;
50
+ state: AutoBeListenerState | null;
51
+ service: IAutoBeRpcService | null;
52
+ listener: AutoBeListener | null;
53
+
54
+ // Service management
55
+ getAutoBeService: (config?: IAutoBeConfig) => Promise<IAutoBeServiceData>;
56
+ resetService: () => void;
23
57
  }
24
58
 
25
59
  const AutoBeAgentContext = createContext<AutoBeAgentContextType | null>(null);
26
60
 
27
61
  export function AutoBeAgentProvider({
28
62
  children,
29
- listener,
30
- service,
31
- header,
63
+ serviceFactory,
64
+ storageStrategy,
32
65
  }: {
33
- listener: AutoBeListener;
34
- service: IAutoBeRpcService;
35
- header: IAutoBePlaygroundHeader<ILlmSchema.Model>;
66
+ serviceFactory: AutoBeServiceFactory;
36
67
  children: ReactNode;
68
+ storageStrategy: IAutoBeAgentSessionStorageStrategy;
37
69
  }) {
70
+ // Service state
71
+ const [connectionStatus, setConnectionStatus] =
72
+ useState<AutoBeConnectionStatus>("disconnected");
73
+
74
+ // Service data
75
+ const { searchParams, setSearchParams } = useSearchParams();
76
+ // Use URL parameter for conversation ID - enables bookmark/share support
77
+ const activeConversationId = searchParams.get("session-id") ?? null;
78
+
38
79
  const [tokenUsage, setTokenUsage] = useState<IAutoBeTokenUsageJson | null>(
39
80
  null,
40
81
  );
41
82
  const [eventGroups, setEventGroups] = useState<IAutoBeEventGroup[]>([]);
42
83
 
84
+ // Context-scoped service instance (contains service, listener, header)
85
+ const [serviceInstance, setServiceInstance] =
86
+ useState<IAutoBeServiceData | null>(null);
87
+
88
+ const { refreshSessionList } = useAutoBeAgentSessionList();
89
+ // Context-scoped service getter
90
+ const getAutoBeService = useCallback(
91
+ async (
92
+ config: IAutoBeConfig = {} as IAutoBeConfig,
93
+ ): Promise<IAutoBeServiceData> => {
94
+ // Return existing instance if available
95
+ if (serviceInstance && connectionStatus === "connected") {
96
+ return serviceInstance;
97
+ }
98
+
99
+ // Prevent multiple concurrent creations
100
+ if (connectionStatus === "connecting") {
101
+ throw new Error("Service is already connecting. Please wait.");
102
+ }
103
+
104
+ if (!serviceFactory) {
105
+ throw new Error("No service factory provided. Cannot create service.");
106
+ }
107
+
108
+ try {
109
+ setConnectionStatus("connecting");
110
+
111
+ // Create new service instance
112
+ const newServiceData = await serviceFactory({
113
+ ...config,
114
+ sessionId: activeConversationId,
115
+ });
116
+ newServiceData.connector.join().then(async () => {
117
+ const res = await serviceFactory({
118
+ ...config,
119
+ sessionId: activeConversationId,
120
+ });
121
+ setServiceInstance(res);
122
+ });
123
+ setServiceInstance(newServiceData);
124
+
125
+ setSearchParams((sp) => {
126
+ const newSp = new URLSearchParams(sp);
127
+ newSp.set("session-id", newServiceData.sessionId);
128
+ return newSp;
129
+ });
130
+ setConnectionStatus("connected");
131
+
132
+ return newServiceData;
133
+ } catch (error) {
134
+ setConnectionStatus("disconnected");
135
+ throw error;
136
+ }
137
+ },
138
+ [
139
+ serviceFactory,
140
+ serviceInstance,
141
+ connectionStatus,
142
+ activeConversationId,
143
+ searchParams,
144
+ ],
145
+ );
146
+
147
+ // Reset service (for reconnection, etc.)
148
+ const resetService = useCallback(() => {
149
+ setServiceInstance(null);
150
+ setConnectionStatus("disconnected");
151
+ setEventGroups([]);
152
+ setTokenUsage(null);
153
+ }, []);
154
+
43
155
  useEffect(() => {
44
- listener.on(async (e) => {
45
- service
156
+ if (activeConversationId === null) {
157
+ setEventGroups([]);
158
+ setTokenUsage(null);
159
+ return;
160
+ }
161
+
162
+ storageStrategy
163
+ .getSession({
164
+ id: activeConversationId,
165
+ })
166
+ .then((v) => {
167
+ if (v === null) {
168
+ return null;
169
+ }
170
+ refreshSessionList();
171
+ setEventGroups(v.events);
172
+ setTokenUsage(v.tokenUsage);
173
+ })
174
+ .catch(console.error);
175
+ }, [activeConversationId]);
176
+
177
+ useEffect(() => {
178
+ if (serviceInstance === null) {
179
+ return;
180
+ }
181
+
182
+ serviceInstance.listener.on(async (e) => {
183
+ serviceInstance.service
46
184
  .getTokenUsage()
47
185
  .then(setTokenUsage)
48
186
  .catch(() => {});
49
187
  setEventGroups(e);
50
188
  });
51
- service
189
+
190
+ serviceInstance.service
52
191
  .getTokenUsage()
53
192
  .then(setTokenUsage)
54
193
  .catch(() => {});
55
- }, []);
194
+ }, [serviceInstance]);
195
+
196
+ useEffect(() => {
197
+ if (activeConversationId === null || serviceInstance === null) {
198
+ return;
199
+ }
200
+
201
+ const originConversate = serviceInstance.service.conversate;
202
+ serviceInstance.service.conversate = async (content) => {
203
+ const result = await originConversate(content);
204
+ await storageStrategy.appendHistory({
205
+ id: activeConversationId,
206
+ history: result,
207
+ });
208
+ return result;
209
+ };
210
+
211
+ const registerEvent = async (e: IAutoBeEventGroup[]) => {
212
+ await storageStrategy.appendEvent({
213
+ id: activeConversationId,
214
+ events: e,
215
+ });
216
+ await storageStrategy.setTokenUsage({
217
+ id: activeConversationId,
218
+ tokenUsage: await serviceInstance.service.getTokenUsage(),
219
+ });
220
+ };
221
+
222
+ serviceInstance.listener.on(registerEvent);
223
+ return () => {
224
+ serviceInstance.service.conversate = originConversate;
225
+ serviceInstance.listener.off(registerEvent);
226
+ };
227
+ }, [activeConversationId, serviceInstance]);
56
228
 
57
229
  return (
58
230
  <AutoBeAgentContext.Provider
59
231
  value={{
232
+ // Service state
233
+ connectionStatus,
234
+
235
+ // Service data
60
236
  eventGroups,
61
237
  tokenUsage,
62
- state: listener.getState(),
63
- header,
64
- service,
65
- listener,
238
+ state: serviceInstance?.listener?.getState() ?? null,
239
+ service: serviceInstance?.service ?? null,
240
+ listener: serviceInstance?.listener ?? null,
241
+
242
+ // Service management
243
+ getAutoBeService,
244
+ resetService,
66
245
  }}
67
246
  >
68
247
  {children}
@@ -0,0 +1,58 @@
1
+ import {
2
+ ReactNode,
3
+ createContext,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useState,
8
+ } from "react";
9
+
10
+ import {
11
+ IAutoBeAgentSession,
12
+ IAutoBeAgentSessionStorageStrategy,
13
+ } from "../structure";
14
+
15
+ interface AutoBeAgentSessionListContextType {
16
+ sessionList: IAutoBeAgentSession[];
17
+ refreshSessionList: () => Promise<void>;
18
+ }
19
+
20
+ const AutoBeAgentSessionListContext =
21
+ createContext<AutoBeAgentSessionListContextType | null>(null);
22
+
23
+ export function AutoBeAgentSessionListProvider({
24
+ children,
25
+ storageStrategy,
26
+ }: {
27
+ storageStrategy: IAutoBeAgentSessionStorageStrategy;
28
+ children: ReactNode;
29
+ }) {
30
+ const [sessionList, setSessionList] = useState<IAutoBeAgentSession[]>([]);
31
+
32
+ const refreshSessionList = useCallback(async () => {
33
+ await storageStrategy.getSessionList().then(setSessionList);
34
+ }, [storageStrategy]);
35
+
36
+ useEffect(() => {
37
+ refreshSessionList();
38
+ }, [storageStrategy]);
39
+
40
+ return (
41
+ <AutoBeAgentSessionListContext.Provider
42
+ value={{
43
+ sessionList,
44
+ refreshSessionList,
45
+ }}
46
+ >
47
+ {children}
48
+ </AutoBeAgentSessionListContext.Provider>
49
+ );
50
+ }
51
+
52
+ export function useAutoBeAgentSessionList() {
53
+ const context = useContext(AutoBeAgentSessionListContext);
54
+ if (!context) {
55
+ throw new Error("useAutoBeAgent must be used within a AutoBeAgentProvider");
56
+ }
57
+ return context;
58
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ ReactNode,
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useState,
7
+ } from "react";
8
+
9
+ interface SearchParamsContextType {
10
+ searchParams: URLSearchParams;
11
+ setSearchParams: React.Dispatch<React.SetStateAction<URLSearchParams>>;
12
+ }
13
+
14
+ const SearchParamsContext = createContext<SearchParamsContextType | null>(null);
15
+
16
+ export function SearchParamsProvider({ children }: { children: ReactNode }) {
17
+ const [searchParams, setSearchParams] = useState<URLSearchParams>(
18
+ new URLSearchParams(window.location.search),
19
+ );
20
+
21
+ useEffect(() => {
22
+ const url = new URL(`${window.location.origin}${window.location.pathname}`);
23
+ searchParams.forEach((value, key) => {
24
+ url.searchParams.set(key, value);
25
+ });
26
+ window.history.pushState({}, "", url);
27
+ }, [searchParams]);
28
+
29
+ return (
30
+ <SearchParamsContext
31
+ value={{
32
+ searchParams,
33
+ setSearchParams,
34
+ }}
35
+ >
36
+ {children}
37
+ </SearchParamsContext>
38
+ );
39
+ }
40
+
41
+ export function useSearchParams() {
42
+ const context = useContext(SearchParamsContext);
43
+ if (!context) {
44
+ throw new Error(
45
+ "useSearchParams must be used within a SearchParamsProvider",
46
+ );
47
+ }
48
+ return context;
49
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export * from "./components";
2
+ export * from "./hooks";
2
3
  export * from "./utils";
3
4
  export * from "./structure";
5
+ export * from "./types";
4
6
  export * from "./context/AutoBeAgentContext";
7
+ export * from "./context/AutoBeAgentSessionList";
8
+ export * from "./context/SearchParamsContext";