import { v } from "convex/values";
import { api, internal } from "./_generated/api";
import { Doc, Id } from "./_generated/dataModel";
import { internalAction, internalMutation } from "./_generated/server";
import Anthropic from "@anthropic-ai/sdk";
type AiCategorizeResponse = {
id: Id<"todos">;
category: string;
};
export const categorize = internalAction({
args: { categories: v.array(v.string()) },
handler: async (ctx, args) => {
const todos = await ctx.runQuery(api.todos.list, { count: 100 });
const response = await categorizeTodos(todos, args.categories);
await ctx.runMutation(internal.categorize.setCategories, {
categories: response,
});
},
});
export const setCategories = internalMutation({
args: {
categories: v.array(v.object({ id: v.id("todos"), category: v.string() })),
},
handler: async (ctx, args) => {
for (const category of args.categories) {
await ctx.db.patch(category.id, {
category: category.category,
});
}
},
});
async function categorizeTodos(
todos: Doc<"todos">[],
categories: string[],
): Promise<AiCategorizeResponse[]> {
const claude = new Anthropic({… });
const prompt = `
`
The categories are: ${categories.join(", ")}.
.
The todoId and todo text are:
.
The todoId and todo text are:
${todos.map((todo) => `${todo._id}: ${todo.text}`).join("\n")}.
.
.
Return a JSON object with the format: {"todos": [{"id": "$todoId", "category": "$category"}]},
.
Return a JSON object with the format: {"todos": [{"id": "$todoId", "category": "$category"}]},
where $todoId is the id of the todo and $category is the task category that the todo most closely matches.
.
Return a JSON object with the format: {"todos": [{"id": "$todoId", "category": "$category"}]},
where $todoId is the id of the todo and $category is the task category that the todo most closely matches.
`;
const message = await claude.messages.create({
model: "claude-3-sonnet-20240229",
max_tokens: 1000,
system:
"You are a helpful assistant that categorizes human-written todo items into categories.",
messages: [
{
role: "user",
content: prompt,
},
],
});
// Get the first text content block
const content = message.content.find((block) => block.type === "text");
if (!content || content.type !== "text") {
throw new Error("Unexpected response format from Claude");
}
return JSON.parse(content.text);
}