mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-29 19:10:43 +08:00
refactor(editor): Use timestamp from backend in agent directory (#21349)
This commit is contained in:
@@ -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>
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ async function handleDeleteSession(sessionId: string) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void Promise.all([chatStore.fetchSessions(), chatStore.fetchCustomAgents()]);
|
||||
void chatStore.fetchSessions();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user