142 lines
3.6 KiB
TypeScript
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 };
|
|
},
|
|
});
|