177 lines
4.4 KiB
TypeScript
177 lines
4.4 KiB
TypeScript
import { v } from 'convex/values';
|
|
import { mutation, query } from './_generated/server';
|
|
|
|
export const listByChat = query({
|
|
args: { chatId: v.id('chats') },
|
|
returns: v.array(
|
|
v.object({
|
|
_id: v.id('messages'),
|
|
_creationTime: v.number(),
|
|
chatId: v.id('chats'),
|
|
role: v.union(v.literal('user'), v.literal('assistant')),
|
|
content: v.string(),
|
|
imageBase64: v.optional(v.string()),
|
|
imageMediaType: v.optional(v.string()),
|
|
followUpOptions: v.optional(v.array(v.string())),
|
|
source: v.union(v.literal('telegram'), v.literal('web')),
|
|
createdAt: v.number(),
|
|
isStreaming: v.optional(v.boolean())
|
|
})
|
|
),
|
|
handler: async (ctx, args) => {
|
|
return await ctx.db
|
|
.query('messages')
|
|
.withIndex('by_chat_id_and_created_at', (q) => q.eq('chatId', args.chatId))
|
|
.order('asc')
|
|
.collect();
|
|
}
|
|
});
|
|
|
|
export const create = mutation({
|
|
args: {
|
|
chatId: v.id('chats'),
|
|
role: v.union(v.literal('user'), v.literal('assistant')),
|
|
content: v.string(),
|
|
source: v.union(v.literal('telegram'), v.literal('web')),
|
|
imageBase64: v.optional(v.string()),
|
|
imageMediaType: v.optional(v.string()),
|
|
followUpOptions: v.optional(v.array(v.string())),
|
|
isStreaming: v.optional(v.boolean())
|
|
},
|
|
returns: v.id('messages'),
|
|
handler: async (ctx, args) => {
|
|
const messageId = await ctx.db.insert('messages', {
|
|
chatId: args.chatId,
|
|
role: args.role,
|
|
content: args.content,
|
|
source: args.source,
|
|
imageBase64: args.imageBase64,
|
|
imageMediaType: args.imageMediaType,
|
|
followUpOptions: args.followUpOptions,
|
|
createdAt: Date.now(),
|
|
isStreaming: args.isStreaming
|
|
});
|
|
|
|
if (args.source === 'web' && args.role === 'user') {
|
|
const chat = await ctx.db.get(args.chatId);
|
|
if (chat) {
|
|
await ctx.db.insert('pendingGenerations', {
|
|
userId: chat.userId,
|
|
chatId: args.chatId,
|
|
userMessage: args.content,
|
|
createdAt: Date.now()
|
|
});
|
|
}
|
|
}
|
|
|
|
return messageId;
|
|
}
|
|
});
|
|
|
|
export const update = mutation({
|
|
args: {
|
|
messageId: v.id('messages'),
|
|
content: v.optional(v.string()),
|
|
followUpOptions: v.optional(v.array(v.string())),
|
|
isStreaming: v.optional(v.boolean())
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
const updates: {
|
|
content?: string;
|
|
followUpOptions?: string[];
|
|
isStreaming?: boolean;
|
|
} = {};
|
|
|
|
if (args.content !== undefined) {
|
|
updates.content = args.content;
|
|
}
|
|
if (args.followUpOptions !== undefined) {
|
|
updates.followUpOptions = args.followUpOptions;
|
|
}
|
|
if (args.isStreaming !== undefined) {
|
|
updates.isStreaming = args.isStreaming;
|
|
}
|
|
|
|
await ctx.db.patch(args.messageId, updates);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
export const getHistoryForAI = query({
|
|
args: { chatId: v.id('chats'), limit: v.optional(v.number()) },
|
|
returns: v.array(
|
|
v.object({
|
|
role: v.union(v.literal('user'), v.literal('assistant')),
|
|
content: v.string()
|
|
})
|
|
),
|
|
handler: async (ctx, args) => {
|
|
const messages = await ctx.db
|
|
.query('messages')
|
|
.withIndex('by_chat_id_and_created_at', (q) => q.eq('chatId', args.chatId))
|
|
.order('asc')
|
|
.collect();
|
|
|
|
const limit = args.limit ?? 50;
|
|
const limited = messages.slice(-limit);
|
|
|
|
return limited.map((m) => ({
|
|
role: m.role,
|
|
content: m.content
|
|
}));
|
|
}
|
|
});
|
|
|
|
export const getLastAssistantMessage = query({
|
|
args: { chatId: v.id('chats') },
|
|
returns: v.union(
|
|
v.object({
|
|
_id: v.id('messages'),
|
|
_creationTime: v.number(),
|
|
chatId: v.id('chats'),
|
|
role: v.union(v.literal('user'), v.literal('assistant')),
|
|
content: v.string(),
|
|
imageBase64: v.optional(v.string()),
|
|
imageMediaType: v.optional(v.string()),
|
|
followUpOptions: v.optional(v.array(v.string())),
|
|
source: v.union(v.literal('telegram'), v.literal('web')),
|
|
createdAt: v.number(),
|
|
isStreaming: v.optional(v.boolean())
|
|
}),
|
|
v.null()
|
|
),
|
|
handler: async (ctx, args) => {
|
|
const messages = await ctx.db
|
|
.query('messages')
|
|
.withIndex('by_chat_id_and_created_at', (q) => q.eq('chatId', args.chatId))
|
|
.order('desc')
|
|
.collect();
|
|
|
|
return messages.find((m) => m.role === 'assistant') ?? null;
|
|
}
|
|
});
|
|
|
|
export const getChatImages = query({
|
|
args: { chatId: v.id('chats') },
|
|
returns: v.array(
|
|
v.object({
|
|
base64: v.string(),
|
|
mediaType: v.string()
|
|
})
|
|
),
|
|
handler: async (ctx, args) => {
|
|
const messages = await ctx.db
|
|
.query('messages')
|
|
.withIndex('by_chat_id', (q) => q.eq('chatId', args.chatId))
|
|
.collect();
|
|
|
|
return messages
|
|
.filter((m) => m.imageBase64 && m.imageMediaType)
|
|
.map((m) => ({
|
|
base64: m.imageBase64!,
|
|
mediaType: m.imageMediaType!
|
|
}));
|
|
}
|
|
});
|