Skip to content

Commit 54fa6d3

Browse files
committed
2 parents 3df42da + 080bcc5 commit 54fa6d3

11 files changed

Lines changed: 124 additions & 28 deletions

File tree

apiBasedTools.ts

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ type GetResourceDataToolResponse = {
9494
options?: Record<string, unknown>;
9595
};
9696

97+
type DateTimeColumnType = AdminForthDataTypes.DATETIME | AdminForthDataTypes.TIME;
98+
9799
const DEFAULT_USER_TIME_ZONE = 'UTC';
98100

99101
const TOOL_OVERRIDES: Record<string, ToolOverride> = {
@@ -373,6 +375,7 @@ async function applyToolOverride(params: {
373375
adminUser,
374376
inputs: nestedInputs,
375377
httpExtra: nestedHttpExtra,
378+
userTimeZone: nestedUserTimeZone,
376379
});
377380

378381
return applyToolOverride({
@@ -488,20 +491,104 @@ function createRawExpressResponse(response: ToolHttpResponse) {
488491
return rawResponse;
489492
}
490493

494+
function normalizeDateTimeInputsToUtc(
495+
body: Record<string, unknown>,
496+
adminforth: IAdminForth,
497+
userTimeZone?: string,
498+
): Record<string, unknown> {
499+
if (!userTimeZone || typeof body.resourceId !== 'string') {
500+
return body;
501+
}
502+
503+
const resource = adminforth.config.resources.find((res) => res.resourceId === body.resourceId);
504+
505+
if (!resource) {
506+
return body;
507+
}
508+
509+
const columnsByName = new Map(resource.dataSourceColumns.map((column) => [column.name, column]));
510+
511+
const normalizeColumnValue = (
512+
value: unknown,
513+
columnType: DateTimeColumnType,
514+
): unknown => {
515+
if (Array.isArray(value)) {
516+
return value.map((item) => normalizeColumnValue(item, columnType));
517+
}
518+
519+
if (typeof value !== 'string' || value === '') {
520+
return value;
521+
}
522+
523+
if (columnType === AdminForthDataTypes.DATETIME) {
524+
return dayjs.tz(value, userTimeZone).utc().toISOString();
525+
}
526+
527+
if (columnType === AdminForthDataTypes.TIME) {
528+
const userDate = dayjs().tz(userTimeZone).format('YYYY-MM-DD');
529+
return dayjs.tz(`${userDate}T${value}`, userTimeZone).utc().format('HH:mm:ss');
530+
}
531+
};
532+
533+
const normalizeValue = (value: unknown, key?: string): unknown => {
534+
const column = key ? columnsByName.get(key) : undefined;
535+
536+
if (column?.type === AdminForthDataTypes.DATETIME || column?.type === AdminForthDataTypes.TIME) {
537+
return normalizeColumnValue(value, column.type);
538+
}
539+
540+
if (Array.isArray(value)) {
541+
return value.map((item) => normalizeValue(item));
542+
}
543+
544+
if (!value || typeof value !== 'object') {
545+
return value;
546+
}
547+
548+
const record = value as Record<string, unknown>;
549+
const filterColumn = typeof record.field === 'string' ? columnsByName.get(record.field) : undefined;
550+
551+
if (
552+
'value' in record &&
553+
(filterColumn?.type === AdminForthDataTypes.DATETIME || filterColumn?.type === AdminForthDataTypes.TIME)
554+
) {
555+
return {
556+
...record,
557+
value: normalizeColumnValue(record.value, filterColumn.type),
558+
};
559+
}
560+
561+
return Object.fromEntries(
562+
Object.entries(record).map(([nestedKey, nestedValue]) => [
563+
nestedKey,
564+
normalizeValue(nestedValue, nestedKey),
565+
]),
566+
);
567+
};
568+
569+
return normalizeValue(body) as Record<string, unknown>;
570+
}
571+
491572
async function callCapturedEndpoint(params: {
492573
adminforth: IAdminForth;
493574
adminUser?: AdminUser;
494575
endpoint: CapturedEndpoint;
495576
httpExtra?: Partial<HttpExtra>;
496577
inputs?: Record<string, unknown>;
578+
userTimeZone?: string;
497579
}) {
498-
const { adminforth, adminUser, endpoint, httpExtra, inputs } = params;
580+
const { adminforth, adminUser, endpoint, httpExtra, inputs, userTimeZone } = params;
499581
const response = createToolResponse(httpExtra?.response);
500582
const headers = {
501583
'content-type': 'application/json',
584+
'X-TimeZone': userTimeZone,
502585
...(httpExtra?.headers ?? {}),
503586
};
504-
const body = (inputs ?? httpExtra?.body ?? {}) as Record<string, unknown>;
587+
const body = normalizeDateTimeInputsToUtc(
588+
(inputs ?? httpExtra?.body ?? {}) as Record<string, unknown>,
589+
adminforth,
590+
headers['X-TimeZone'],
591+
);
505592
const query = httpExtra?.query ?? {};
506593
const cookies = normalizeCookies(httpExtra?.cookies);
507594
const requestUrl = httpExtra?.requestUrl ?? `${adminforth.config.baseUrl}/adminapi/v1${endpoint.path}`;
@@ -596,6 +683,7 @@ export function prepareApiBasedTools(adminforth: IAdminForth): Record<string, Ap
596683
adminUser: adminUser ?? adminuser,
597684
inputs,
598685
httpExtra,
686+
userTimeZone,
599687
});
600688

601689
const processedOutput = await applyToolOverride({
@@ -629,4 +717,4 @@ export function serializeApiBasedTool(tool: ApiBasedTool | undefined) {
629717
output_schema: tool.output_schema,
630718
call: '[Function]',
631719
};
632-
}
720+
}

custom/ChatSurface.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ const props = defineProps<{
195195
defaultModeName: string | null;
196196
stickByDefault: boolean;
197197
}
198+
adminUser: any
198199
}>();
199200
200201
const chatSurface = useTemplateRef('chatSurface');

custom/SessionsHistory.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<h3 :class="h3Style">{{ $t('Chat history') }}</h3>
99
<div class="w-full flex items-center justify-center">
1010
<Button
11-
@click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false); agentStore.focusTextInput();"
11+
@click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false); agentStore.focusTextInput(); recalculateScroll();"
1212
:disabled="agentStore.isResponseInProgress"
1313
class="w-[90%] my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200"
1414
>
@@ -34,7 +34,7 @@
3434
'bg-lightPrimary/20 hover:bg-lightPrimary/20 dark:bg-darkPrimary/20 dark:hover:bg-darkPrimary/20': agentStore.activeSessionId === session.sessionId,
3535
'cursor-default opacity-50 pointer-events-none': agentStore.isResponseInProgress,
3636
}"
37-
@click="agentStore.setActiveSession(session.sessionId); agentStore.setSessionHistoryOpen(false);"
37+
@click="agentStore.setActiveSession(session.sessionId); agentStore.setSessionHistoryOpen(false); recalculateScroll();"
3838
:disabled="agentStore.isResponseInProgress"
3939
>
4040
<p class="truncate">{{ session.title || session.sessionId }}</p>
@@ -99,4 +99,13 @@ const groupedSessions = computed(() => {
9999
return Array.from(groups.values());
100100
});
101101
102+
const emit = defineEmits<{
103+
(e: 'recalculateScroll'): void
104+
}>()
105+
106+
function recalculateScroll() {
107+
// Emit an event to notify the parent component to recalculate scroll
108+
emit('recalculateScroll');
109+
}
110+
102111
</script>

