@adminforth/agent 1.0.0 → 1.1.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.
@@ -1,7 +1,11 @@
1
1
  import { ToolMessage } from "@langchain/core/messages";
2
2
  import { createMiddleware } from "langchain";
3
3
  import { logger } from "adminforth";
4
- import type { ApiBasedTool } from "../../apiBasedTools.js";
4
+ import { type ApiBasedTool } from "../../apiBasedTools.js";
5
+ import {
6
+ createToolCallTracker,
7
+ type ToolCallEventSink,
8
+ } from "../toolCallEvents.js";
5
9
  import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "../tools/index.js";
6
10
  import { createApiTool } from "../tools/apiTool.js";
7
11
 
@@ -67,30 +71,46 @@ export function createApiBasedToolsMiddleware(
67
71
  async wrapToolCall(request, handler) {
68
72
  const startedAt = Date.now();
69
73
  const toolInput = JSON.stringify(request.toolCall.args ?? {});
74
+ const { emitToolCallEvent } = request.runtime.context as {
75
+ emitToolCallEvent: ToolCallEventSink;
76
+ };
77
+ const toolCallTracker = createToolCallTracker({
78
+ emit: emitToolCallEvent,
79
+ toolCallId: request.toolCall.id,
80
+ toolName: request.toolCall.name,
81
+ input: (request.toolCall.args ?? {}) as Record<string, unknown>,
82
+ startedAt,
83
+ });
84
+ toolCallTracker.start();
70
85
  logger.info(
71
86
  `Invoking tool "${request.toolCall.name}" with input: ${toolInput}`,
72
87
  );
73
88
 
74
89
  try {
75
- if (request.tool) {
76
- return await handler(request);
77
- }
90
+ let result;
78
91
 
79
- const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
92
+ if (request.tool) {
93
+ result = await handler(request);
94
+ } else {
95
+ const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
80
96
 
81
- if (enabledApiToolNames.has(request.toolCall.name)) {
82
- return await handler({
83
- ...request,
84
- tool: dynamicTools[request.toolCall.name],
85
- });
97
+ if (enabledApiToolNames.has(request.toolCall.name)) {
98
+ result = await handler({
99
+ ...request,
100
+ tool: dynamicTools[request.toolCall.name],
101
+ });
102
+ } else {
103
+ result = new ToolMessage({
104
+ content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
105
+ tool_call_id: request.toolCall.id ?? "",
106
+ name: request.toolCall.name,
107
+ status: "error",
108
+ });
109
+ }
86
110
  }
87
111
 
88
- return new ToolMessage({
89
- content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
90
- tool_call_id: request.toolCall.id ?? "",
91
- name: request.toolCall.name,
92
- status: "error",
93
- });
112
+ toolCallTracker.finishSuccess(result);
113
+ return result;
94
114
  } catch (error) {
95
115
  const errorDetails =
96
116
  error instanceof Error ? error.stack ?? error.message : String(error);
@@ -98,6 +118,7 @@ export function createApiBasedToolsMiddleware(
98
118
  logger.error(
99
119
  `Tool "${request.toolCall.name}" failed after ${Date.now() - startedAt}ms with input: ${toolInput}\n${errorDetails}`,
100
120
  );
121
+ toolCallTracker.finishError(error);
101
122
  throw error;
102
123
  } finally {
103
124
  logger.info(
@@ -1,3 +1,7 @@
1
+ import { randomUUID } from "crypto";
2
+ import YAML from "yaml";
3
+ import { serializeUnknownError } from "../apiBasedTools.js";
4
+
1
5
  export type ToolCallEvent =
2
6
  | {
3
7
  toolCallId: string;
@@ -15,3 +19,45 @@ export type ToolCallEvent =
15
19
  };
16
20
 
17
21
  export type ToolCallEventSink = (event: ToolCallEvent) => void;
22
+
23
+ export function createToolCallTracker(params: {
24
+ emit: ToolCallEventSink;
25
+ toolCallId?: string;
26
+ toolName: string;
27
+ input?: Record<string, unknown>;
28
+ startedAt?: number;
29
+ }) {
30
+ const toolCallId = params.toolCallId ?? randomUUID();
31
+ const startedAt = params.startedAt ?? Date.now();
32
+
33
+ return {
34
+ start() {
35
+ params.emit({
36
+ toolCallId,
37
+ toolName: params.toolName,
38
+ phase: "start",
39
+ input: YAML.stringify(params.input ?? {}),
40
+ });
41
+ },
42
+ finishSuccess(output: unknown) {
43
+ params.emit({
44
+ toolCallId,
45
+ toolName: params.toolName,
46
+ phase: "end",
47
+ durationMs: Date.now() - startedAt,
48
+ output: YAML.stringify(output).trimEnd(),
49
+ error: null,
50
+ });
51
+ },
52
+ finishError(error: unknown) {
53
+ params.emit({
54
+ toolCallId,
55
+ toolName: params.toolName,
56
+ phase: "end",
57
+ durationMs: Date.now() - startedAt,
58
+ output: null,
59
+ error: YAML.stringify(serializeUnknownError(error)).trimEnd(),
60
+ });
61
+ },
62
+ };
63
+ }
@@ -1,8 +1,5 @@
1
1
  import { tool } from "langchain";
2
- import { randomUUID } from "crypto";
3
- import YAML from "yaml";
4
2
  import type { ApiBasedTool } from "../../apiBasedTools.js";
5
- import { serializeUnknownError } from "../../apiBasedTools.js";
6
3
 
7
4
  const emptyToolSchema = {
8
5
  type: "object",
@@ -52,43 +49,11 @@ export function createApiTool(toolName: string, apiBasedTool: ApiBasedTool) {
52
49
  return tool(
53
50
  async (input, runtime) => {
54
51
  const normalizedInput = (input ?? {}) as Record<string, unknown>;
55
- const toolCallId = randomUUID();
56
- const startedAt = Date.now();
57
- runtime.context.emitToolCallEvent({
58
- toolCallId,
59
- toolName,
60
- phase: "start",
61
- input: YAML.stringify(normalizedInput),
52
+ return apiBasedTool.call({
53
+ adminUser: runtime.context.adminUser,
54
+ inputs: normalizedInput,
55
+ userTimeZone: runtime.context.userTimeZone,
62
56
  });
63
-
64
- try {
65
- const output = await apiBasedTool.call({
66
- adminUser: runtime.context.adminUser,
67
- inputs: normalizedInput,
68
- userTimeZone: runtime.context.userTimeZone,
69
- });
70
-
71
- runtime.context.emitToolCallEvent({
72
- toolCallId,
73
- toolName,
74
- phase: "end",
75
- durationMs: Date.now() - startedAt,
76
- output,
77
- error: null,
78
- });
79
-
80
- return output;
81
- } catch (error) {
82
- runtime.context.emitToolCallEvent({
83
- toolCallId,
84
- toolName,
85
- phase: "end",
86
- durationMs: Date.now() - startedAt,
87
- output: null,
88
- error: YAML.stringify(serializeUnknownError(error)),
89
- });
90
- throw error;
91
- }
92
57
  },
93
58
  {
94
59
  name: toolName,
package/build.log CHANGED
@@ -26,5 +26,5 @@ custom/skills/fetch_data/SKILL.md
26
26
  custom/skills/mutate_data/
27
27
  custom/skills/mutate_data/SKILL.md
28
28
 
29
- sent 136,251 bytes received 367 bytes 273,236.00 bytes/sec
30
- total size is 134,767 speedup is 0.99
29
+ sent 136,840 bytes received 367 bytes 274,414.00 bytes/sec
30
+ total size is 135,334 speedup is 0.99
@@ -17,9 +17,10 @@
17
17
 
18
18
  </div>
19
19
  <AutoScrollContainer
20
- enabled
20
+ :enabled="!showScrollToBottomButton"
21
21
  class="flex flex-col overflow-y-auto border-t border-gray-200 dark:border-gray-700"
22
22
  ref="scrollContainer"
23
+ :threshold="10"
23
24
  behavior="smooth"
24
25
  >
25
26
 
@@ -57,8 +58,8 @@
57
58
  v-if="props.messages.length === 0"
58
59
  class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
59
60
  >
60
- <p>Start the conversation</p>
61
- <p class="tracking-normal text-base text">Give any input to begin</p>
61
+ <p>{{ $t('Start the conversation') }}</p>
62
+ <p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
62
63
  </div>
63
64
  </AutoScrollContainer>
64
65
  </template>
@@ -151,20 +152,16 @@ const groupToolCallParts = (message: IMessage) => {
151
152
  if(!part?.toolInfo) {
152
153
  continue;
153
154
  }
154
- console.log('part', part);
155
155
  if (part.toolInfo.toolName === currentToolName) {
156
- console.log('grouping part with tool name', currentToolName);
157
156
  groupedParts[groupedParts.length - 1].groupedTools.push(part);
158
157
  continue;
159
158
  }
160
159
  currentToolName = part.toolInfo.toolName;
161
- console.log('starting new group with tool name', currentToolName);
162
160
  groupedParts.push({
163
161
  title: currentToolName,
164
162
  groupedTools: [part]
165
163
  });
166
164
  }
167
- console.log('groupedParts', groupedParts);
168
165
  return groupedParts;
169
166
  }
170
167
 
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div
3
- class="max-w-[80%] flex px-4 py-2 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
3
+ class="max-w-[80%] flex px-4 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
4
4
  @click="handleMarkdownLinkClick"
5
5
  :class="props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
6
6
  : isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
@@ -6,14 +6,14 @@
6
6
  "
7
7
  >
8
8
  <h3 :class="h3Style">{{ $t('Chat history') }}</h3>
9
- <Button @click="agentStore.createPreSession()" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
9
+ <Button @click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false)" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
10
10
  <IconPlusOutline class="w-5 h-5" />
11
11
  {{ $t('New chat') }}
12
12
  </Button>
13
13
  <div class="w-full border-b border-gray-200 dark:border-gray-700"/>
14
14
  <div class="absolute w-full h-full flex flex-col items-center justify-center bg-gray-100/50 dark:bg-gray-700/50 z-10" v-if="agentStore.isResponseInProgress">
15
15
  <Spinner class="w-8 h-8" v-if="agentStore.isResponseInProgress" />
16
- <p class="mt-2 text-gray-800 dark:text-gray-200">generation in progress...</p>
16
+ <p class="mt-2 text-gray-800 dark:text-gray-200">{{ $t('Generation in progress...') }}</p>
17
17
  </div>
18
18
  <div v-for="group in groupedSessions" :key="group.dayKey" class="w-full py-2">
19
19
  <div class="px-4 pb-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 dark:text-gray-400">
@@ -38,7 +38,7 @@
38
38
  v-if="!groupedSessions || groupedSessions.length === 0"
39
39
  class="w-full h-full flex items-center justify-center text-gray-800 dark:text-gray-200"
40
40
  >
41
- There is no previous chat sessions
41
+ {{ $t('There are no previous chat sessions') }}
42
42
  </p>
43
43
  </div>
44
44
  </template>
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <div v-if="props.data?.toolInfo" class="inline-flex m-2 max-w-[80%] flex-col gap-3 rounded-xl p-2 text-lightListTableHeadingText dark:text-darkListTableHeadingText">
2
+ <div
3
+ class="inline-flex m-2 max-w-[80%] flex-col gap-3 rounded-xl p-2 cursor-pointer text-lightListTableHeadingText dark:text-darkListTableHeadingText hover:opacity-75"
4
+ @click="isInputOutputExpanded = !isInputOutputExpanded"
5
+ >
3
6
  <div class="flex items-center gap-3">
4
7
  <div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/70 dark:bg-blue-700/20">
5
8
  <Spinner v-if="isRunning" class="h-4 w-4" />
@@ -18,8 +21,7 @@
18
21
  <IconAngleDownOutline
19
22
  v-if="hasToolSections"
20
23
  :class="isInputOutputExpanded ? 'rotate-180' : 'rotate-0'"
21
- class="cursor-pointer transition-transform duration-200 hover:scale-105 hover:opacity-75"
22
- @click="isInputOutputExpanded = !isInputOutputExpanded"
24
+ class="cursor-pointer transition-transform duration-200 hover:scale-105"
23
25
  />
24
26
  </div>
25
27
  <transition name="expand">
@@ -1,8 +1,9 @@
1
1
  <template>
2
2
  <template v-for="group in props.toolGroup" :key="group.title">
3
- <div v-if="group.groupedTools.length > 1" class="mb-4 flex flex-col">
4
- <div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5" @click="toggleGroup(group.title)">
5
- - {{ group.title }} {{ 'x' + group.groupedTools.length }}
3
+ <div v-if="group.groupedTools.length > 1" class="flex flex-col">
4
+ <div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5 text-lightListTableHeadingText dark:text-darkListTableHeadingText" @click="toggleGroup(group.title)">
5
+ <IconMinusOutline class="w-6 h-6 p-1"/>
6
+ {{ group.title }} {{ 'x' + group.groupedTools.length }}
6
7
  <IconAngleDownOutline
7
8
  class="transition-transform duration-200 hover:scale-105 hover:opacity-75"
8
9
  :class="expandedGroups.includes(group.title) ? 'rotate-180' : 'rotate-0'"
@@ -10,7 +11,7 @@
10
11
  </div>
11
12
  <transition name="expand">
12
13
  <div v-show="expandedGroups.includes(group.title)" class="flex flex-col">
13
- <ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" />
14
+ <ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" class="ml-8"/>
14
15
  </div>
15
16
  </transition>
16
17
  </div>
@@ -24,7 +25,7 @@ import { Tool } from 'langchain';
24
25
  import ToolRenderer from './ToolRenderer.vue';
25
26
  import type { IPart } from './types';
26
27
  import { ref } from 'vue';
27
- import { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
28
+ import { IconAngleDownOutline, IconMinusOutline } from '@iconify-prerendered/vue-flowbite';
28
29
 
29
30
  const props = defineProps<{
30
31
  toolGroup: {
@@ -120,6 +120,10 @@ export const useAgentStore = defineStore('agent', () => {
120
120
  });
121
121
  const blockCloseOfChat = ref(false);
122
122
 
123
+ function sortSessionsListByTimestamp(sessionsList: ISessionsListItem[]) {
124
+ return [...sessionsList].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
125
+ }
126
+
123
127
  async function sendMessage() {
124
128
  const message = trimmedUserMessage.value;
125
129
  if (!message || isResponseInProgress.value) {
@@ -128,6 +132,11 @@ export const useAgentStore = defineStore('agent', () => {
128
132
  if (!currentSession.value || currentSession.value.sessionId === 'pre-session') {
129
133
  await createNewSession(message);
130
134
  }
135
+ currentSession.value.timestamp = new Date().toISOString();
136
+ sessionList.value = sortSessionsListByTimestamp(sessionList.value.map((s: ISessionsListItem) => s.sessionId === currentSession.value?.sessionId ? {
137
+ ...s,
138
+ timestamp: currentSession.value?.timestamp || s.timestamp,
139
+ } : s));
131
140
  lastMessage.value = message;
132
141
  currentChat.value?.sendMessage({
133
142
  text: message,
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { ToolMessage } from "@langchain/core/messages";
11
11
  import { createMiddleware } from "langchain";
12
12
  import { logger } from "adminforth";
13
+ import { createToolCallTracker, } from "../toolCallEvents.js";
13
14
  import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "../tools/index.js";
14
15
  import { createApiTool } from "../tools/apiTool.js";
15
16
  function getEnabledApiToolNames(messages) {
@@ -58,28 +59,45 @@ export function createApiBasedToolsMiddleware(apiBasedTools) {
58
59
  },
59
60
  wrapToolCall(request, handler) {
60
61
  return __awaiter(this, void 0, void 0, function* () {
61
- var _a, _b, _c;
62
+ var _a, _b, _c, _d;
62
63
  const startedAt = Date.now();
63
64
  const toolInput = JSON.stringify((_a = request.toolCall.args) !== null && _a !== void 0 ? _a : {});
65
+ const { emitToolCallEvent } = request.runtime.context;
66
+ const toolCallTracker = createToolCallTracker({
67
+ emit: emitToolCallEvent,
68
+ toolCallId: request.toolCall.id,
69
+ toolName: request.toolCall.name,
70
+ input: ((_b = request.toolCall.args) !== null && _b !== void 0 ? _b : {}),
71
+ startedAt,
72
+ });
73
+ toolCallTracker.start();
64
74
  logger.info(`Invoking tool "${request.toolCall.name}" with input: ${toolInput}`);
65
75
  try {
76
+ let result;
66
77
  if (request.tool) {
67
- return yield handler(request);
78
+ result = yield handler(request);
68
79
  }
69
- const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
70
- if (enabledApiToolNames.has(request.toolCall.name)) {
71
- return yield handler(Object.assign(Object.assign({}, request), { tool: dynamicTools[request.toolCall.name] }));
80
+ else {
81
+ const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
82
+ if (enabledApiToolNames.has(request.toolCall.name)) {
83
+ result = yield handler(Object.assign(Object.assign({}, request), { tool: dynamicTools[request.toolCall.name] }));
84
+ }
85
+ else {
86
+ result = new ToolMessage({
87
+ content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
88
+ tool_call_id: (_c = request.toolCall.id) !== null && _c !== void 0 ? _c : "",
89
+ name: request.toolCall.name,
90
+ status: "error",
91
+ });
92
+ }
72
93
  }
73
- return new ToolMessage({
74
- content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
75
- tool_call_id: (_b = request.toolCall.id) !== null && _b !== void 0 ? _b : "",
76
- name: request.toolCall.name,
77
- status: "error",
78
- });
94
+ toolCallTracker.finishSuccess(result);
95
+ return result;
79
96
  }
80
97
  catch (error) {
81
- const errorDetails = error instanceof Error ? (_c = error.stack) !== null && _c !== void 0 ? _c : error.message : String(error);
98
+ const errorDetails = error instanceof Error ? (_d = error.stack) !== null && _d !== void 0 ? _d : error.message : String(error);
82
99
  logger.error(`Tool "${request.toolCall.name}" failed after ${Date.now() - startedAt}ms with input: ${toolInput}\n${errorDetails}`);
100
+ toolCallTracker.finishError(error);
83
101
  throw error;
84
102
  }
85
103
  finally {
@@ -1 +1,39 @@
1
- export {};
1
+ import { randomUUID } from "crypto";
2
+ import YAML from "yaml";
3
+ import { serializeUnknownError } from "../apiBasedTools.js";
4
+ export function createToolCallTracker(params) {
5
+ var _a, _b;
6
+ const toolCallId = (_a = params.toolCallId) !== null && _a !== void 0 ? _a : randomUUID();
7
+ const startedAt = (_b = params.startedAt) !== null && _b !== void 0 ? _b : Date.now();
8
+ return {
9
+ start() {
10
+ var _a;
11
+ params.emit({
12
+ toolCallId,
13
+ toolName: params.toolName,
14
+ phase: "start",
15
+ input: YAML.stringify((_a = params.input) !== null && _a !== void 0 ? _a : {}),
16
+ });
17
+ },
18
+ finishSuccess(output) {
19
+ params.emit({
20
+ toolCallId,
21
+ toolName: params.toolName,
22
+ phase: "end",
23
+ durationMs: Date.now() - startedAt,
24
+ output: YAML.stringify(output).trimEnd(),
25
+ error: null,
26
+ });
27
+ },
28
+ finishError(error) {
29
+ params.emit({
30
+ toolCallId,
31
+ toolName: params.toolName,
32
+ phase: "end",
33
+ durationMs: Date.now() - startedAt,
34
+ output: null,
35
+ error: YAML.stringify(serializeUnknownError(error)).trimEnd(),
36
+ });
37
+ },
38
+ };
39
+ }
@@ -8,9 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { tool } from "langchain";
11
- import { randomUUID } from "crypto";
12
- import YAML from "yaml";
13
- import { serializeUnknownError } from "../../apiBasedTools.js";
14
11
  const emptyToolSchema = {
15
12
  type: "object",
16
13
  properties: {},
@@ -50,41 +47,11 @@ export function createApiTool(toolName, apiBasedTool) {
50
47
  var _a;
51
48
  return tool((input, runtime) => __awaiter(this, void 0, void 0, function* () {
52
49
  const normalizedInput = (input !== null && input !== void 0 ? input : {});
53
- const toolCallId = randomUUID();
54
- const startedAt = Date.now();
55
- runtime.context.emitToolCallEvent({
56
- toolCallId,
57
- toolName,
58
- phase: "start",
59
- input: YAML.stringify(normalizedInput),
50
+ return apiBasedTool.call({
51
+ adminUser: runtime.context.adminUser,
52
+ inputs: normalizedInput,
53
+ userTimeZone: runtime.context.userTimeZone,
60
54
  });
61
- try {
62
- const output = yield apiBasedTool.call({
63
- adminUser: runtime.context.adminUser,
64
- inputs: normalizedInput,
65
- userTimeZone: runtime.context.userTimeZone,
66
- });
67
- runtime.context.emitToolCallEvent({
68
- toolCallId,
69
- toolName,
70
- phase: "end",
71
- durationMs: Date.now() - startedAt,
72
- output,
73
- error: null,
74
- });
75
- return output;
76
- }
77
- catch (error) {
78
- runtime.context.emitToolCallEvent({
79
- toolCallId,
80
- toolName,
81
- phase: "end",
82
- durationMs: Date.now() - startedAt,
83
- output: null,
84
- error: YAML.stringify(serializeUnknownError(error)),
85
- });
86
- throw error;
87
- }
88
55
  }), {
89
56
  name: toolName,
90
57
  description: (_a = apiBasedTool.description) !== null && _a !== void 0 ? _a : `${toolName} tool`,
@@ -17,9 +17,10 @@
17
17
 
18
18
  </div>
19
19
  <AutoScrollContainer
20
- enabled
20
+ :enabled="!showScrollToBottomButton"
21
21
  class="flex flex-col overflow-y-auto border-t border-gray-200 dark:border-gray-700"
22
22
  ref="scrollContainer"
23
+ :threshold="10"
23
24
  behavior="smooth"
24
25
  >
25
26
 
@@ -57,8 +58,8 @@
57
58
  v-if="props.messages.length === 0"
58
59
  class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
59
60
  >
60
- <p>Start the conversation</p>
61
- <p class="tracking-normal text-base text">Give any input to begin</p>
61
+ <p>{{ $t('Start the conversation') }}</p>
62
+ <p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
62
63
  </div>
63
64
  </AutoScrollContainer>
64
65
  </template>
@@ -151,20 +152,16 @@ const groupToolCallParts = (message: IMessage) => {
151
152
  if(!part?.toolInfo) {
152
153
  continue;
153
154
  }
154
- console.log('part', part);
155
155
  if (part.toolInfo.toolName === currentToolName) {
156
- console.log('grouping part with tool name', currentToolName);
157
156
  groupedParts[groupedParts.length - 1].groupedTools.push(part);
158
157
  continue;
159
158
  }
160
159
  currentToolName = part.toolInfo.toolName;
161
- console.log('starting new group with tool name', currentToolName);
162
160
  groupedParts.push({
163
161
  title: currentToolName,
164
162
  groupedTools: [part]
165
163
  });
166
164
  }
167
- console.log('groupedParts', groupedParts);
168
165
  return groupedParts;
169
166
  }
170
167
 
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div
3
- class="max-w-[80%] flex px-4 py-2 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
3
+ class="max-w-[80%] flex px-4 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
4
4
  @click="handleMarkdownLinkClick"
5
5
  :class="props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
6
6
  : isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
@@ -6,14 +6,14 @@
6
6
  "
7
7
  >
8
8
  <h3 :class="h3Style">{{ $t('Chat history') }}</h3>
9
- <Button @click="agentStore.createPreSession()" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
9
+ <Button @click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false)" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
10
10
  <IconPlusOutline class="w-5 h-5" />
11
11
  {{ $t('New chat') }}
12
12
  </Button>
13
13
  <div class="w-full border-b border-gray-200 dark:border-gray-700"/>
14
14
  <div class="absolute w-full h-full flex flex-col items-center justify-center bg-gray-100/50 dark:bg-gray-700/50 z-10" v-if="agentStore.isResponseInProgress">
15
15
  <Spinner class="w-8 h-8" v-if="agentStore.isResponseInProgress" />
16
- <p class="mt-2 text-gray-800 dark:text-gray-200">generation in progress...</p>
16
+ <p class="mt-2 text-gray-800 dark:text-gray-200">{{ $t('Generation in progress...') }}</p>
17
17
  </div>
18
18
  <div v-for="group in groupedSessions" :key="group.dayKey" class="w-full py-2">
19
19
  <div class="px-4 pb-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 dark:text-gray-400">
@@ -38,7 +38,7 @@
38
38
  v-if="!groupedSessions || groupedSessions.length === 0"
39
39
  class="w-full h-full flex items-center justify-center text-gray-800 dark:text-gray-200"
40
40
  >
41
- There is no previous chat sessions
41
+ {{ $t('There are no previous chat sessions') }}
42
42
  </p>
43
43
  </div>
44
44
  </template>
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <div v-if="props.data?.toolInfo" class="inline-flex m-2 max-w-[80%] flex-col gap-3 rounded-xl p-2 text-lightListTableHeadingText dark:text-darkListTableHeadingText">
2
+ <div
3
+ class="inline-flex m-2 max-w-[80%] flex-col gap-3 rounded-xl p-2 cursor-pointer text-lightListTableHeadingText dark:text-darkListTableHeadingText hover:opacity-75"
4
+ @click="isInputOutputExpanded = !isInputOutputExpanded"
5
+ >
3
6
  <div class="flex items-center gap-3">
4
7
  <div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/70 dark:bg-blue-700/20">
5
8
  <Spinner v-if="isRunning" class="h-4 w-4" />
@@ -18,8 +21,7 @@
18
21
  <IconAngleDownOutline
19
22
  v-if="hasToolSections"
20
23
  :class="isInputOutputExpanded ? 'rotate-180' : 'rotate-0'"
21
- class="cursor-pointer transition-transform duration-200 hover:scale-105 hover:opacity-75"
22
- @click="isInputOutputExpanded = !isInputOutputExpanded"
24
+ class="cursor-pointer transition-transform duration-200 hover:scale-105"
23
25
  />
24
26
  </div>
25
27
  <transition name="expand">
@@ -1,8 +1,9 @@
1
1
  <template>
2
2
  <template v-for="group in props.toolGroup" :key="group.title">
3
- <div v-if="group.groupedTools.length > 1" class="mb-4 flex flex-col">
4
- <div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5" @click="toggleGroup(group.title)">
5
- - {{ group.title }} {{ 'x' + group.groupedTools.length }}
3
+ <div v-if="group.groupedTools.length > 1" class="flex flex-col">
4
+ <div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5 text-lightListTableHeadingText dark:text-darkListTableHeadingText" @click="toggleGroup(group.title)">
5
+ <IconMinusOutline class="w-6 h-6 p-1"/>
6
+ {{ group.title }} {{ 'x' + group.groupedTools.length }}
6
7
  <IconAngleDownOutline
7
8
  class="transition-transform duration-200 hover:scale-105 hover:opacity-75"
8
9
  :class="expandedGroups.includes(group.title) ? 'rotate-180' : 'rotate-0'"
@@ -10,7 +11,7 @@
10
11
  </div>
11
12
  <transition name="expand">
12
13
  <div v-show="expandedGroups.includes(group.title)" class="flex flex-col">
13
- <ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" />
14
+ <ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" class="ml-8"/>
14
15
  </div>
15
16
  </transition>
16
17
  </div>
@@ -24,7 +25,7 @@ import { Tool } from 'langchain';
24
25
  import ToolRenderer from './ToolRenderer.vue';
25
26
  import type { IPart } from './types';
26
27
  import { ref } from 'vue';
27
- import { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
28
+ import { IconAngleDownOutline, IconMinusOutline } from '@iconify-prerendered/vue-flowbite';
28
29
 
29
30
  const props = defineProps<{
30
31
  toolGroup: {
@@ -120,6 +120,10 @@ export const useAgentStore = defineStore('agent', () => {
120
120
  });
121
121
  const blockCloseOfChat = ref(false);
122
122
 
123
+ function sortSessionsListByTimestamp(sessionsList: ISessionsListItem[]) {
124
+ return [...sessionsList].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
125
+ }
126
+
123
127
  async function sendMessage() {
124
128
  const message = trimmedUserMessage.value;
125
129
  if (!message || isResponseInProgress.value) {
@@ -128,6 +132,11 @@ export const useAgentStore = defineStore('agent', () => {
128
132
  if (!currentSession.value || currentSession.value.sessionId === 'pre-session') {
129
133
  await createNewSession(message);
130
134
  }
135
+ currentSession.value.timestamp = new Date().toISOString();
136
+ sessionList.value = sortSessionsListByTimestamp(sessionList.value.map((s: ISessionsListItem) => s.sessionId === currentSession.value?.sessionId ? {
137
+ ...s,
138
+ timestamp: currentSession.value?.timestamp || s.timestamp,
139
+ } : s));
131
140
  lastMessage.value = message;
132
141
  currentChat.value?.sendMessage({
133
142
  text: message,
package/dist/index.js CHANGED
@@ -71,6 +71,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
71
71
  return { ok: true };
72
72
  });
73
73
  }
74
+ updateSessionDate(sessionId) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ yield this.adminforth.resource(this.options.sessionResource.resourceId).update(sessionId, {
77
+ [this.options.sessionResource.createdAtField]: new Date().toISOString(),
78
+ });
79
+ return { ok: true };
80
+ });
81
+ }
74
82
  getSessionTurns(sessionId) {
75
83
  return __awaiter(this, void 0, void 0, function* () {
76
84
  const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField)]);
@@ -134,6 +142,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
134
142
  const userTimeZone = (_e = body.timeZone) !== null && _e !== void 0 ? _e : 'UTC';
135
143
  const sessionId = body.sessionId || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.pk) || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.username) || 'default';
136
144
  const turnId = yield this.createNewTurn(sessionId, prompt);
145
+ yield this.updateSessionDate(sessionId);
137
146
  let fullResponse = "";
138
147
  let isStreamClosed = false;
139
148
  const sequenceDebugCollector = createSequenceDebugCollector();
package/index.ts CHANGED
@@ -87,6 +87,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
87
87
  return {ok: true};
88
88
  }
89
89
 
90
+ private async updateSessionDate(sessionId: string) {
91
+ await this.adminforth.resource(this.options.sessionResource.resourceId).update(sessionId, {
92
+ [this.options.sessionResource.createdAtField]: new Date().toISOString(),
93
+ });
94
+ return {ok: true};
95
+ }
96
+
90
97
  private async getSessionTurns(sessionId: string) {
91
98
  const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
92
99
  [Filters.EQ(this.options.turnResource.sessionIdField, sessionId)],
@@ -150,6 +157,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
150
157
  const userTimeZone = (body.timeZone as string | undefined) ?? 'UTC';
151
158
  const sessionId = body.sessionId || adminUser?.pk || adminUser?.username || 'default';
152
159
  const turnId = await this.createNewTurn(sessionId, prompt);
160
+ await this.updateSessionDate(sessionId);
153
161
  let fullResponse = "";
154
162
  let isStreamClosed = false;
155
163
  const sequenceDebugCollector = createSequenceDebugCollector();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",