refactor(editor): Use timestamp from backend in agent directory (#21349)

This commit is contained in:
Suguru Inoue
2025-10-30 10:59:17 +01:00
committed by GitHub
parent b63a64a424
commit cb7d9bf00c
8 changed files with 36 additions and 82 deletions
+2
View File
@@ -85,6 +85,8 @@ export interface ChatModelDto {
model: ChatHubConversationModel;
name: string;
description: string | null;
updatedAt: string | null;
createdAt: string | null;
}
/**
@@ -29,6 +29,8 @@ export class ChatHubAgentService {
provider: 'custom-agent',
agentId: agent.id,
},
createdAt: agent.createdAt.toISOString(),
updatedAt: agent.updatedAt.toISOString(),
})),
};
}
@@ -192,12 +192,14 @@ export class ChatHubService {
return {
models: resourceLocatorResults.results.map((result) => ({
name: String(result.value),
description: null,
name: result.name,
description: result.description ?? null,
model: {
provider: 'openai',
model: String(result.value),
},
createdAt: null,
updatedAt: null,
})),
};
}
@@ -217,12 +219,14 @@ export class ChatHubService {
return {
models: resourceLocatorResults.results.map((result) => ({
name: String(result.value),
description: null,
name: result.name,
description: result.description ?? null,
model: {
provider: 'anthropic',
model: String(result.value),
},
createdAt: null,
updatedAt: null,
})),
};
}
@@ -281,11 +285,13 @@ export class ChatHubService {
return {
models: results.map((result) => ({
name: String(result.value),
description: null,
description: result.description ?? null,
model: {
provider: 'google',
model: String(result.value),
},
createdAt: null,
updatedAt: null,
})),
};
}
@@ -331,6 +337,8 @@ export class ChatHubService {
provider: 'n8n',
workflowId: workflow.id,
},
createdAt: workflow.createdAt ? workflow.createdAt.toISOString() : null,
updatedAt: workflow.updatedAt ? workflow.updatedAt.toISOString() : null,
},
];
}),
@@ -12,9 +12,8 @@ import {
N8nSelect,
N8nText,
} from '@n8n/design-system';
import { computed, onMounted, ref, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import AgentEditorModal from '@/features/ai/chatHub/components/AgentEditorModal.vue';
import ChatAgentCard from '@/features/ai/chatHub/components/ChatAgentCard.vue';
import { useChatCredentials } from '@/features/ai/chatHub/composables/useChatCredentials';
@@ -28,7 +27,6 @@ import { MOBILE_MEDIA_QUERY } from '@/features/ai/chatHub/constants';
const chatStore = useChatStore();
const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
const toast = useToast();
const message = useMessage();
const usersStore = useUsersStore();
@@ -45,19 +43,12 @@ const agentFilter = ref<ChatAgentFilter>({
const { credentialsByProvider } = useChatCredentials(usersStore.currentUserId ?? 'anonymous');
const readyToShowList = computed(() => chatStore.customAgentsReady && chatStore.agentsReady);
const readyToShowList = computed(() => chatStore.agentsReady);
const allModels = computed(() =>
chatStore.agents.n8n.models.concat(chatStore.agents['custom-agent'].models),
);
const agents = computed(() => {
return filterAndSortAgents(
allModels.value,
agentFilter.value,
chatStore.customAgents,
workflowsStore.workflowsById, // TODO: ensure workflows are fetched
);
});
const agents = computed(() => filterAndSortAgents(allModels.value, agentFilter.value));
const providerOptions = [
{ label: 'All', value: '' },
@@ -95,7 +86,6 @@ function handleCloseAgentEditor() {
}
async function handleAgentCreatedOrUpdated() {
await chatStore.fetchCustomAgents();
editingAgentId.value = undefined;
}
@@ -130,10 +120,6 @@ watch(
},
{ immediate: true },
);
onMounted(() => {
void chatStore.fetchCustomAgents();
});
</script>
<template>
@@ -194,8 +180,6 @@ onMounted(() => {
v-for="agent in agents"
:key="stringifyModel(agent.model)"
:agent="agent"
:agents="chatStore.customAgents"
:workflows-by-id="workflowsStore.workflowsById"
@edit="handleEditAgent(agent.model)"
@delete="
agent.model.provider === 'custom-agent'
@@ -13,7 +13,6 @@ import {
deleteConversationApi,
stopGenerationApi,
fetchSingleConversationApi,
fetchAgentsApi,
fetchAgentApi,
createAgentApi,
updateAgentApi,
@@ -44,7 +43,6 @@ export const useChatStore = defineStore(CHAT_STORE, () => {
const rootStore = useRootStore();
const agents = ref<ChatModelsResponse>();
const sessions = ref<ChatHubSessionDto[]>();
const customAgents = ref<ChatHubAgentDto[]>();
const currentEditingAgent = ref<ChatHubAgentDto | null>(null);
const conversationsBySession = ref<Map<ChatSessionId, ChatConversation>>(new Map());
@@ -593,10 +591,6 @@ export const useChatStore = defineStore(CHAT_STORE, () => {
conversation.activeMessageChain = computeActiveChain(conversation.messages, messageId);
}
async function fetchCustomAgents() {
customAgents.value = await fetchAgentsApi(rootStore.restApiContext);
}
async function fetchCustomAgent(agentId: string): Promise<ChatHubAgentDto> {
const agent = await fetchAgentApi(rootStore.restApiContext, agentId);
currentEditingAgent.value = agent;
@@ -614,7 +608,6 @@ export const useChatStore = defineStore(CHAT_STORE, () => {
credentials: CredentialsMap,
): Promise<ChatModelDto> {
const agent = await createAgentApi(rootStore.restApiContext, payload);
customAgents.value = [...(customAgents.value ?? []), agent];
const agentModel = {
model: {
provider: 'custom-agent' as const,
@@ -622,6 +615,8 @@ export const useChatStore = defineStore(CHAT_STORE, () => {
},
name: agent.name,
description: agent.description ?? null,
createdAt: agent.createdAt,
updatedAt: agent.updatedAt,
};
agents.value?.['custom-agent'].models.push(agentModel);
@@ -636,7 +631,6 @@ export const useChatStore = defineStore(CHAT_STORE, () => {
credentials: CredentialsMap,
): Promise<ChatHubAgentDto> {
const agent = await updateAgentApi(rootStore.restApiContext, agentId, payload);
customAgents.value = customAgents.value?.map((a) => (a.id === agentId ? agent : a));
// Update the agent in models as well
if (agents.value?.['custom-agent']) {
@@ -652,7 +646,6 @@ export const useChatStore = defineStore(CHAT_STORE, () => {
async function deleteCustomAgent(agentId: string, credentials: CredentialsMap) {
await deleteAgentApi(rootStore.restApiContext, agentId);
customAgents.value = customAgents.value?.filter((a) => a.id !== agentId);
// Remove the agent from models as well
if (agents.value?.['custom-agent']) {
@@ -684,13 +677,10 @@ export const useChatStore = defineStore(CHAT_STORE, () => {
*/
agents: computed(() => agents.value ?? emptyChatModelsResponse),
agentsReady: computed(() => agents.value !== undefined),
customAgents: computed(() => customAgents.value ?? []),
customAgentsReady: computed(() => customAgents.value !== undefined),
currentEditingAgent,
getAgent,
fetchAgents,
getCustomAgent,
fetchCustomAgents,
fetchCustomAgent,
createCustomAgent,
updateCustomAgent,
@@ -3,12 +3,10 @@ import {
type ChatHubConversationModel,
type ChatModelsResponse,
type ChatHubSessionDto,
type ChatHubAgentDto,
type ChatModelDto,
} from '@n8n/api-types';
import type { ChatMessage, GroupedConversations, ChatAgentFilter } from './chat.types';
import { CHAT_VIEW } from './constants';
import type { IWorkflowDb } from '@/Interface';
export function findOneFromModelsResponse(response: ChatModelsResponse): ChatModelDto | undefined {
for (const provider of chatHubProviderSchema.options) {
@@ -139,34 +137,9 @@ export function restoreConversationModelFromMessageOrSession(
}
}
export function getTimestamp(
model: ChatHubConversationModel,
type: 'createdAt' | 'updatedAt',
agents: ChatHubAgentDto[],
workflowsById: Partial<Record<string, IWorkflowDb>>,
): number | null {
if (model.provider === 'custom-agent') {
const agent = agents.find((a) => model.provider === 'custom-agent' && a.id === model.agentId);
return agent?.[type] ? Date.parse(agent[type]) : null;
}
if (model.provider === 'n8n') {
const workflow = workflowsById[model.workflowId];
return workflow?.[type]
? typeof workflow[type] === 'string'
? Date.parse(workflow[type])
: workflow[type]
: null;
}
return null;
}
export function filterAndSortAgents(
models: ChatModelDto[],
filter: ChatAgentFilter,
agents: ChatHubAgentDto[],
workflowsById: Partial<Record<string, IWorkflowDb>>,
): ChatModelDto[] {
let filtered = models;
@@ -183,8 +156,10 @@ export function filterAndSortAgents(
// Apply sorting
filtered = [...filtered].sort((a, b) => {
const dateA = getTimestamp(a.model, filter.sortBy, agents, workflowsById);
const dateB = getTimestamp(b.model, filter.sortBy, agents, workflowsById);
const dateAStr = a[filter.sortBy];
const dateBStr = b[filter.sortBy];
const dateA = dateAStr ? Date.parse(dateAStr) : undefined;
const dateB = dateBStr ? Date.parse(dateBStr) : undefined;
// Sort by dates (newest first)
if (dateA && dateB) {
@@ -1,26 +1,19 @@
<script setup lang="ts">
import { getAgentRoute, getTimestamp } from '@/features/ai/chatHub/chat.utils';
import ChatAgentAvatar from '@/features/ai/chatHub/components/ChatAgentAvatar.vue';
import type { ChatHubAgentDto, ChatModelDto } from '@n8n/api-types';
import type { IWorkflowDb } from '@/Interface';
import { N8nIconButton, N8nText } from '@n8n/design-system';
import TimeAgo from '@/components/TimeAgo.vue';
import { computed } from 'vue';
import { getAgentRoute } from '@/features/ai/chatHub/chat.utils';
import ChatAgentAvatar from '@/features/ai/chatHub/components/ChatAgentAvatar.vue';
import type { ChatModelDto } from '@n8n/api-types';
import { N8nIconButton, N8nText } from '@n8n/design-system';
import { RouterLink } from 'vue-router';
const { agent, agents, workflowsById } = defineProps<{
const { agent } = defineProps<{
agent: ChatModelDto;
agents: ChatHubAgentDto[];
workflowsById: Partial<Record<string, IWorkflowDb>>;
}>();
const emit = defineEmits<{
edit: [];
delete: [];
}>();
const updatedAt = computed(() => getTimestamp(agent.model, 'updatedAt', agents, workflowsById));
const createdAt = computed(() => getTimestamp(agent.model, 'createdAt', agents, workflowsById));
</script>
<template>
@@ -38,11 +31,11 @@ const createdAt = computed(() => getTimestamp(agent.model, 'createdAt', agents,
<N8nText size="small" color="text-light">
{{ agent.model.provider === 'n8n' ? 'n8n workflow' : 'Custom agent' }}
</N8nText>
<N8nText v-if="updatedAt" size="small" color="text-light">
Last updated <TimeAgo :date="String(updatedAt)" />
<N8nText v-if="agent.updatedAt" size="small" color="text-light">
Last updated <TimeAgo :date="agent.updatedAt" />
</N8nText>
<N8nText v-if="createdAt" size="small" color="text-light">
Created <TimeAgo :date="String(createdAt)" />
<N8nText v-if="agent.createdAt" size="small" color="text-light">
Created <TimeAgo :date="agent.createdAt" />
</N8nText>
</div>
</div>
@@ -78,7 +78,7 @@ async function handleDeleteSession(sessionId: string) {
}
onMounted(() => {
void Promise.all([chatStore.fetchSessions(), chatStore.fetchCustomAgents()]);
void chatStore.fetchSessions();
});
</script>