refactor: replace focusedIndex with activeChildId
- Replace focusedIndex integer with activeChildId string in messages schema - Add migration to convert existing data and drop focused_index column - Add hooks for providers.updatedAt and messageParts.lastUpdatedAt - Add file size column to files table - Update message navigation to use child ID-based lookup - Simplify delete logic by removing focusedIndex math - Fix file delete endpoint filename ([id[ -> [id])
This commit is contained in:
@@ -8,7 +8,7 @@ defineProps<{
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2 max-w-full bg-[var(--bg-container)] py-2 px-3 rounded-xl">
|
<div class="flex flex-col gap-2 max-w-full bg-[var(--bg-container)] py-2 px-3 rounded-xl">
|
||||||
<MarkdownRenderer :finished="true" :content="message.content!" :id="message.id" />
|
<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-if="message.attachments && message.attachments.length > 0" class="flex flex-col gap-2">
|
||||||
<div v-for="attachment in message.attachments" :key="attachment.id"
|
<div v-for="attachment in message.attachments" :key="attachment.id"
|
||||||
class="flex flex-wrap items-center gap-2">
|
class="flex flex-wrap items-center gap-2">
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
let reqAbortController: AbortController | null = null;
|
let reqAbortController: AbortController | null = null;
|
||||||
|
|
||||||
watch(() => message.focusedIndex, async (newValue) => {
|
watch(() => message.activeChildId, async (newValue) => {
|
||||||
if (newValue !== undefined) {
|
|
||||||
if (reqAbortController) {
|
if (reqAbortController) {
|
||||||
reqAbortController.abort();
|
reqAbortController.abort();
|
||||||
reqAbortController = null;
|
reqAbortController = null;
|
||||||
@@ -28,54 +27,45 @@ watch(() => message.focusedIndex, async (newValue) => {
|
|||||||
await $fetch(`/api/messages/${message.id}`, {
|
await $fetch(`/api/messages/${message.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: {
|
body: {
|
||||||
focusedIndex: newValue
|
activeChildId: newValue
|
||||||
},
|
},
|
||||||
signal: reqAbortController.signal,
|
signal: reqAbortController.signal,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => message.children.length, (newCount, oldCount) => {
|
watch(() => message.children.length, (newCount, oldCount) => {
|
||||||
if (newCount === oldCount) return;
|
if (newCount === oldCount) return;
|
||||||
|
|
||||||
// if we are deleting children, only move the focus index if its no longer valid
|
if (newCount === 0) {
|
||||||
// e.g. if we we have 4 children, and are focused on the 2nd, if we delete it,
|
emit('patch', { activeChildId: null });
|
||||||
// we want to keep the focus on the 2nd index. It just makes me feel better
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (oldCount > newCount) {
|
if (oldCount > newCount) {
|
||||||
if (message.deleted === true && (message.focusedIndex || 0) === newCount) {
|
const activeChild = message.children.find(c => c.id === message.activeChildId);
|
||||||
console.log('focusedIndex Math.max(0, newCount - 1)');
|
if (!activeChild) {
|
||||||
message.focusedIndex = Math.max(0, newCount - 1);
|
emit('patch', { activeChildId: message.children[newCount - 1]?.id ?? null });
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((message.focusedIndex || 0) > newCount) {
|
const newChild = message.children[newCount - 1];
|
||||||
console.log('focusedIndex newCount');
|
if (newChild) {
|
||||||
message.focusedIndex = newCount;
|
emit('patch', { activeChildId: newChild.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.deleted === true) {
|
|
||||||
console.log('focusedIndex newCount - 1');
|
|
||||||
message.focusedIndex = newCount - 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('focusedIndex newCount');
|
|
||||||
message.focusedIndex = newCount;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeMessage = computed(() => {
|
const activeMessage = computed(() => {
|
||||||
if (message.children.length === 0 || ((message.focusedIndex || 0) === 0 && !message.deleted)) {
|
if (message.activeChildId && message.children.length > 0) {
|
||||||
|
const child = message.children.find(c => c.id === message.activeChildId);
|
||||||
|
if (child) return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.deleted === true && message.children.length > 0) {
|
||||||
|
return message.children[0];
|
||||||
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
|
||||||
|
|
||||||
if (message.deleted === true) {
|
|
||||||
return message.children[Math.min((message.focusedIndex || 0), message.children.length - 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return message.children[message.focusedIndex! - 1];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const copied = ref(false);
|
const copied = ref(false);
|
||||||
@@ -137,10 +127,6 @@ const handleEdit = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteMessage = () => {
|
const deleteMessage = () => {
|
||||||
// if (focusedIndex.value !== 0 && focusedIndex.value === message.children.length) {
|
|
||||||
// focusedIndex.value = Math.max(0, focusedIndex.value - 1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
emit('delete');
|
emit('delete');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,6 +137,27 @@ const messageCount = computed(() => {
|
|||||||
|
|
||||||
return message.children.length + 1;
|
return message.children.length + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currentChildIndex = computed(() => {
|
||||||
|
if (!message.activeChildId) return 0;
|
||||||
|
const idx = message.children.findIndex(c => c.id === message.activeChildId);
|
||||||
|
return idx >= 0 ? idx + 1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const navigateChild = (direction: -1 | 1) => {
|
||||||
|
const allItems = message.deleted === true
|
||||||
|
? message.children
|
||||||
|
: [null, ...message.children];
|
||||||
|
|
||||||
|
const currentIdx = allItems.findIndex(item =>
|
||||||
|
item === null ? !message.activeChildId : item.id === message.activeChildId
|
||||||
|
);
|
||||||
|
|
||||||
|
const newIdx = Math.max(0, Math.min(allItems.length - 1, currentIdx + direction));
|
||||||
|
const newItem = allItems[newIdx];
|
||||||
|
|
||||||
|
emit('patch', { activeChildId: newItem?.id ?? null });
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -162,21 +169,21 @@ const messageCount = computed(() => {
|
|||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<div v-if="messageCount > 1" class="flex gap-1">
|
<div v-if="messageCount > 1" class="flex gap-1">
|
||||||
<Tooltip :inert="message.focusedIndex === 0" :hotkey="['alt', '[']">
|
<Tooltip :inert="currentChildIndex === 0" :hotkey="['alt', '[']">
|
||||||
<button @click="emit('patch', { focusedIndex: message.focusedIndex! - 1 })"
|
<button @click="navigateChild(-1)"
|
||||||
class="@hover:bg-[var(--color-hover)] rounded transition duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
class="@hover:bg-[var(--color-hover)] rounded transition duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
||||||
<span class="i-mynaui-chevron-left w-4 h-4 text-[var(--text-secondary)]"
|
<span class="i-mynaui-chevron-left w-4 h-4 text-[var(--text-secondary)]"
|
||||||
:class="message.focusedIndex === 0 ? 'opacity-0' : ''"></span>
|
:class="currentChildIndex === 0 ? 'opacity-0' : ''"></span>
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span class="text-xs text-[var(--text-secondary)]">
|
<span class="text-xs text-[var(--text-secondary)]">
|
||||||
{{ (message.focusedIndex || 0) + 1 }} / {{ messageCount }}
|
{{ currentChildIndex + 1 }} / {{ messageCount }}
|
||||||
</span>
|
</span>
|
||||||
<Tooltip :inert="(message.focusedIndex || 0) + 1 === messageCount" :hotkey="['alt', ']']">
|
<Tooltip :inert="currentChildIndex + 1 === messageCount" :hotkey="['alt', ']']">
|
||||||
<button @click="emit('patch', { focusedIndex: (message.focusedIndex || 0) + 1 })"
|
<button @click="navigateChild(1)"
|
||||||
class="@hover:bg-[var(--color-hover)] rounded transition duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
class="@hover:bg-[var(--color-hover)] rounded transition duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
||||||
<span class="i-mynaui-chevron-right w-4 h-4 text-[var(--text-secondary)]"
|
<span class="i-mynaui-chevron-right w-4 h-4 text-[var(--text-secondary)]"
|
||||||
:class="(message.focusedIndex || 0) + 1 === messageCount ? 'opacity-0' : ''"></span>
|
:class="currentChildIndex + 1 === messageCount ? 'opacity-0' : ''"></span>
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
|
|
||||||
let sse: EventSource | undefined;
|
let sse: EventSource | undefined;
|
||||||
|
|
||||||
const textDeltaBuffer: Map<string, Map<string, string>> = new Map();
|
const textDeltaBuffer: Map<string, Map<string, {
|
||||||
|
content: string;
|
||||||
|
lastUpdatedAt: string;
|
||||||
|
}>> = new Map();
|
||||||
let flushTimeout: ReturnType<typeof setTimeout> | undefined;
|
let flushTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||||
let flushCbs: Set<() => void> = new Set();
|
let flushCbs: Set<() => void> = new Set();
|
||||||
|
|
||||||
@@ -54,7 +57,8 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
for (const [partId, content] of parts) {
|
for (const [partId, content] of parts) {
|
||||||
const part = msg.parts?.find(p => p.id === partId);
|
const part = msg.parts?.find(p => p.id === partId);
|
||||||
if (part) {
|
if (part) {
|
||||||
part.content += content;
|
part.content += content.content;
|
||||||
|
part.lastUpdatedAt = new Date(content.lastUpdatedAt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,8 +136,8 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
textDeltaBuffer.set(payload.messageId, new Map());
|
textDeltaBuffer.set(payload.messageId, new Map());
|
||||||
}
|
}
|
||||||
const messageBuffer = textDeltaBuffer.get(payload.messageId)!;
|
const messageBuffer = textDeltaBuffer.get(payload.messageId)!;
|
||||||
const existing = messageBuffer.get(payload.partId) || '';
|
const existing = messageBuffer.get(payload.partId)?.content || '';
|
||||||
messageBuffer.set(payload.partId, existing + payload.content);
|
messageBuffer.set(payload.partId, { content: existing + payload.content, lastUpdatedAt: payload.lastUpdatedAt });
|
||||||
|
|
||||||
scheduleFlush();
|
scheduleFlush();
|
||||||
break;
|
break;
|
||||||
@@ -145,6 +149,7 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
if (part) {
|
if (part) {
|
||||||
nextFlush(() => {
|
nextFlush(() => {
|
||||||
part.content = payload.content;
|
part.content = payload.content;
|
||||||
|
part.lastUpdatedAt = payload.lastUpdatedAt;
|
||||||
part.finished = true;
|
part.finished = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -328,7 +333,7 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
generation: null,
|
generation: null,
|
||||||
parentMessageId: null,
|
parentMessageId: null,
|
||||||
generationId: null,
|
generationId: null,
|
||||||
focusedIndex: null,
|
activeChildId: null,
|
||||||
deleted: null,
|
deleted: null,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
@@ -457,11 +462,46 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteMessagesFocusedDescendant = async (messageId: string): Promise<Result<void, Error>> => {
|
||||||
|
if (!data.value) return Err(new Error('No data'));
|
||||||
|
|
||||||
|
const msg = data.value.messages.find(m => m.id === messageId);
|
||||||
|
if (!msg) return Err(new Error('Message not found'));
|
||||||
|
|
||||||
|
const previousMessages = data.value!.messages;
|
||||||
|
|
||||||
|
const res = await $fetch(`/api/messages/${messageId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
onRequest() {
|
||||||
|
if (!msg.activeChildId && (msg.parentMessageId === null || msg.parentMessageId === undefined)) {
|
||||||
|
msg.deleted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = data.value!.messages.find(m => m.id === msg.parentMessageId);
|
||||||
|
data.value!.messages = data.value!.messages.filter(m => m.id !== msg.id);
|
||||||
|
if (parent?.deleted) {
|
||||||
|
data.value!.messages = data.value!.messages.filter(m => m.id !== parent.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onResponseError() {
|
||||||
|
data.value!.messages = previousMessages;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return Err(new Error('Failed to delete message'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
topic,
|
topic,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
startGeneration,
|
startGeneration,
|
||||||
regenerateMessage,
|
regenerateMessage,
|
||||||
patchMessageLocally
|
patchMessageLocally,
|
||||||
|
deleteMessagesFocusedDescendant
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ const handleSubmit = async (message: BaseMessage, model: ModelWithProvider | nul
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log("handleSubmit", message);
|
console.log("handleSubmit", message);
|
||||||
if (!message.content) return;
|
|
||||||
|
|
||||||
pendingMessage.value = {
|
pendingMessage.value = {
|
||||||
id: '',
|
id: '',
|
||||||
@@ -43,7 +42,7 @@ const handleSubmit = async (message: BaseMessage, model: ModelWithProvider | nul
|
|||||||
parentMessageId: null,
|
parentMessageId: null,
|
||||||
children: [],
|
children: [],
|
||||||
generationId: null,
|
generationId: null,
|
||||||
focusedIndex: null,
|
activeChildId: null,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
deleted: false,
|
deleted: false,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const { addShortcut } = useKeyboardShortcuts();
|
|||||||
const agent = getAgent(route.params.id as string);
|
const agent = getAgent(route.params.id as string);
|
||||||
|
|
||||||
const topicId = computed(() => route.params.topicId as string);
|
const topicId = computed(() => route.params.topicId as string);
|
||||||
const { topic, sendMessage, startGeneration, regenerateMessage, patchMessageLocally } = await useChat(topicId);
|
const { topic, sendMessage, startGeneration, regenerateMessage, patchMessageLocally, deleteMessagesFocusedDescendant } = await useChat(topicId);
|
||||||
|
|
||||||
watch(() => topic.value?.name, (newTopicName) => {
|
watch(() => topic.value?.name, (newTopicName) => {
|
||||||
if (newTopicName !== undefined) {
|
if (newTopicName !== undefined) {
|
||||||
@@ -61,12 +61,8 @@ const handleRegenerate = async (message: Message) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let messageId: string;
|
let messageId: string;
|
||||||
if (
|
if (message.activeChildId && message.children.length > 0) {
|
||||||
(message.focusedIndex !== undefined && message.focusedIndex !== null)
|
messageId = message.activeChildId;
|
||||||
&& message.focusedIndex > 0
|
|
||||||
&& message.children.length > 0
|
|
||||||
) {
|
|
||||||
messageId = message.children[message.focusedIndex - 1]!.id;
|
|
||||||
} else {
|
} else {
|
||||||
messageId = message.id;
|
messageId = message.id;
|
||||||
}
|
}
|
||||||
@@ -98,9 +94,18 @@ const flatMessages = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = async (rootMessage: Message) => {
|
const handleDelete = async (rootMessage: Message) => {
|
||||||
await $fetch(`/api/messages/${rootMessage.id}`, {
|
let messageId: string;
|
||||||
method: 'DELETE',
|
if (rootMessage.activeChildId && rootMessage.children.length > 0) {
|
||||||
});
|
messageId = rootMessage.activeChildId;
|
||||||
|
} else {
|
||||||
|
messageId = rootMessage.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await deleteMessagesFocusedDescendant(messageId);
|
||||||
|
if (!result.ok) {
|
||||||
|
console.error('Failed to delete message:', result.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (flatMessages.value.length === 0) {
|
if (flatMessages.value.length === 0) {
|
||||||
await navigateTo(`/agent/${route.params.id}`);
|
await navigateTo(`/agent/${route.params.id}`);
|
||||||
@@ -108,6 +113,20 @@ const handleDelete = async (rootMessage: Message) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
const el = chatPaneWrapper.value;
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
const atBottom = (el.scrollHeight - el.scrollTop - el.clientHeight) <= 80;
|
||||||
|
if (!atBottom) return;
|
||||||
|
|
||||||
|
nextTick().then(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
scrollToBottom('instant');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const activeGeneration = computed(() => {
|
const activeGeneration = computed(() => {
|
||||||
if (topic.value === null) return null;
|
if (topic.value === null) return null;
|
||||||
const generations = flatMessages.value.flatMap(message => message.generation);
|
const generations = flatMessages.value.flatMap(message => message.generation);
|
||||||
@@ -155,8 +174,11 @@ addShortcut(['alt', '['], async (event) => {
|
|||||||
|
|
||||||
const lastMessage = topic.value?.messages?.at(-1);
|
const lastMessage = topic.value?.messages?.at(-1);
|
||||||
if (lastMessage && lastMessage.children.length > 0) {
|
if (lastMessage && lastMessage.children.length > 0) {
|
||||||
const newIndex = Math.max(0, (lastMessage.focusedIndex || 0) - 1);
|
const currentIdx = lastMessage.children.findIndex(c => c?.id === lastMessage.activeChildId);
|
||||||
patchMessageLocally(lastMessage.id, { focusedIndex: newIndex });
|
const prevIdx = Math.max(0, currentIdx);
|
||||||
|
if (currentIdx > 0) {
|
||||||
|
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[prevIdx - 1]!.id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -165,10 +187,20 @@ addShortcut(['alt', ']'], async (event) => {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const lastMessage = topic.value?.messages?.at(-1);
|
const lastMessage = topic.value?.messages?.at(-1);
|
||||||
const messageCount = lastMessage ? (lastMessage.deleted ? lastMessage.children.length : lastMessage.children.length + 1) : 0;
|
|
||||||
if (lastMessage && lastMessage.children.length > 0) {
|
if (lastMessage && lastMessage.children.length > 0) {
|
||||||
const newIndex = Math.min(messageCount - 1, (lastMessage.focusedIndex || 0) + 1);
|
const currentIdx = lastMessage.children.findIndex(c => c?.id === lastMessage.activeChildId);
|
||||||
patchMessageLocally(lastMessage.id, { focusedIndex: newIndex });
|
const maxIdx = lastMessage.deleted ? lastMessage.children.length - 1 : lastMessage.children.length;
|
||||||
|
const nextIdx = Math.min(maxIdx, currentIdx + 1);
|
||||||
|
|
||||||
|
if (lastMessage.deleted) {
|
||||||
|
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[nextIdx]?.id ?? null });
|
||||||
|
} else {
|
||||||
|
if (nextIdx === 0) {
|
||||||
|
patchMessageLocally(lastMessage.id, { activeChildId: null });
|
||||||
|
} else {
|
||||||
|
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[nextIdx - 1]?.id ?? null });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -178,20 +210,32 @@ addShortcut(['ctrl', 'alt', 'arrowleft'], async (event) => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const newIndex = Math.max(0, (lastMessage.focusedIndex || 0) - 1);
|
const currentIdx = lastMessage.children.findIndex(c => c?.id === lastMessage.activeChildId);
|
||||||
patchMessageLocally(lastMessage.id, { focusedIndex: newIndex });
|
if (currentIdx > 0) {
|
||||||
|
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[currentIdx - 1]!.id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
addShortcut(['ctrl', 'alt', 'arrowright'], async (event) => {
|
addShortcut(['ctrl', 'alt', 'arrowright'], async (event) => {
|
||||||
const lastMessage = topic.value?.messages?.at(-1);
|
const lastMessage = topic.value?.messages?.at(-1);
|
||||||
const messageCount = lastMessage ? (lastMessage.deleted ? lastMessage.children.length : lastMessage.children.length + 1) : 0;
|
|
||||||
if (lastMessage && lastMessage.children.length > 0) {
|
if (lastMessage && lastMessage.children.length > 0) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const newIndex = Math.min(messageCount - 1, (lastMessage.focusedIndex || 0) + 1);
|
const currentIdx = lastMessage.children.findIndex(c => c?.id === lastMessage.activeChildId);
|
||||||
patchMessageLocally(lastMessage.id, { focusedIndex: newIndex });
|
const maxIdx = lastMessage.deleted ? lastMessage.children.length - 1 : lastMessage.children.length;
|
||||||
|
const nextIdx = Math.min(maxIdx, currentIdx + 1);
|
||||||
|
|
||||||
|
if (lastMessage.deleted) {
|
||||||
|
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[nextIdx]?.id ?? null });
|
||||||
|
} else {
|
||||||
|
if (nextIdx === 0) {
|
||||||
|
patchMessageLocally(lastMessage.id, { activeChildId: null });
|
||||||
|
} else {
|
||||||
|
patchMessageLocally(lastMessage.id, { activeChildId: lastMessage.children[nextIdx - 1]?.id ?? null });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -249,7 +293,8 @@ console.log("full page render took", Date.now() - rootStart);
|
|||||||
<div class="sticky bottom-0 z-10 bg-[var(--bg-surface)] pb-4 w-full rounded-t-2xl">
|
<div class="sticky bottom-0 z-10 bg-[var(--bg-surface)] pb-4 w-full rounded-t-2xl">
|
||||||
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
|
<ChatInput v-model="inputValue" class="[view-transition-name:chat-prompt] duration-150 ease-in-out"
|
||||||
:loading="activeGeneration !== null" :agent="agent"
|
:loading="activeGeneration !== null" :agent="agent"
|
||||||
:providers="providers?.filter(p => p.enabled)" @submit="submitMessage" @cancel="handleCancel" />
|
:providers="providers?.filter(p => p.enabled)" @submit="submitMessage" @cancel="handleCancel"
|
||||||
|
@resize="handleResize" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
ALTER TABLE "agents" ADD COLUMN "config" jsonb DEFAULT '{}';--> statement-breakpoint
|
|
||||||
ALTER TABLE "accounts" DROP CONSTRAINT "accounts_user_id_users_id_fkey", ADD CONSTRAINT "accounts_user_id_users_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "agents" DROP CONSTRAINT "agents_default_model_id_models_id_fkey", ADD CONSTRAINT "agents_default_model_id_models_id_fkey" FOREIGN KEY ("default_model_id") REFERENCES "models"("id") ON DELETE SET NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "attachments" DROP CONSTRAINT "attachments_file_id_files_id_fkey", ADD CONSTRAINT "attachments_file_id_files_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "attachments" DROP CONSTRAINT "attachments_message_id_messages_id_fkey", ADD CONSTRAINT "attachments_message_id_messages_id_fkey" FOREIGN KEY ("message_id") REFERENCES "messages"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "attachments" DROP CONSTRAINT "attachments_topic_id_topics_id_fkey", ADD CONSTRAINT "attachments_topic_id_topics_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "topics"("id");--> statement-breakpoint
|
|
||||||
ALTER TABLE "embeddings" DROP CONSTRAINT "embeddings_file_id_files_id_fkey", ADD CONSTRAINT "embeddings_file_id_files_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "generations" DROP CONSTRAINT "generations_topic_id_topics_id_fkey", ADD CONSTRAINT "generations_topic_id_topics_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "topics"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "message_parts" DROP CONSTRAINT "message_parts_message_id_messages_id_fkey", ADD CONSTRAINT "message_parts_message_id_messages_id_fkey" FOREIGN KEY ("message_id") REFERENCES "messages"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "message_parts" DROP CONSTRAINT "message_parts_tool_call_id_tool_calls_id_fkey", ADD CONSTRAINT "message_parts_tool_call_id_tool_calls_id_fkey" FOREIGN KEY ("tool_call_id") REFERENCES "tool_calls"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "message_parts" DROP CONSTRAINT "message_parts_topic_id_topics_id_fkey", ADD CONSTRAINT "message_parts_topic_id_topics_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "topics"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "messages" DROP CONSTRAINT "messages_generation_id_generations_id_fkey", ADD CONSTRAINT "messages_generation_id_generations_id_fkey" FOREIGN KEY ("generation_id") REFERENCES "generations"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "messages" DROP CONSTRAINT "messages_topic_id_topics_id_fkey", ADD CONSTRAINT "messages_topic_id_topics_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "topics"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "models" DROP CONSTRAINT "models_provider_id_providers_id_fkey", ADD CONSTRAINT "models_provider_id_providers_id_fkey" FOREIGN KEY ("provider_id") REFERENCES "providers"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "sessions" DROP CONSTRAINT "sessions_user_id_users_id_fkey", ADD CONSTRAINT "sessions_user_id_users_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "topics" DROP CONSTRAINT "topics_agent_id_agents_id_fkey", ADD CONSTRAINT "topics_agent_id_agents_id_fkey" FOREIGN KEY ("agent_id") REFERENCES "agents"("id") ON DELETE CASCADE;
|
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
CREATE TYPE "part_type" AS ENUM('reasoning', 'text', 'tool-call', 'file');--> statement-breakpoint
|
||||||
|
CREATE TYPE "role" AS ENUM('user', 'assistant');--> statement-breakpoint
|
||||||
|
CREATE TYPE "status" AS ENUM('pending', 'completed', 'failed', 'cancelled');--> statement-breakpoint
|
||||||
|
CREATE TABLE "accounts" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"account_id" text NOT NULL,
|
||||||
|
"provider_id" text NOT NULL,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"access_token" text,
|
||||||
|
"refresh_token" text,
|
||||||
|
"id_token" text,
|
||||||
|
"access_token_expires_at" timestamp,
|
||||||
|
"refresh_token_expires_at" timestamp,
|
||||||
|
"scope" text,
|
||||||
|
"password" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "agents" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"system_prompt" text,
|
||||||
|
"image_url" text,
|
||||||
|
"default_model_id" text,
|
||||||
|
"config" jsonb DEFAULT '{}',
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "attachments" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"topic_id" text NOT NULL,
|
||||||
|
"message_id" text NOT NULL,
|
||||||
|
"file_id" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "embeddings" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"file_id" text NOT NULL,
|
||||||
|
"content" text NOT NULL,
|
||||||
|
"embedding" vector(1536),
|
||||||
|
"metadata" jsonb
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "files" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"mime_type" text NOT NULL,
|
||||||
|
"url" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "generations" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"topic_id" text NOT NULL,
|
||||||
|
"model_id" text NOT NULL,
|
||||||
|
"status" "status" NOT NULL,
|
||||||
|
"tokens" jsonb,
|
||||||
|
"error" text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "message_parts" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"topic_id" text NOT NULL,
|
||||||
|
"message_id" text NOT NULL,
|
||||||
|
"tool_call_id" text,
|
||||||
|
"type" "part_type" NOT NULL,
|
||||||
|
"content" text,
|
||||||
|
"provider_options" jsonb,
|
||||||
|
"finished" boolean DEFAULT false NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"last_updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "messages" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"topic_id" text NOT NULL,
|
||||||
|
"parent_message_id" text,
|
||||||
|
"generation_id" text,
|
||||||
|
"role" "role" NOT NULL,
|
||||||
|
"content" text,
|
||||||
|
"focused_index" integer,
|
||||||
|
"deleted" boolean DEFAULT false,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "models" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"external_id" text NOT NULL,
|
||||||
|
"provider_id" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"cost" jsonb DEFAULT '{}' NOT NULL,
|
||||||
|
"input_modalities" text[] DEFAULT '{}'::text[] NOT NULL,
|
||||||
|
"output_modalities" text[] DEFAULT '{}'::text[] NOT NULL,
|
||||||
|
"capabilities" text[] DEFAULT '{}'::text[] NOT NULL,
|
||||||
|
"context_window" integer,
|
||||||
|
"supported_parameters" text[],
|
||||||
|
"is_custom" boolean DEFAULT false NOT NULL,
|
||||||
|
"enabled" boolean DEFAULT true NOT NULL,
|
||||||
|
"released_at" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "providers" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"enabled" boolean DEFAULT true NOT NULL,
|
||||||
|
"config" jsonb DEFAULT '{}' NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "sessions" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"token" text NOT NULL UNIQUE,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL,
|
||||||
|
"ip_address" text,
|
||||||
|
"user_agent" text,
|
||||||
|
"user_id" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "settings" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"system_assistants" jsonb DEFAULT '{}' NOT NULL,
|
||||||
|
"appearance" jsonb
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "tool_calls" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"tool_name" text NOT NULL,
|
||||||
|
"status" "status" DEFAULT 'pending'::"status" NOT NULL,
|
||||||
|
"input" jsonb,
|
||||||
|
"output" jsonb,
|
||||||
|
"error" jsonb,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "topics" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"agent_id" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"renaming" boolean DEFAULT false,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"email" text NOT NULL UNIQUE,
|
||||||
|
"email_verified" boolean DEFAULT false NOT NULL,
|
||||||
|
"image" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "verifications" (
|
||||||
|
"id" text PRIMARY KEY,
|
||||||
|
"identifier" text NOT NULL,
|
||||||
|
"value" text NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX "accounts_userId_idx" ON "accounts" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "agents_userId_idx" ON "agents" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "attachments_topicId_idx" ON "attachments" ("topic_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "attachments_messageId_idx" ON "attachments" ("message_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "attachments_userId_idx" ON "attachments" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "embeddings_fileId_idx" ON "embeddings" ("file_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "files_userId_idx" ON "files" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "generations_topicId_idx" ON "generations" ("topic_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "generations_userId_idx" ON "generations" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "message_parts_topicId_idx" ON "message_parts" ("topic_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "message_parts_messageId_idx" ON "message_parts" ("message_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "message_parts_userId_idx" ON "message_parts" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "messages_topicId_idx" ON "messages" ("topic_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "messages_parentMessageId_idx" ON "messages" ("parent_message_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "messages_userId_idx" ON "messages" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "models_providerId_idx" ON "models" ("provider_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "models_userId_idx" ON "models" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "providers_userId_idx" ON "providers" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "sessions_userId_idx" ON "sessions" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "settings_userId_idx" ON "settings" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "tool_calls_userId_idx" ON "tool_calls" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "topics_agentId_idx" ON "topics" ("agent_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "topics_userId_idx" ON "topics" ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "verifications_identifier_idx" ON "verifications" ("identifier");--> statement-breakpoint
|
||||||
|
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "agents" ADD CONSTRAINT "agents_default_model_id_models_id_fkey" FOREIGN KEY ("default_model_id") REFERENCES "models"("id") ON DELETE SET NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "attachments" ADD CONSTRAINT "attachments_topic_id_topics_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "topics"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "attachments" ADD CONSTRAINT "attachments_message_id_messages_id_fkey" FOREIGN KEY ("message_id") REFERENCES "messages"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "attachments" ADD CONSTRAINT "attachments_file_id_files_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "embeddings" ADD CONSTRAINT "embeddings_file_id_files_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "generations" ADD CONSTRAINT "generations_topic_id_topics_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "topics"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "message_parts" ADD CONSTRAINT "message_parts_topic_id_topics_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "topics"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "message_parts" ADD CONSTRAINT "message_parts_message_id_messages_id_fkey" FOREIGN KEY ("message_id") REFERENCES "messages"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "message_parts" ADD CONSTRAINT "message_parts_tool_call_id_tool_calls_id_fkey" FOREIGN KEY ("tool_call_id") REFERENCES "tool_calls"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "messages" ADD CONSTRAINT "messages_topic_id_topics_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "topics"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "messages" ADD CONSTRAINT "messages_generation_id_generations_id_fkey" FOREIGN KEY ("generation_id") REFERENCES "generations"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "models" ADD CONSTRAINT "models_provider_id_providers_id_fkey" FOREIGN KEY ("provider_id") REFERENCES "providers"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||||
|
ALTER TABLE "topics" ADD CONSTRAINT "topics_agent_id_agents_id_fkey" FOREIGN KEY ("agent_id") REFERENCES "agents"("id") ON DELETE CASCADE;
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"version": "8",
|
"version": "8",
|
||||||
"dialect": "postgres",
|
"dialect": "postgres",
|
||||||
"id": "5af4d48d-34c4-4952-aadf-c434cf32d55f",
|
"id": "fca582f6-c25d-4250-92f4-a51d1b57d3b4",
|
||||||
"prevIds": [
|
"prevIds": [
|
||||||
"f031de7a-cc2b-4725-9667-e059eb7fb46c"
|
"00000000-0000-0000-0000-000000000000"
|
||||||
],
|
],
|
||||||
"ddl": [
|
"ddl": [
|
||||||
{
|
{
|
||||||
@@ -2346,7 +2346,7 @@
|
|||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"onUpdate": "NO ACTION",
|
"onUpdate": "NO ACTION",
|
||||||
"onDelete": "NO ACTION",
|
"onDelete": "CASCADE",
|
||||||
"name": "attachments_topic_id_topics_id_fkey",
|
"name": "attachments_topic_id_topics_id_fkey",
|
||||||
"entityType": "fks",
|
"entityType": "fks",
|
||||||
"schema": "public",
|
"schema": "public",
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
ALTER TABLE "files" ADD COLUMN "size" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "messages" ADD COLUMN "active_child_id" text;--> statement-breakpoint
|
||||||
|
UPDATE "messages" m
|
||||||
|
SET "active_child_id" = (
|
||||||
|
SELECT child."id"
|
||||||
|
FROM "messages" child
|
||||||
|
WHERE child."parent_message_id" = m."id"
|
||||||
|
ORDER BY child."created_at" ASC
|
||||||
|
OFFSET (m."focused_index" - 1)
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE m."focused_index" IS NOT NULL AND m."focused_index" > 0;--> statement-breakpoint
|
||||||
|
ALTER TABLE "messages" DROP COLUMN "focused_index";
|
||||||
+1674
-1648
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -55,7 +55,7 @@ export const providers = pgTable('providers', {
|
|||||||
apiProxyUrl?: string;
|
apiProxyUrl?: string;
|
||||||
}>().default({}),
|
}>().default({}),
|
||||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||||
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
updatedAt: timestamp('updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||||
}, (table) => [
|
}, (table) => [
|
||||||
index('providers_userId_idx').on(table.userId),
|
index('providers_userId_idx').on(table.userId),
|
||||||
]);
|
]);
|
||||||
@@ -134,7 +134,7 @@ export const messages = pgTable('messages', {
|
|||||||
generationId: text('generation_id').references(() => generations.id, { onDelete: 'cascade' }),
|
generationId: text('generation_id').references(() => generations.id, { onDelete: 'cascade' }),
|
||||||
role: roleEnum('role').notNull(),
|
role: roleEnum('role').notNull(),
|
||||||
content: text('content'),
|
content: text('content'),
|
||||||
focusedIndex: integer('focused_index'),
|
activeChildId: text('active_child_id'),
|
||||||
deleted: boolean('deleted').default(false),
|
deleted: boolean('deleted').default(false),
|
||||||
updatedAt: timestamp('updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
updatedAt: timestamp('updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||||
@@ -155,7 +155,7 @@ export const messageParts = pgTable('message_parts', {
|
|||||||
providerOptions: jsonb('provider_options'),
|
providerOptions: jsonb('provider_options'),
|
||||||
finished: boolean('finished').notNull().default(false),
|
finished: boolean('finished').notNull().default(false),
|
||||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||||
lastUpdatedAt: timestamp('last_updated_at').notNull().defaultNow(),
|
lastUpdatedAt: timestamp('last_updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||||
}, (table) => [
|
}, (table) => [
|
||||||
index('message_parts_topicId_idx').on(table.topicId),
|
index('message_parts_topicId_idx').on(table.topicId),
|
||||||
index('message_parts_messageId_idx').on(table.messageId),
|
index('message_parts_messageId_idx').on(table.messageId),
|
||||||
@@ -212,6 +212,7 @@ export const files = pgTable('files', {
|
|||||||
userId: text('user_id').notNull(),
|
userId: text('user_id').notNull(),
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
mimeType: text('mime_type').notNull(),
|
mimeType: text('mime_type').notNull(),
|
||||||
|
size: integer('size').notNull().default(0),
|
||||||
url: text('url').notNull(),
|
url: text('url').notNull(),
|
||||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||||
}, (table) => [
|
}, (table) => [
|
||||||
|
|||||||
@@ -77,7 +77,9 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rootMessage.deleted === true) {
|
if (rootMessage.deleted === true) {
|
||||||
const childMessage = rootMessage.children[rootMessage.focusedIndex!];
|
const childMessage = rootMessage.activeChildId
|
||||||
|
? rootMessage.children.find(c => c.id === rootMessage.activeChildId)
|
||||||
|
: rootMessage.children[0];
|
||||||
if (!childMessage) {
|
if (!childMessage) {
|
||||||
console.error('Message not found');
|
console.error('Message not found');
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
@@ -88,27 +90,15 @@ export default defineEventHandler(async (event) => {
|
|||||||
const remainingChildren = rootMessage.children.filter(child => child!.id !== childMessage.id);
|
const remainingChildren = rootMessage.children.filter(child => child!.id !== childMessage.id);
|
||||||
if (remainingChildren.length === 0) {
|
if (remainingChildren.length === 0) {
|
||||||
await deeplyDeleteMessage(rootMessage.id, topicId, userId);
|
await deeplyDeleteMessage(rootMessage.id, topicId, userId);
|
||||||
} else {
|
|
||||||
const newFocusedIndex = rootMessage.focusedIndex && rootMessage.focusedIndex > 0
|
|
||||||
? Math.min(rootMessage.focusedIndex - 1, remainingChildren.length - 1)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
await db.update(messages).set({ focusedIndex: newFocusedIndex }).where(sql`${messages.id} = ${rootMessage.id} AND ${messages.userId} = ${userId}`);
|
|
||||||
|
|
||||||
topicEvents.emit(topicId, {
|
|
||||||
type: 'MESSAGE_UPDATED',
|
|
||||||
payload: { ...rootMessage, focusedIndex: newFocusedIndex },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(rootMessage.focusedIndex !== undefined && rootMessage.focusedIndex !== null)
|
rootMessage.activeChildId
|
||||||
&& rootMessage.focusedIndex > 0
|
|
||||||
&& rootMessage.children.length > 0
|
&& rootMessage.children.length > 0
|
||||||
) {
|
) {
|
||||||
const childMessage = rootMessage.children[rootMessage.focusedIndex - 1];
|
const childMessage = rootMessage.children.find(c => c.id === rootMessage.activeChildId);
|
||||||
if (!childMessage) {
|
if (!childMessage) {
|
||||||
console.error('Message not found');
|
console.error('Message not found');
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
@@ -116,17 +106,6 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
await deeplyDeleteMessage(childMessage.id, topicId, userId);
|
await deeplyDeleteMessage(childMessage.id, topicId, userId);
|
||||||
|
|
||||||
const remainingChildren = rootMessage.children.filter(child => child!.id !== childMessage.id);
|
|
||||||
const newFocusedIndex = remainingChildren.length > 0
|
|
||||||
? Math.min(rootMessage.focusedIndex - 1, remainingChildren.length - 1)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
await db.update(messages).set({ focusedIndex: newFocusedIndex }).where(sql`${messages.id} = ${rootMessage.id} AND ${messages.userId} = ${userId}`);
|
|
||||||
|
|
||||||
topicEvents.emit(topicId, {
|
|
||||||
type: 'MESSAGE_UPDATED',
|
|
||||||
payload: { ...rootMessage, focusedIndex: newFocusedIndex },
|
|
||||||
});
|
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -260,8 +260,8 @@ export default defineEventHandler(async (event) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.update(messages).set({ focusedIndex: (parentMessage.focusedIndex ?? 0) + 1 }).where(eq(messages.id, parentMessageId));
|
await db.update(messages).set({ activeChildId: agentmessage.id }).where(eq(messages.id, parentMessageId));
|
||||||
events.push({ type: 'MESSAGE_UPDATED', payload: { focusedIndex: (parentMessage.focusedIndex ?? 0) + 1 } });
|
events.push({ type: 'MESSAGE_UPDATED', payload: { id: parentMessageId, activeChildId: agentmessage.id } });
|
||||||
|
|
||||||
// we need to make the topic messages all the messages excluding the ones after the message we wish to regenerate
|
// we need to make the topic messages all the messages excluding the ones after the message we wish to regenerate
|
||||||
// and if parentMessageId is undefined, then excluding the last message
|
// and if parentMessageId is undefined, then excluding the last message
|
||||||
@@ -392,7 +392,13 @@ type SearchTheWebConfig = SearchTheWebRerankedConfig | SearchTheWebUnrankedConfi
|
|||||||
|
|
||||||
export const searchTheWeb = (config: SearchTheWebConfig) => {
|
export const searchTheWeb = (config: SearchTheWebConfig) => {
|
||||||
return async (query: string) => {
|
return async (query: string) => {
|
||||||
const results = await $fetch<any>(`${process.env.SEARXNG_URL}/search`, {
|
const results = await $fetch<{
|
||||||
|
results: Array<{
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
content: string;
|
||||||
|
}>;
|
||||||
|
}>(`${process.env.SEARXNG_URL}/search`, {
|
||||||
query: {
|
query: {
|
||||||
q: query,
|
q: query,
|
||||||
format: 'json',
|
format: 'json',
|
||||||
@@ -675,6 +681,7 @@ async function generateResponse(
|
|||||||
payload: {
|
payload: {
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
partId: part.id,
|
partId: part.id,
|
||||||
|
lastUpdatedAt: new Date(),
|
||||||
content: part.accumulatedContent,
|
content: part.accumulatedContent,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -850,6 +857,7 @@ async function generateResponse(
|
|||||||
payload: {
|
payload: {
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
partId: part.id,
|
partId: part.id,
|
||||||
|
lastUpdatedAt: new Date(),
|
||||||
content: token.text,
|
content: token.text,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -903,6 +911,7 @@ async function generateResponse(
|
|||||||
payload: {
|
payload: {
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
partId: part.id,
|
partId: part.id,
|
||||||
|
lastUpdatedAt: dbPart.lastUpdatedAt,
|
||||||
content: part.accumulatedContent,
|
content: part.accumulatedContent,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1177,7 +1186,7 @@ async function generateResponse(
|
|||||||
error: {
|
error: {
|
||||||
type: outputType as ToolCallType,
|
type: outputType as ToolCallType,
|
||||||
value: outputValue as string,
|
value: outputValue as string,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+3
-11
@@ -22,21 +22,13 @@ export const buildFocusedMessageTree = (messages: Readonly<Message[]>): MessageE
|
|||||||
let focusedMessageTree: MessageEntity[] = [];
|
let focusedMessageTree: MessageEntity[] = [];
|
||||||
|
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
if (message.focusedIndex) {
|
if (message.activeChildId && message.children.length > 0) {
|
||||||
if (message.focusedIndex === 0) {
|
const activeChild = message.children.find(c => c?.id === message.activeChildId);
|
||||||
focusedMessageTree.push(message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeChild = message.children[Math.min(Math.max(message.focusedIndex! - 1, 0), message.children.length - 1)]
|
|
||||||
if (activeChild) {
|
if (activeChild) {
|
||||||
focusedMessageTree.push(activeChild);
|
focusedMessageTree.push(activeChild);
|
||||||
} else {
|
|
||||||
focusedMessageTree.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
focusedMessageTree.push(message);
|
focusedMessageTree.push(message);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user