(
aspects: Array<{
fact: string;
aspect: VoiceAspect;
episodeUuid: string;
userId: string;
workspaceId: string;
}>,
)
| 46 | * Save voice aspects from extraction and store their embeddings |
| 47 | */ |
| 48 | export async function saveVoiceAspects( |
| 49 | aspects: Array<{ |
| 50 | fact: string; |
| 51 | aspect: VoiceAspect; |
| 52 | episodeUuid: string; |
| 53 | userId: string; |
| 54 | workspaceId: string; |
| 55 | }>, |
| 56 | ): Promise<VoiceAspectNode[]> { |
| 57 | if (aspects.length === 0) return []; |
| 58 | |
| 59 | const saved: VoiceAspectNode[] = []; |
| 60 | |
| 61 | for (const a of aspects) { |
| 62 | const record = await prisma.voiceAspect.create({ |
| 63 | data: { |
| 64 | fact: a.fact, |
| 65 | aspect: a.aspect, |
| 66 | episodeUuids: [a.episodeUuid], |
| 67 | userId: a.userId, |
| 68 | workspaceId: a.workspaceId, |
| 69 | validAt: new Date(), |
| 70 | }, |
| 71 | }); |
| 72 | saved.push(toNode(record)); |
| 73 | } |
| 74 | |
| 75 | // Generate and store embeddings in vector provider |
| 76 | const embeddings = await Promise.all(saved.map((s) => getEmbedding(s.fact))); |
| 77 | |
| 78 | const items = saved.map((s, i) => ({ |
| 79 | id: s.uuid, |
| 80 | vector: embeddings[i], |
| 81 | content: s.fact, |
| 82 | metadata: { |
| 83 | userId: s.userId, |
| 84 | workspaceId: s.workspaceId, |
| 85 | aspect: s.aspect, |
| 86 | type: "voice_aspect", |
| 87 | }, |
| 88 | })); |
| 89 | |
| 90 | await vectorProvider().batchUpsert(items, VECTOR_NAMESPACES.ASPECT); |
| 91 | |
| 92 | return saved; |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Find similar voice aspects by vector similarity (for dedup in aspect-resolution) |
no test coverage detected