Files
spoon/packages/backend/convex/aiSettings.ts
T
Gabriel Brown 2dfa97ee4f
Build and Push Next App / quality (push) Failing after 48s
Build and Push Next App / build-next (push) Has been skipped
Add agent workflows & stuff
2026-06-21 21:15:15 -05:00

142 lines
3.6 KiB
TypeScript

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<Doc<'userAiSettings'>>,
) => {
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 };
},
});