custom/composables/useAgentStore.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,8 @@ export const useAgentStore = defineStore('agent', () => {
524524
if (!sessions.value[sessionId]) {
525525
await fetchSession(sessionId);
526526
}
527-
console.log('Set active session from sessions', sessionId, sessions.value[sessionId]);
528527
currentSession.value = sessions.value[sessionId];
529528
setCurrentChat(sessionId);
530-
console.log('Set active session chat', sessionId, currentSession.value);
531529
currentChat.value.messages = currentSession.value?.messages.map((m: any) => ({
532530
role: m.role,
533531
parts:[{

custom/conversation_area/ConversationArea.vue

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<SessionsHistory
1111
:class="agentStore.isSessionHistoryOpen ? 'translate-x-0' : '-translate-x-full'"
12+
@recalculateScroll="recalculateScroll"
1213
/>
1314
<div
1415
v-if="agentStore.isSessionHistoryOpen"
@@ -51,18 +52,19 @@
5152

5253

5354
<script setup lang="ts">
54-
import Message from './Message.vue';
5555
import type { IMessage, IPart } from '../types';
56-
import { useTemplateRef, ref, defineAsyncComponent, onMounted, onUnmounted, watch, computed } from 'vue';
56+
import { useTemplateRef, ref, defineAsyncComponent, onMounted, onUnmounted, watch, computed, nextTick } from 'vue';
5757
import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
5858
import SessionsHistory from '../SessionsHistory.vue';
5959
import { useAgentStore } from '../composables/useAgentStore';
60-
import ToolsGroup from './ToolsGroup.vue';
6160
import { useAgentTransitions } from '../composables/useAgentTransitions';
62-
import { getMessageParts } from '../utils';
6361
import MessageRenderer from './MessageRenderer.vue';
6462
import CustomAutoScrollContainer from '../CustomAutoScrollContainer.vue';
6563
64+
const props = defineProps<{
65+
messages: IMessage[]
66+
}>();
67+
6668
const scrollContainer = useTemplateRef('scrollContainer');
6769
const showScrollToBottomButton = ref(false);
6870
const innerScrollContainerRef = ref(null);
@@ -101,9 +103,4 @@ watch(clicks, () => {
101103
})
102104
103105
104-
105-
const props = defineProps<{
106-
messages: IMessage[]
107-
}>();
108-
109106
</script>

custom/conversation_area/ProcessingTimeline.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<template v-if="ToolOrReasoningParts.length > 0 || isResponseInProgress || showFakeThinkingMessage">
33
<div
4-
class="ml-2 px-4 flex items-center gap-1 cursor-pointer select-none hover:opacity-80 tracking-wide font-medium text-sm"
4+
class="ml-2 px-4 flex items-center gap-1 cursor-pointer select-none hover:opacity-80 tracking-wide font-medium text-sm text-listTableHeadingText dark:text-darkListTableHeadingText"
55
@click="isExpanded = !isExpanded"
66
>
77
Thoughts
@@ -20,7 +20,7 @@
2020
v-show="isExpanded"
2121
class="mask-y"
2222
>
23-
<ol class="ml-8 relative border-l border-l-2 border-black border-default">
23+
<ol class="ml-8 relative border-l border-l-2 border-black border-default border-listTableHeadingText dark:border-darkListTableHeadingText">
2424
<li class="mb-6 ms-2 z-50" v-for="(part, index) in ToolOrReasoningParts" :key="index">
2525
<ReasoningRenderer v-if="part.type === 'reasoning'" :state="part.state" :text="part.text" />
2626
<ToolsGroup v-else :toolGroup="groupToolCallParts(message, part)" />

custom/conversation_area/ReasoningRenderer.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<template>
2-
<span class="bg-lightNavbar absolute flex items-center justify-center w-5 h-5 bg-brand-softer rounded-full -start-[0.68rem] ring-4 ring-lightNavbar ring-default">
2+
<span class="bg-lightNavbar dark:bg-darkNavbar absolute flex items-center text-listTableHeadingText dark:text-darkListTableHeadingText justify-center w-5 h-5 bg-brand-softer rounded-full -start-[0.68rem] ring-4 ring-lightNavbar dark:ring-darkNavbar ring-default">
33
<div class="w-5 h-5 rounded-full flex items-center justify-center">
44
<IconBrainOutline class="w-4 h-4" />
55
</div>
66
</span>
77
<h3
8-
class="flex items-center mb-1 text-sm my-2 ml-3 gap-1 cursor-pointer select-none hover:opacity-80"
8+
class="flex items-center mb-1 text-sm my-2 ml-3 gap-1 cursor-pointer select-none hover:opacity-80 text-listTableHeadingText dark:text-darkListTableHeadingText"
99
@click="isExpanded = !isExpanded"
1010
>
1111
<span class="font-semibold">{{ reasoningTitle }}</span>

custom/conversation_area/ThreeDotsAnimation.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
2-
<span class="bounce-dot1 rounded-full w-2 h-2 bg-lightPrimary"></span>
3-
<span class="bounce-dot2 rounded-full w-2 h-2 bg-lightPrimary"></span>
4-
<span class="bounce-dot3 rounded-full w-2 h-2 bg-lightPrimary"></span>
2+
<span class="bounce-dot1 rounded-full w-2 h-2 bg-lightPrimary dark:bg-darkPrimary"></span>
3+
<span class="bounce-dot2 rounded-full w-2 h-2 bg-lightPrimary dark:bg-darkPrimary"></span>
4+
<span class="bounce-dot3 rounded-full w-2 h-2 bg-lightPrimary dark:bg-darkPrimary"></span>
55
</template>
66

77
<style scoped>

custom/conversation_area/ToolRenderer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class="flex items-center gap-1 cursor-pointer"
2020
@click="toggleInputOutput()"
2121
>
22-
<div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/70 dark:bg-blue-700/20">
22+
<div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-lightNavbar dark:bg-darkNavbar">
2323
<Spinner v-if="isRunning" class="h-4 w-4" />
2424
<IconCheckOutline v-else class="h-4 w-4 text-lightPrimary dark:text-darkPrimary" />
2525
</div>

custom/conversation_area/ToolsGroup.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<template >
22
<template v-if="toolGroup.length > 0">
3-
<span class="bg-lightNavbar absolute flex items-center justify-center w-5 h-5 bg-brand-softer rounded-full -start-[0.68rem] ring-4 ring-lightNavbar ring-default">
3+
<span class="text-listTableHeadingText dark:text-darkListTableHeadingText bg-lightNavbar dark:bg-darkNavbar absolute flex items-center justify-center w-5 h-5 bg-brand-softer rounded-full -start-[0.68rem] ring-4 ring-lightNavbar dark:ring-darkNavbar ring-default">
44
<div class="w-5 h-5 rounded-full flex items-center justify-center">
55
<IconWrenchSolid class="w-4 h-4" />
66
</div>
77
</span>
88
<h3
9-
class="flex items-center mb-1 text-sm my-2 ml-3 gap-1"
9+
class="flex items-center mb-1 text-sm my-2 ml-3 gap-1 text-listTableHeadingText dark:text-darkListTableHeadingText"
1010
>
1111
<span class="font-semibold select-none ">Call tools</span>
1212
</h3>

0 commit comments

Comments
 (0)