feat: add timestamp and forking

This commit is contained in:
Zoe
2026-07-15 15:49:38 -05:00
parent 8ccaa824dd
commit 886f385835
7 changed files with 308 additions and 79 deletions
+13 -8
View File
@@ -7,14 +7,19 @@ defineProps<{
</script>
<template>
<div class="flex flex-col gap-2 max-w-full bg-[var(--bg-container)] py-2 px-3 rounded-xl">
<MarkdownRenderer v-if="message.content" :finished="true" :content="message.content" :id="message.id" />
<div v-if="message.attachments && message.attachments.length > 0" class="flex flex-col gap-2">
<div v-for="attachment in message.attachments" :key="attachment.id"
class="flex flex-wrap items-center gap-2">
<div
class="flex-shrink-0 rounded-lg overflow-hidden bg-[var(--bg-surface)] flex items-center justify-center">
<AttachmentDisplay :file="attachment.file" />
<div class="flex flex-col items-end gap-1 max-w-full">
<time class="text-[10px] text-[var(--text-secondary)] opacity-60 select-none pr-1">
{{ new Date(message.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }}
</time>
<div class="flex flex-col gap-2 max-w-full bg-[var(--bg-container)] py-2 px-3 rounded-xl">
<MarkdownRenderer v-if="message.content" :finished="true" :content="message.content" :id="message.id" />
<div v-if="message.attachments && message.attachments.length > 0" class="flex flex-col gap-2">
<div v-for="attachment in message.attachments" :key="attachment.id"
class="flex flex-wrap items-center gap-2">
<div
class="flex-shrink-0 rounded-lg overflow-hidden bg-[var(--bg-surface)] flex items-center justify-center">
<AttachmentDisplay :file="attachment.file" />
</div>
</div>
</div>
</div>
+9
View File
@@ -12,6 +12,7 @@ const emit = defineEmits<{
delete: [];
edit: [value: string];
patch: [updates: Partial<Message>];
fork: [];
}>();
let reqAbortController: AbortController | null = null;
@@ -126,6 +127,10 @@ const handleEdit = async () => {
});
};
const forkMessage = () => {
emit('fork');
};
const deleteMessage = () => {
emit('delete');
};
@@ -205,6 +210,10 @@ const navigateChild = (direction: -1 | 1) => {
class="flex justify-center items-center w-7 h-6 @hover:bg-[var(--color-hover)]">
<span :class="copied ? 'i-mynaui-check text-emerald-500' : 'i-mynaui-copy text-4.5'"></span>
</button>
<button @click="forkMessage"
class="flex justify-center items-center w-7 h-6 @hover:bg-[var(--color-hover)]">
<span class="i-mynaui-git-branch text-4.5"></span>
</button>
<Tooltip :hotkey="['ctrl', 'shift', 'backspace']">
<button @click="deleteMessage"
class="flex justify-center items-center w-7 h-6 text-red-500 @hover:bg-[var(--color-hover)]">
+32
View File
@@ -198,11 +198,43 @@ export const useAgents = async () => {
});
}
const forkTopic = async (topicId: string, messageId: string): Promise<string | null> => {
try {
const result = await $fetch<{ ok: boolean; topicId: string; name: string }>(`/api/topic/${topicId}/fork`, {
method: 'POST',
body: { messageId },
});
if (result.ok) {
const agent = agents.value.find(a => a.topics.some(t => t.id === topicId));
if (agent) {
const newTopic: Topic = {
id: result.topicId,
name: result.name,
agentId: agent.id,
userId: agent.userId,
renaming: false,
createdAt: new Date(),
};
agents.value = agents.value.map(a =>
a.id === agent.id ? { ...a, topics: [newTopic, ...a.topics] } as AgentWithTopics : a
);
}
return result.topicId;
}
return null;
} catch (error) {
console.error('Failed to fork topic:', error);
return null;
}
};
return {
agents,
refresh,
createAgent,
createTopic,
forkTopic,
getAgent,
patchAgentLocally,
patchTopicLocally,
+16 -2
View File
@@ -8,7 +8,7 @@ const rootStart = Date.now();
const chatPaneWrapper = ref<HTMLElement | null>(null);
const inputValue = ref<BaseMessage>({ content: '', fileIds: [] });
const route = useRoute();
const { getAgent, deleteTopic } = await useAgents();
const { getAgent, deleteTopic, forkTopic } = await useAgents();
const { open: sidebarOpen, openSidebar } = useSidebar();
const { providers, allModels } = await useModels();
const { addShortcut } = useKeyboardShortcuts();
@@ -114,6 +114,19 @@ const flatMessages = computed(() => {
return messages;
});
const handleFork = async (message: Message) => {
const messageId = message.activeChildId && message.children.length > 0
? message.activeChildId
: message.id;
const newTopicId = await forkTopic(topicId.value, messageId);
if (newTopicId) {
await navigateTo(`/agent/${route.params.id}/topic/${newTopicId}`);
} else {
console.error('Failed to fork topic');
}
};
const handleDelete = async (rootMessage: Message) => {
let messageId: string;
if (rootMessage.activeChildId && rootMessage.children.length > 0) {
@@ -306,7 +319,8 @@ console.log("full page render took", Date.now() - rootStart);
<Message v-for="message in topic.messages" :key="message.id" :message="message"
@edit="(value) => patchMessageLocally(message.id, { content: value })"
@patch="(updates) => patchMessageLocally(message.id, updates)"
@delete="handleDelete(message)" @regenerate="handleRegenerate(message)" />
@delete="handleDelete(message)" @regenerate="handleRegenerate(message)"
@fork="handleFork(message)" />
</template>
</Suspense>
</div>