import { ConvexError, v } from 'convex/values'; import type { Doc, Id } from './_generated/dataModel'; import type { MutationCtx } from './_generated/server'; import { internalMutation, internalQuery, mutation, query, } from './_generated/server'; import { getRequiredUserId } from './model'; const reasoningEffort = v.union( v.literal('none'), v.literal('minimal'), v.literal('low'), v.literal('medium'), v.literal('high'), v.literal('xhigh'), ); export const getMine = query({ args: {}, handler: async (ctx) => { const userId = await getRequiredUserId(ctx); const settings = await ctx.db .query('userAiSettings') .withIndex('by_user_provider', (q) => q.eq('userId', userId).eq('provider', 'openai'), ) .first(); if (!settings) { return { configured: false, apiKeyPreview: undefined, model: 'gpt-5.5', reasoningEffort: 'medium' as const, updatedAt: undefined, }; } return { configured: Boolean(settings.encryptedApiKey), apiKeyPreview: settings.apiKeyPreview, model: settings.model, reasoningEffort: settings.reasoningEffort, updatedAt: settings.updatedAt, }; }, }); export const getForUserInternal = internalQuery({ args: { userId: v.id('users') }, handler: async (ctx, { userId }) => { return await ctx.db .query('userAiSettings') .withIndex('by_user_provider', (q) => q.eq('userId', userId).eq('provider', 'openai'), ) .first(); }, }); const upsert = async ( ctx: MutationCtx, userId: Id<'users'>, patch: Partial>, ) => { const now = Date.now(); const existing = await ctx.db .query('userAiSettings') .withIndex('by_user_provider', (q) => q.eq('userId', userId).eq('provider', 'openai'), ) .first(); if (existing) { await ctx.db.patch(existing._id, { ...patch, updatedAt: now }); return existing._id; } return await ctx.db.insert('userAiSettings', { userId, provider: 'openai', model: patch.model ?? 'gpt-5.5', reasoningEffort: patch.reasoningEffort ?? 'medium', encryptedApiKey: patch.encryptedApiKey, apiKeyPreview: patch.apiKeyPreview, createdAt: now, updatedAt: now, }); }; export const upsertEncryptedInternal = internalMutation({ args: { userId: v.id('users'), encryptedApiKey: v.string(), apiKeyPreview: v.string(), model: v.string(), reasoningEffort, }, handler: async (ctx, args) => { return await upsert(ctx, args.userId, { encryptedApiKey: args.encryptedApiKey, apiKeyPreview: args.apiKeyPreview, model: args.model, reasoningEffort: args.reasoningEffort, }); }, }); export const updatePreferences = mutation({ args: { model: v.string(), reasoningEffort, }, handler: async (ctx, args) => { const userId = await getRequiredUserId(ctx); return await upsert(ctx, userId, { model: args.model.trim() || 'gpt-5.5', reasoningEffort: args.reasoningEffort, }); }, }); export const removeOpenAiKey = mutation({ args: {}, handler: async (ctx) => { const userId = await getRequiredUserId(ctx); const settings = await ctx.db .query('userAiSettings') .withIndex('by_user_provider', (q) => q.eq('userId', userId).eq('provider', 'openai'), ) .first(); if (!settings) throw new ConvexError('OpenAI settings not found.'); await ctx.db.patch(settings._id, { encryptedApiKey: undefined, apiKeyPreview: undefined, updatedAt: Date.now(), }); return { success: true }; }, });