74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
|
|
import { getQueue } from '@/lib/queue';
|
||
|
|
|
||
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
export interface DraftRequest {
|
||
|
|
interestId: string;
|
||
|
|
clientId: string;
|
||
|
|
portId: string;
|
||
|
|
context: 'follow_up' | 'introduction' | 'stage_update' | 'general';
|
||
|
|
additionalInstructions?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface DraftResult {
|
||
|
|
subject: string;
|
||
|
|
body: string;
|
||
|
|
generatedAt: Date;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─── Request draft (enqueues job) ─────────────────────────────────────────────
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Request an AI-generated email draft.
|
||
|
|
* Enqueues a job on the 'ai' queue. Returns jobId for polling.
|
||
|
|
* Job payload contains ONLY entity IDs (no PII).
|
||
|
|
*/
|
||
|
|
export async function requestEmailDraft(
|
||
|
|
userId: string,
|
||
|
|
request: DraftRequest,
|
||
|
|
): Promise<{ jobId: string }> {
|
||
|
|
const aiQueue = getQueue('ai');
|
||
|
|
|
||
|
|
const job = await aiQueue.add('generate-email-draft', {
|
||
|
|
// No PII — only IDs and context parameters
|
||
|
|
interestId: request.interestId,
|
||
|
|
clientId: request.clientId,
|
||
|
|
portId: request.portId,
|
||
|
|
context: request.context,
|
||
|
|
additionalInstructions: request.additionalInstructions,
|
||
|
|
requestedBy: userId,
|
||
|
|
});
|
||
|
|
|
||
|
|
return { jobId: job.id! };
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─── Poll for result ──────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the result of an email draft generation job.
|
||
|
|
* Returns null if still processing.
|
||
|
|
*/
|
||
|
|
export async function getEmailDraftResult(jobId: string): Promise<DraftResult | null> {
|
||
|
|
const aiQueue = getQueue('ai');
|
||
|
|
const job = await aiQueue.getJob(jobId);
|
||
|
|
|
||
|
|
if (!job) return null;
|
||
|
|
|
||
|
|
const state = await job.getState();
|
||
|
|
|
||
|
|
if (state !== 'completed') return null;
|
||
|
|
|
||
|
|
const returnValue = job.returnvalue as
|
||
|
|
| { subject: string; body: string; generatedAt: string }
|
||
|
|
| undefined
|
||
|
|
| null;
|
||
|
|
|
||
|
|
if (!returnValue) return null;
|
||
|
|
|
||
|
|
return {
|
||
|
|
subject: returnValue.subject,
|
||
|
|
body: returnValue.body,
|
||
|
|
generatedAt: new Date(returnValue.generatedAt),
|
||
|
|
};
|
||
|
|
}
|