@assistant-ui/react 0.5.23 → 0.5.25

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.mjs CHANGED
@@ -159,11 +159,23 @@ var makeComposerStore = (useThreadMessages, useThreadActions) => {
159
159
 
160
160
  // src/context/stores/Thread.ts
161
161
  import { create as create4 } from "zustand";
162
+ var getThreadStateFromRuntime = (runtime) => {
163
+ const lastMessage = runtime.messages.at(-1);
164
+ if (lastMessage?.role !== "assistant")
165
+ return Object.freeze({
166
+ isDisabled: runtime.isDisabled,
167
+ isRunning: false,
168
+ unstable_canAppendNew: runtime.isDisabled
169
+ });
170
+ return Object.freeze({
171
+ isDisabled: runtime.isDisabled,
172
+ isRunning: lastMessage.status.type === "running",
173
+ unstable_canAppendNew: !runtime.isDisabled && lastMessage.status.type !== "running" && lastMessage.status.type !== "requires-action"
174
+ });
175
+ };
162
176
  var makeThreadStore = (runtimeRef) => {
163
- return create4(() => ({
164
- isDisabled: runtimeRef.getState().isDisabled,
165
- isRunning: runtimeRef.getState().isRunning
166
- }));
177
+ const runtime = runtimeRef.getState();
178
+ return create4(() => getThreadStateFromRuntime(runtime));
167
179
  };
168
180
 
169
181
  // src/context/stores/ThreadViewport.tsx
@@ -260,13 +272,11 @@ var ThreadProvider = ({
260
272
  useCallback2(
261
273
  (thread) => {
262
274
  const onThreadUpdate = () => {
263
- const threadState = context.useThread.getState();
264
- if (thread.isRunning !== threadState.isRunning || thread.isDisabled !== threadState.isDisabled) {
275
+ const oldState = context.useThread.getState();
276
+ const state = getThreadStateFromRuntime(thread);
277
+ if (oldState.isDisabled !== state.isDisabled || oldState.isRunning !== state.isRunning || oldState.unstable_canAppendNew !== state.unstable_canAppendNew) {
265
278
  context.useThread.setState(
266
- Object.freeze({
267
- isRunning: thread.isRunning,
268
- isDisabled: thread.isDisabled
269
- }),
279
+ getThreadStateFromRuntime(thread),
270
280
  true
271
281
  );
272
282
  }
@@ -520,8 +530,8 @@ var useCombinedStore = (stores, selector) => {
520
530
  return useCombined(selector);
521
531
  };
522
532
 
523
- // src/utils/getMessageText.tsx
524
- var getMessageText = (message) => {
533
+ // src/utils/getThreadMessageText.tsx
534
+ var getThreadMessageText = (message) => {
525
535
  const textParts = message.content.filter(
526
536
  (part) => part.type === "text"
527
537
  );
@@ -543,7 +553,7 @@ var useActionBarCopy = ({
543
553
  const { message } = useMessage.getState();
544
554
  const { setIsCopied } = useMessageUtils.getState();
545
555
  const { isEditing, value: composerValue } = useEditComposer.getState();
546
- const valueToCopy = isEditing ? composerValue : getMessageText(message);
556
+ const valueToCopy = isEditing ? composerValue : getThreadMessageText(message);
547
557
  navigator.clipboard.writeText(valueToCopy).then(() => {
548
558
  setIsCopied(true);
549
559
  setTimeout(() => setIsCopied(false), copiedDuration);
@@ -670,7 +680,7 @@ var useComposerSend = () => {
670
680
  const { useComposer } = useComposerContext();
671
681
  const disabled = useCombinedStore(
672
682
  [useThread, useComposer],
673
- (t, c) => t.isDisabled || t.isRunning || !c.isEditing || c.value.length === 0
683
+ (t, c) => !t.unstable_canAppendNew || !c.isEditing || c.value.length === 0
674
684
  );
675
685
  const callback = useCallback11(() => {
676
686
  const composerState = useComposer.getState();
@@ -1568,8 +1578,8 @@ var ComposerPrimitiveInput = forwardRef12(
1568
1578
  const handleKeyPress = (e) => {
1569
1579
  if (isDisabled) return;
1570
1580
  if (e.key === "Enter" && e.shiftKey === false) {
1571
- const isRunning = useThread.getState().isRunning;
1572
- if (!isRunning) {
1581
+ const { unstable_canAppendNew } = useThread.getState();
1582
+ if (unstable_canAppendNew) {
1573
1583
  e.preventDefault();
1574
1584
  textareaRef.current?.closest("form")?.requestSubmit();
1575
1585
  }
@@ -1902,7 +1912,7 @@ var useMessageContext2 = (messageIndex) => {
1902
1912
  throw new Error(
1903
1913
  "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
1904
1914
  );
1905
- const text = getMessageText(message);
1915
+ const text = getThreadMessageText(message);
1906
1916
  return text;
1907
1917
  },
1908
1918
  onSend: (text) => {
@@ -2144,6 +2154,13 @@ var MessageRepository = class {
2144
2154
  }
2145
2155
  }
2146
2156
  if (operation !== "cut") {
2157
+ for (let current = newParent; current; current = current.prev) {
2158
+ if (current.current.id === child.current.id) {
2159
+ throw new Error(
2160
+ "MessageRepository(performOp/link): A message with the same id already exists in the parent tree. This error occurs if the same message id is found multiple times. This is likely an internal bug in assistant-ui."
2161
+ );
2162
+ }
2163
+ }
2147
2164
  newParentOrRoot.children = [
2148
2165
  ...newParentOrRoot.children,
2149
2166
  child.current.id
@@ -2702,9 +2719,6 @@ var LocalThreadRuntime = class {
2702
2719
  get messages() {
2703
2720
  return this.repository.getMessages();
2704
2721
  }
2705
- get isRunning() {
2706
- return this.abortController != null;
2707
- }
2708
2722
  getBranches(messageId) {
2709
2723
  return this.repository.getBranches(messageId);
2710
2724
  }
@@ -2793,8 +2807,6 @@ var LocalThreadRuntime = class {
2793
2807
  updateMessage({
2794
2808
  status: { type: "complete", reason: "unknown" }
2795
2809
  });
2796
- } else {
2797
- this.notifySubscribers();
2798
2810
  }
2799
2811
  } catch (e) {
2800
2812
  this.abortController = null;
@@ -2893,18 +2905,12 @@ var useLocalRuntime = (adapter, options) => {
2893
2905
  return runtime;
2894
2906
  };
2895
2907
 
2896
- // src/runtimes/external-store/ExternalStoreThreadRuntime.tsx
2897
- import { create as create15 } from "zustand";
2898
-
2899
2908
  // src/runtimes/external-store/getExternalStoreMessage.tsx
2900
2909
  var symbolInnerMessage = Symbol("innerMessage");
2901
2910
  var getExternalStoreMessage = (message) => {
2902
2911
  return message[symbolInnerMessage];
2903
2912
  };
2904
2913
 
2905
- // src/runtimes/external-store/useExternalStoreSync.tsx
2906
- import { useEffect as useEffect11, useInsertionEffect as useInsertionEffect4, useMemo as useMemo4, useRef as useRef6 } from "react";
2907
-
2908
2914
  // src/runtimes/external-store/ThreadMessageConverter.ts
2909
2915
  var ThreadMessageConverter = class {
2910
2916
  cache = /* @__PURE__ */ new WeakMap();
@@ -2925,6 +2931,7 @@ var AUTO_STATUS_COMPLETE = Object.freeze({
2925
2931
  type: "complete",
2926
2932
  reason: "unknown"
2927
2933
  });
2934
+ var isAutoStatus = (status) => status === AUTO_STATUS_RUNNING || status === AUTO_STATUS_COMPLETE;
2928
2935
  var getAutoStatus = (isLast, isRunning) => isLast && isRunning ? AUTO_STATUS_RUNNING : AUTO_STATUS_COMPLETE;
2929
2936
 
2930
2937
  // src/runtimes/external-store/ThreadMessageLike.tsx
@@ -2995,106 +3002,112 @@ var fromThreadMessageLike = (like, fallbackId, fallbackStatus) => {
2995
3002
  }
2996
3003
  };
2997
3004
 
2998
- // src/runtimes/external-store/useExternalStoreSync.tsx
2999
- var useExternalStoreSync = (adapter, updateData) => {
3000
- const adapterRef = useRef6(adapter);
3001
- useInsertionEffect4(() => {
3002
- adapterRef.current = adapter;
3003
- });
3004
- const [converter, convertCallback] = useMemo4(() => {
3005
- const converter2 = adapter.convertMessage ?? ((m) => m);
3006
- const convertCallback2 = (cache, m, idx) => {
3007
- const autoStatus = getAutoStatus(
3008
- adapterRef.current.messages.at(-1) === m,
3009
- adapterRef.current.isRunning ?? false
3010
- );
3011
- if (cache && (cache.role !== "assistant" || cache.status === autoStatus))
3012
- return cache;
3013
- const newMessage = fromThreadMessageLike(
3014
- converter2(m, idx),
3015
- idx.toString(),
3016
- autoStatus
3017
- );
3018
- newMessage[symbolInnerMessage] = m;
3019
- return newMessage;
3020
- };
3021
- return [new ThreadMessageConverter(), convertCallback2];
3022
- }, [adapter.convertMessage]);
3023
- useEffect11(() => {
3024
- updateData(
3025
- adapter.isDisabled ?? false,
3026
- adapter.isRunning ?? false,
3027
- converter.convertMessages(adapter.messages, convertCallback)
3028
- );
3029
- }, [
3030
- updateData,
3031
- converter,
3032
- convertCallback,
3033
- adapter.isDisabled,
3034
- adapter.isRunning,
3035
- adapter.messages
3036
- ]);
3037
- };
3038
-
3039
3005
  // src/runtimes/external-store/ExternalStoreThreadRuntime.tsx
3040
3006
  var hasUpcomingMessage = (isRunning, messages) => {
3041
3007
  return isRunning && messages[messages.length - 1]?.role !== "assistant";
3042
3008
  };
3043
3009
  var ExternalStoreThreadRuntime = class {
3044
- constructor(store) {
3045
- this.store = store;
3046
- this.updateData(
3047
- store.isDisabled ?? false,
3048
- store.isRunning ?? false,
3049
- store.messages
3050
- );
3051
- this.useStore = create15(() => ({
3052
- store
3053
- }));
3054
- }
3055
3010
  _subscriptions = /* @__PURE__ */ new Set();
3056
3011
  repository = new MessageRepository();
3057
3012
  assistantOptimisticId = null;
3058
- useStore;
3059
3013
  get capabilities() {
3060
3014
  return {
3061
- switchToBranch: this.store.setMessages !== void 0,
3062
- edit: this.store.onEdit !== void 0,
3063
- reload: this.store.onReload !== void 0,
3064
- cancel: this.store.onCancel !== void 0,
3065
- copy: this.store.onCopy !== null
3015
+ switchToBranch: this._store.setMessages !== void 0,
3016
+ edit: this._store.onEdit !== void 0,
3017
+ reload: this._store.onReload !== void 0,
3018
+ cancel: this._store.onCancel !== void 0,
3019
+ copy: this._store.onCopy !== null
3066
3020
  };
3067
3021
  }
3068
3022
  messages = [];
3069
3023
  isDisabled = false;
3070
- isRunning = false;
3024
+ converter = new ThreadMessageConverter();
3025
+ _store;
3026
+ constructor(store) {
3027
+ this.store = store;
3028
+ }
3029
+ set store(store) {
3030
+ const oldStore = this._store;
3031
+ if (oldStore) {
3032
+ if (oldStore.convertMessage !== store.convertMessage) {
3033
+ this.converter = new ThreadMessageConverter();
3034
+ } else if (oldStore.isDisabled === store.isDisabled && oldStore.isRunning === store.isRunning && oldStore.messages === store.messages) {
3035
+ return;
3036
+ }
3037
+ }
3038
+ this._store = store;
3039
+ const isRunning = store.isRunning ?? false;
3040
+ const isDisabled = store.isDisabled ?? false;
3041
+ const convertCallback = (cache, m, idx) => {
3042
+ if (!store.convertMessage) return m;
3043
+ const isLast = idx === store.messages.length - 1;
3044
+ const autoStatus = getAutoStatus(isLast, isRunning);
3045
+ if (cache && (cache.role !== "assistant" || !isAutoStatus(cache.status) || cache.status === autoStatus))
3046
+ return cache;
3047
+ const newMessage = fromThreadMessageLike(
3048
+ store.convertMessage(m, idx),
3049
+ idx.toString(),
3050
+ autoStatus
3051
+ );
3052
+ newMessage[symbolInnerMessage] = m;
3053
+ return newMessage;
3054
+ };
3055
+ const messages = this.converter.convertMessages(
3056
+ store.messages,
3057
+ convertCallback
3058
+ );
3059
+ for (let i = 0; i < messages.length; i++) {
3060
+ const message = messages[i];
3061
+ const parent = messages[i - 1];
3062
+ this.repository.addOrUpdateMessage(parent?.id ?? null, message);
3063
+ }
3064
+ if (this.assistantOptimisticId) {
3065
+ this.repository.deleteMessage(this.assistantOptimisticId);
3066
+ this.assistantOptimisticId = null;
3067
+ }
3068
+ if (hasUpcomingMessage(isRunning, messages)) {
3069
+ this.assistantOptimisticId = this.repository.appendOptimisticMessage(
3070
+ messages.at(-1)?.id ?? null,
3071
+ {
3072
+ role: "assistant",
3073
+ content: []
3074
+ }
3075
+ );
3076
+ }
3077
+ this.repository.resetHead(
3078
+ this.assistantOptimisticId ?? messages.at(-1)?.id ?? null
3079
+ );
3080
+ this.messages = this.repository.getMessages();
3081
+ this.isDisabled = isDisabled;
3082
+ for (const callback of this._subscriptions) callback();
3083
+ }
3071
3084
  getBranches(messageId) {
3072
3085
  return this.repository.getBranches(messageId);
3073
3086
  }
3074
3087
  switchToBranch(branchId) {
3075
- if (!this.store.setMessages)
3088
+ if (!this._store.setMessages)
3076
3089
  throw new Error("Runtime does not support switching branches.");
3077
3090
  this.repository.switchToBranch(branchId);
3078
3091
  this.updateMessages(this.repository.getMessages());
3079
3092
  }
3080
3093
  async append(message) {
3081
3094
  if (message.parentId !== (this.messages.at(-1)?.id ?? null)) {
3082
- if (!this.store.onEdit)
3095
+ if (!this._store.onEdit)
3083
3096
  throw new Error("Runtime does not support editing messages.");
3084
- await this.store.onEdit(message);
3097
+ await this._store.onEdit(message);
3085
3098
  } else {
3086
- await this.store.onNew(message);
3099
+ await this._store.onNew(message);
3087
3100
  }
3088
3101
  }
3089
3102
  async startRun(parentId) {
3090
- if (!this.store.onReload)
3103
+ if (!this._store.onReload)
3091
3104
  throw new Error("Runtime does not support reloading messages.");
3092
- await this.store.onReload(parentId);
3105
+ await this._store.onReload(parentId);
3093
3106
  }
3094
3107
  cancelRun() {
3095
- if (!this.store.onCancel)
3108
+ if (!this._store.onCancel)
3096
3109
  throw new Error("Runtime does not support cancelling runs.");
3097
- this.store.onCancel();
3110
+ this._store.onCancel();
3098
3111
  if (this.assistantOptimisticId) {
3099
3112
  this.repository.deleteMessage(this.assistantOptimisticId);
3100
3113
  this.assistantOptimisticId = null;
@@ -3109,51 +3122,14 @@ var ExternalStoreThreadRuntime = class {
3109
3122
  return () => this._subscriptions.delete(callback);
3110
3123
  }
3111
3124
  updateMessages = (messages) => {
3112
- this.store.setMessages?.(
3125
+ this._store.setMessages?.(
3113
3126
  messages.flatMap(getExternalStoreMessage).filter((m) => m != null)
3114
3127
  );
3115
3128
  };
3116
- onStoreUpdated() {
3117
- if (this.useStore.getState().store !== this.store) {
3118
- this.useStore.setState({ store: this.store });
3119
- }
3120
- }
3121
- updateData = (isDisabled, isRunning, vm) => {
3122
- for (let i = 0; i < vm.length; i++) {
3123
- const message = vm[i];
3124
- const parent = vm[i - 1];
3125
- this.repository.addOrUpdateMessage(parent?.id ?? null, message);
3126
- }
3127
- if (this.assistantOptimisticId) {
3128
- this.repository.deleteMessage(this.assistantOptimisticId);
3129
- this.assistantOptimisticId = null;
3130
- }
3131
- if (hasUpcomingMessage(isRunning, vm)) {
3132
- this.assistantOptimisticId = this.repository.appendOptimisticMessage(
3133
- vm.at(-1)?.id ?? null,
3134
- {
3135
- role: "assistant",
3136
- content: []
3137
- }
3138
- );
3139
- }
3140
- this.repository.resetHead(
3141
- this.assistantOptimisticId ?? vm.at(-1)?.id ?? null
3142
- );
3143
- this.messages = this.repository.getMessages();
3144
- this.isDisabled = isDisabled;
3145
- this.isRunning = isRunning;
3146
- for (const callback of this._subscriptions) callback();
3147
- };
3148
- unstable_synchronizer = () => {
3149
- const { store } = this.useStore();
3150
- useExternalStoreSync(store, this.updateData);
3151
- return null;
3152
- };
3153
3129
  addToolResult(options) {
3154
- if (!this.store.onAddToolResult)
3130
+ if (!this._store.onAddToolResult)
3155
3131
  throw new Error("Runtime does not support tool results.");
3156
- this.store.onAddToolResult(options);
3132
+ this._store.onAddToolResult(options);
3157
3133
  }
3158
3134
  };
3159
3135
 
@@ -3166,9 +3142,6 @@ var ExternalStoreRuntime = class extends BaseAssistantRuntime {
3166
3142
  set store(store) {
3167
3143
  this.thread.store = store;
3168
3144
  }
3169
- onStoreUpdated() {
3170
- return this.thread.onStoreUpdated();
3171
- }
3172
3145
  getModelConfig() {
3173
3146
  return this._proxyConfigProvider.getModelConfig();
3174
3147
  }
@@ -3189,15 +3162,12 @@ var ExternalStoreRuntime = class extends BaseAssistantRuntime {
3189
3162
  };
3190
3163
 
3191
3164
  // src/runtimes/external-store/useExternalStoreRuntime.tsx
3192
- import { useEffect as useEffect12, useInsertionEffect as useInsertionEffect5, useState as useState10 } from "react";
3165
+ import { useInsertionEffect as useInsertionEffect4, useState as useState10 } from "react";
3193
3166
  var useExternalStoreRuntime = (store) => {
3194
3167
  const [runtime] = useState10(() => new ExternalStoreRuntime(store));
3195
- useInsertionEffect5(() => {
3168
+ useInsertionEffect4(() => {
3196
3169
  runtime.store = store;
3197
3170
  });
3198
- useEffect12(() => {
3199
- runtime.onStoreUpdated();
3200
- });
3201
3171
  return runtime;
3202
3172
  };
3203
3173