mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-23 23:02:17 +10:00
feat(ai): implement an AI chat window for agentic resume building (#3022)
This commit is contained in:
@@ -58,6 +58,7 @@ export const aiRouter = {
|
||||
try {
|
||||
return await aiService.testConnection(input);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (isInvalidAiBaseUrlError(error)) throwAiProviderConfigError();
|
||||
if (isAiProviderGatewayError(error)) throwAiProviderGatewayError();
|
||||
throw error;
|
||||
@@ -137,7 +138,7 @@ export const aiRouter = {
|
||||
operationId: "aiChat",
|
||||
summary: "Chat with AI to modify resume",
|
||||
description:
|
||||
"Streams a chat response from the configured AI provider. The LLM can call the patch_resume tool to generate JSON Patch operations that modify the resume. Requires authentication and AI provider credentials.",
|
||||
"Streams a chat response from the configured AI provider. The LLM can call the propose_resume_patches tool to generate JSON Patch proposals for explicit user approval. Requires authentication and AI provider credentials.",
|
||||
})
|
||||
.input(
|
||||
type<{
|
||||
@@ -147,6 +148,7 @@ export const aiRouter = {
|
||||
baseURL: string;
|
||||
messages: UIMessage[];
|
||||
resumeData: ResumeData;
|
||||
resumeUpdatedAt: Date;
|
||||
}>(),
|
||||
)
|
||||
.use(aiRequestRateLimit)
|
||||
|
||||
@@ -6,6 +6,7 @@ import { protectedProcedure, publicProcedure } from "../context";
|
||||
import { resumeDto } from "../dto/resume";
|
||||
import { resumeMutationRateLimit, resumePasswordRateLimit } from "../middleware/rate-limit";
|
||||
import { resumeService } from "../services/resume";
|
||||
import { subscribeResumeUpdated } from "../services/resume-events";
|
||||
|
||||
const tagsRouter = {
|
||||
list: protectedProcedure
|
||||
@@ -71,10 +72,43 @@ const analysisRouter = {
|
||||
}),
|
||||
};
|
||||
|
||||
const updatesRouter = {
|
||||
subscribe: protectedProcedure
|
||||
.route({
|
||||
method: "GET",
|
||||
path: "/resumes/{id}/updates",
|
||||
tags: ["Resumes"],
|
||||
operationId: "subscribeResumeUpdates",
|
||||
summary: "Subscribe to resume updates",
|
||||
description:
|
||||
"Streams lightweight invalidation events when the specified resume changes. The event payload contains metadata only; clients should refetch the resume for canonical data.",
|
||||
successDescription: "A stream of resume update invalidation events.",
|
||||
})
|
||||
.input(z.object({ id: z.string().describe("The unique identifier of the resume.") }))
|
||||
.handler(async function* ({ context, input, signal }) {
|
||||
const resume = await resumeService.getById({ id: input.id, userId: context.user.id });
|
||||
|
||||
yield {
|
||||
type: "resume.updated" as const,
|
||||
resumeId: input.id,
|
||||
userId: context.user.id,
|
||||
updatedAt: resume.updatedAt.toISOString(),
|
||||
mutation: "sync" as const,
|
||||
};
|
||||
|
||||
yield* subscribeResumeUpdated({
|
||||
resumeId: input.id,
|
||||
userId: context.user.id,
|
||||
...(signal ? { signal } : {}),
|
||||
});
|
||||
}),
|
||||
};
|
||||
|
||||
export const resumeRouter = {
|
||||
tags: tagsRouter,
|
||||
statistics: statisticsRouter,
|
||||
analysis: analysisRouter,
|
||||
updates: updatesRouter,
|
||||
|
||||
list: protectedProcedure
|
||||
.route({
|
||||
@@ -250,12 +284,17 @@ export const resumeRouter = {
|
||||
message: "The patch operations are invalid or produced an invalid resume.",
|
||||
status: 400,
|
||||
},
|
||||
RESUME_VERSION_CONFLICT: {
|
||||
message: "The resume changed after this patch was generated.",
|
||||
status: 409,
|
||||
},
|
||||
})
|
||||
.handler(async ({ context, input }) => {
|
||||
return resumeService.patch({
|
||||
id: input.id,
|
||||
userId: context.user.id,
|
||||
operations: input.operations,
|
||||
...(input.expectedUpdatedAt ? { expectedUpdatedAt: input.expectedUpdatedAt } : {}),
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user