@adminforth/agent 2.3.0 → 2.3.2

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.
@@ -215,9 +215,11 @@ async function sendMessage() {
215
215
  // While editing a message, the same send action commits the edit (regenerating
216
216
  // from that turn) instead of starting/steering a turn.
217
217
  if (agentStore.isEditingMessage) {
218
- await agentStore.submitEditMessage();
219
- autoResize();
220
- props.conversationAreaRef?.handleSendMessage();
218
+ const editSubmitted = await agentStore.submitEditMessage();
219
+ if (editSubmitted) {
220
+ autoResize();
221
+ props.conversationAreaRef?.handleSendMessage();
222
+ }
221
223
  return;
222
224
  }
223
225
  // Routes to a steer (folded into the running turn) while generating, or a normal
@@ -100,12 +100,14 @@ export function createAgentChatManager({
100
100
  }
101
101
  for (let i = messages.length - 1; i >= 0; i--) {
102
102
  const message = messages[i];
103
- if (message.role === 'user' && !message.metadata?.steer) {
103
+ if (message.role === 'user') {
104
104
  messages.splice(i, 1, {
105
105
  ...message,
106
106
  metadata: { ...(message.metadata ?? {}), turnId },
107
107
  });
108
- return;
108
+ if (!message.metadata?.steer) {
109
+ return;
110
+ }
109
111
  }
110
112
  }
111
113
  }
@@ -310,10 +312,6 @@ export function createAgentChatManager({
310
312
  if (!chat) {
311
313
  return;
312
314
  }
313
- const currentMessage = chat.messages.find((m: any) => m.id === messageId);
314
- if (!currentMessage || currentMessage.parts[0].text.trim() === text.trim()) {
315
- return;
316
- }
317
315
  await stopActiveTurnAndWaitForIdle();
318
316
  editingTurnId.value = turnId;
319
317
  lastMessage.value = text;
@@ -55,6 +55,7 @@ export function createAgentSteerQueue({
55
55
  return;
56
56
  }
57
57
  const steerMessage: IMessage = {
58
+ id: crypto.randomUUID(),
58
59
  role: 'user',
59
60
  metadata: { steer: true },
60
61
  parts: [{ type: 'text', text, state: 'done' }],
@@ -19,6 +19,7 @@ import { createAgentChatManager } from './agentStore/useAgentChat';
19
19
  import { createAgentPlaceholderController } from './agentStore/useAgentPlaceholder';
20
20
  import { createAgentSessionManager } from './agentStore/useAgentSessions';
21
21
  import { createAgentSteerQueue } from './agentStore/useAgentSteerQueue';
22
+ import { STEER_PERSIST_PREFIX } from './agentStore/constants';
22
23
 
23
24
  export const useAgentStore = defineStore('agent', () => {
24
25
  const agentTransitions = useAgentTransitions();
@@ -465,6 +466,19 @@ export const useAgentStore = defineStore('agent', () => {
465
466
  editingEnabled.value = enabled;
466
467
  }
467
468
 
469
+
470
+ function getCompiledMessageTextWithSteeredParts(message: any): string {
471
+ const currentTurnUserMessages = currentChat.value?.messages.filter((msg: any) => msg.metadata?.turnId === message?.metadata?.turnId && msg.role === 'user');
472
+ let messageToCompile = '';
473
+ for(const msg of currentTurnUserMessages) {
474
+ const text = (msg.parts ?? [])
475
+ .filter((part: any) => part.type === 'text')
476
+ .map((part: any) => part.text ?? '')
477
+ .join('');
478
+ messageToCompile += text + ' ';
479
+ }
480
+ return messageToCompile.trim();
481
+ }
468
482
  // Enter edit mode for a user message: stash the current input, then load the
469
483
  // message's text into the textarea for the user to amend. Allowed mid-generation —
470
484
  // committing the edit stops the running turn (see submitEditMessage).
@@ -480,10 +494,7 @@ export const useAgentStore = defineStore('agent', () => {
480
494
  if (editingMessageId.value === null) {
481
495
  savedUserInput.value = userMessageInput.value ?? '';
482
496
  }
483
- const text = (message.parts ?? [])
484
- .filter((part: any) => part.type === 'text')
485
- .map((part: any) => part.text ?? '')
486
- .join('');
497
+ const text = getCompiledMessageTextWithSteeredParts(message);
487
498
  editingMessageId.value = messageId;
488
499
  editingMessageTurnId.value = turnId;
489
500
  userMessageInput.value = text;
@@ -506,20 +517,25 @@ export const useAgentStore = defineStore('agent', () => {
506
517
  nextTick(() => focusTextInput());
507
518
  }
508
519
 
509
- async function submitEditMessage() {
520
+ async function submitEditMessage(): Promise<boolean> {
510
521
  if (editingMessageId.value === null) {
511
- return;
522
+ return false;
512
523
  }
513
524
  const text = trimmedUserMessage.value;
514
525
  if (!text) {
515
- return;
526
+ return false;
516
527
  }
517
528
  const messageId = editingMessageId.value;
518
529
  const turnId = editingMessageTurnId.value!;
519
530
  steerBuffer.clear();
520
531
  // Restore the stashed input and leave edit mode before the regeneration streams in.
521
532
  exitEditMode();
533
+ const lastMessageText = getCompiledMessageTextWithSteeredParts(currentChat.value?.messages.find((m: any) => m.id === messageId));
534
+ if (lastMessageText.trim() === text.trim()) {
535
+ return false;
536
+ }
522
537
  await sendEditMessage({ messageId, turnId, text });
538
+ return true;
523
539
  }
524
540
 
525
541
  function resolveInternalRoute(href: string): string | null {
@@ -612,6 +628,7 @@ export const useAgentStore = defineStore('agent', () => {
612
628
  setEditingEnabled,
613
629
  isEditingMessage,
614
630
  editingMessageId,
631
+ editingMessageTurnId,
615
632
  startEditMessage,
616
633
  cancelEditMessage,
617
634
  submitEditMessage,
@@ -41,7 +41,7 @@
41
41
  </div>
42
42
  </Transition>
43
43
  <div
44
- v-if="message.role === 'user' && message.metadata?.steer"
44
+ v-if="message.role === 'user' && message.metadata?.steer && !isBeingEditedTurn"
45
45
  class="self-end mr-4 mb-0.5 text-xs italic text-gray-400 dark:text-gray-500"
46
46
  >
47
47
  {{ $t('Steering instruction') }}
@@ -55,10 +55,10 @@
55
55
  :key="part.type"
56
56
  >
57
57
  <TextRenderer
58
- v-if="part.type === 'text' && !checkIfMessageSystemMessage(part.text ?? '')"
59
- :message="part.text"
58
+ v-if="part.type === 'text' && !checkIfMessageSystemMessage(part.text ?? '') && shouldBeShownWhileEditing"
59
+ :message="isBeingEditedMessage ? agentStore.userMessageInput : part.text"
60
60
  :role="props.message.role"
61
- :highlight="isBeingEdited"
61
+ :editMode="isBeingEditedMessage"
62
62
  :state="part.state ?? (props.message.role === 'user' ? 'done' : undefined)"
63
63
  />
64
64
  <ToolApprovalRenderer
@@ -105,15 +105,27 @@
105
105
  const canEdit = computed(() =>
106
106
  agentStore.editingEnabled
107
107
  && props.message.role === 'user'
108
- && !props.message.metadata?.steer
108
+ // && !props.message.metadata?.steer
109
109
  && Boolean(props.message.metadata?.turnId)
110
110
  && !agentStore.isEditingMessage
111
111
  );
112
112
 
113
- const isBeingEdited = computed(() =>
113
+ const isBeingEditedMessage = computed(() =>
114
114
  Boolean(props.message.id) && props.message.id === agentStore.editingMessageId
115
115
  );
116
116
 
117
+ const isBeingEditedTurn = computed(() =>
118
+ Boolean(agentStore.editingMessageTurnId && props.message?.metadata?.turnId === agentStore.editingMessageTurnId)
119
+ );
120
+
121
+ const isUserMessage = computed(() => props.message.role === 'user');
122
+
123
+ // While editing, hide only the *other* user messages of the edited turn (its steer
124
+ // sub-messages); the edited message itself and all non-user parts stay visible.
125
+ const shouldBeShownWhileEditing = computed(() =>
126
+ !(isBeingEditedTurn.value && isUserMessage.value && !isBeingEditedMessage.value)
127
+ );
128
+
117
129
  function checkIfMessageSystemMessage(message: IMessage): boolean {
118
130
  const isReserved = Object.values(RESERVED_SYSTEM_MESSAGE_CONTENT).includes(message as RESERVED_SYSTEM_MESSAGE_CONTENT);
119
131
  return isReserved;
@@ -6,7 +6,7 @@
6
6
  hasVegaLite ? 'w-full my-2' : 'm-2',
7
7
  props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end max-w-[80%] mr-4'
8
8
  : 'border-none self-start max-w-full',
9
- props.highlight ? 'ring-2 ring-lightPrimary dark:ring-darkPrimary w-full max-w-[calc(100%-2rem)]' : ''
9
+ props.editMode ? 'ring-2 ring-lightPrimary dark:ring-darkPrimary w-full max-w-[calc(100%-2rem)]' : ''
10
10
  ]"
11
11
  >
12
12
  <IncremarkContent
@@ -36,7 +36,7 @@
36
36
  message: string | undefined,
37
37
  state: string | undefined,
38
38
  role: IMessage['role'],
39
- highlight?: boolean
39
+ editMode?: boolean
40
40
  }>();
41
41
 
42
42
  const emit = defineEmits(['toggle-thoughts']);
@@ -27,6 +27,7 @@
27
27
  :isRecording="microphoneButtonMode === 'listen'"
28
28
  :amplitude="audioAmplitude"
29
29
  />
30
+ <Spinner v-if="isPolling" class="ml-2 w-4 h-4 text-lightButtonsText dark:text-darkButtonsText fill-lightButtonsBackground dark:fill-darkPrimary" />
30
31
  </div>
31
32
  <div v-else-if="microphoneButtonMode === 'generating'" class="flex items-center justify-center gap-2 text-white text-sm">
32
33
  <span class="w-3 h-3 bg-white rounded-sm" />
@@ -65,6 +66,7 @@ const hideAnimationDebounced = debounce(() => {
65
66
  const sendUserRecordDebounced = debounce(() => {
66
67
  sendRecordForTranscription();
67
68
  }, 500);
69
+ const isPolling = ref(false);
68
70
 
69
71
  const isAudioChatMode = computed(() => agentStore.isAudioChatMode);
70
72
 
@@ -116,8 +118,10 @@ function toggleChatMode() {
116
118
  }
117
119
 
118
120
  async function onStartRecording() {
121
+ isPolling.value = true;
119
122
  await unlockAudio();
120
123
  await requestMicAndStartVAD(saidSomething, stopRecording, onAnySound);
124
+ isPolling.value = false;
121
125
  microphoneButtonMode.value = 'listen';
122
126
  agentAudio.playBeep(1000);
123
127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",