- Add Journal.AI project with LLamaSharp-based AI service (Phi-3 model) - Implement coach sessions (daily check-in, evening review, weekly review) - Add conversation CRUD with SQLCipher persistence - AI chat with full conversation history for context-aware replies - Frontend: CoachPanel, AI stores, conversation stores, side panel UI - Conversation list with create, rename, and delete support - Fix Phi-3 output quality (system prompt leaking, token cleanup, JSON filtering) - Fix CREATEDRAFT kind override in coach sessions Co-Authored-By: Oz <oz-agent@warp.dev>
223 lines
5.5 KiB
TypeScript
223 lines
5.5 KiB
TypeScript
import { sendCommand } from "./client";
|
|
import { pickCase } from "./normalize";
|
|
|
|
// ── Public types ────────────────────────────────────────────────
|
|
|
|
export type AiHealthDto = {
|
|
provider: string;
|
|
enabled: boolean;
|
|
healthy: boolean;
|
|
message: string;
|
|
};
|
|
|
|
export type CoachEvidenceDto = {
|
|
recordId: string | null;
|
|
text: string;
|
|
};
|
|
|
|
export type CoachPatchProposalDto = {
|
|
kind: string;
|
|
description: string | null;
|
|
content: string | null;
|
|
};
|
|
|
|
export type CoachPlanDto = {
|
|
kind: string;
|
|
title: string;
|
|
summary: string;
|
|
questions: string[];
|
|
suggestedNextActions: string[];
|
|
suggestedTags: string[];
|
|
evidence: CoachEvidenceDto[];
|
|
patchProposal: CoachPatchProposalDto | null;
|
|
};
|
|
|
|
export type CoachPreferencesDto = {
|
|
maxQuestions?: number;
|
|
maxNextActions?: number;
|
|
};
|
|
|
|
export type CoachSessionPayload = {
|
|
dateLocal?: string;
|
|
weekStartLocal?: string;
|
|
weekEndLocal?: string;
|
|
recentEntries?: string[];
|
|
recentFragments?: string[];
|
|
preferences?: CoachPreferencesDto;
|
|
};
|
|
|
|
// ── Raw (PascalCase) variants for normalization ─────────────────
|
|
|
|
type AiHealthDtoRaw = {
|
|
provider?: string;
|
|
enabled?: boolean;
|
|
healthy?: boolean;
|
|
message?: string;
|
|
Provider?: string;
|
|
Enabled?: boolean;
|
|
Healthy?: boolean;
|
|
Message?: string;
|
|
};
|
|
|
|
type CoachEvidenceDtoRaw = {
|
|
recordId?: string | null;
|
|
text?: string;
|
|
RecordId?: string | null;
|
|
Text?: string;
|
|
};
|
|
|
|
type CoachPatchProposalDtoRaw = {
|
|
kind?: string;
|
|
description?: string | null;
|
|
content?: string | null;
|
|
Kind?: string;
|
|
Description?: string | null;
|
|
Content?: string | null;
|
|
};
|
|
|
|
type CoachPlanDtoRaw = {
|
|
kind?: string;
|
|
title?: string;
|
|
summary?: string;
|
|
questions?: string[];
|
|
suggestedNextActions?: string[];
|
|
suggestedTags?: string[];
|
|
evidence?: CoachEvidenceDtoRaw[];
|
|
patchProposal?: CoachPatchProposalDtoRaw | null;
|
|
Kind?: string;
|
|
Title?: string;
|
|
Summary?: string;
|
|
Questions?: string[];
|
|
SuggestedNextActions?: string[];
|
|
SuggestedTags?: string[];
|
|
Evidence?: CoachEvidenceDtoRaw[];
|
|
PatchProposal?: CoachPatchProposalDtoRaw | null;
|
|
};
|
|
|
|
// ── Normalizers ─────────────────────────────────────────────────
|
|
|
|
function normalizeHealth(raw: AiHealthDtoRaw): AiHealthDto {
|
|
return {
|
|
provider: pickCase(raw, "provider", "Provider", ""),
|
|
enabled: pickCase(raw, "enabled", "Enabled", false),
|
|
healthy: pickCase(raw, "healthy", "Healthy", false),
|
|
message: pickCase(raw, "message", "Message", ""),
|
|
};
|
|
}
|
|
|
|
function normalizeEvidence(raw: CoachEvidenceDtoRaw): CoachEvidenceDto {
|
|
return {
|
|
recordId: pickCase(raw, "recordId", "RecordId", null as string | null),
|
|
text: pickCase(raw, "text", "Text", ""),
|
|
};
|
|
}
|
|
|
|
function normalizePatchProposal(
|
|
raw: CoachPatchProposalDtoRaw | null | undefined,
|
|
): CoachPatchProposalDto | null {
|
|
if (!raw) return null;
|
|
return {
|
|
kind: pickCase(raw, "kind", "Kind", ""),
|
|
description: pickCase(
|
|
raw,
|
|
"description",
|
|
"Description",
|
|
null as string | null,
|
|
),
|
|
content: pickCase(raw, "content", "Content", null as string | null),
|
|
};
|
|
}
|
|
|
|
function normalizeCoachPlan(raw: CoachPlanDtoRaw): CoachPlanDto {
|
|
const evidenceRaw = pickCase(
|
|
raw,
|
|
"evidence",
|
|
"Evidence",
|
|
[] as CoachEvidenceDtoRaw[],
|
|
);
|
|
const patchRaw = pickCase(
|
|
raw,
|
|
"patchProposal",
|
|
"PatchProposal",
|
|
null as CoachPatchProposalDtoRaw | null,
|
|
);
|
|
|
|
return {
|
|
kind: pickCase(raw, "kind", "Kind", ""),
|
|
title: pickCase(raw, "title", "Title", ""),
|
|
summary: pickCase(raw, "summary", "Summary", ""),
|
|
questions: pickCase(raw, "questions", "Questions", [] as string[]),
|
|
suggestedNextActions: pickCase(
|
|
raw,
|
|
"suggestedNextActions",
|
|
"SuggestedNextActions",
|
|
[] as string[],
|
|
),
|
|
suggestedTags: pickCase(
|
|
raw,
|
|
"suggestedTags",
|
|
"SuggestedTags",
|
|
[] as string[],
|
|
),
|
|
evidence: evidenceRaw.map(normalizeEvidence),
|
|
patchProposal: normalizePatchProposal(patchRaw),
|
|
};
|
|
}
|
|
|
|
// ── API functions ───────────────────────────────────────────────
|
|
|
|
export async function aiHealth(): Promise<AiHealthDto> {
|
|
const data = await sendCommand<AiHealthDtoRaw>({
|
|
action: "ai.health",
|
|
payload: {},
|
|
});
|
|
return normalizeHealth(data);
|
|
}
|
|
|
|
export async function aiChat(prompt: string): Promise<string> {
|
|
return sendCommand<string>({
|
|
action: "ai.chat",
|
|
payload: { prompt },
|
|
});
|
|
}
|
|
|
|
export async function aiSummarizeEntry(
|
|
content: string,
|
|
fileStem?: string,
|
|
): Promise<string> {
|
|
return sendCommand<string>({
|
|
action: "ai.summarize_entry",
|
|
payload: { content, fileStem },
|
|
});
|
|
}
|
|
|
|
export async function coachDaily(
|
|
payload: CoachSessionPayload = {},
|
|
): Promise<CoachPlanDto> {
|
|
const data = await sendCommand<CoachPlanDtoRaw>({
|
|
action: "ai.coach.daily",
|
|
payload,
|
|
});
|
|
return normalizeCoachPlan(data);
|
|
}
|
|
|
|
export async function coachEvening(
|
|
payload: CoachSessionPayload = {},
|
|
): Promise<CoachPlanDto> {
|
|
const data = await sendCommand<CoachPlanDtoRaw>({
|
|
action: "ai.coach.evening",
|
|
payload,
|
|
});
|
|
return normalizeCoachPlan(data);
|
|
}
|
|
|
|
export async function coachWeekly(
|
|
payload: CoachSessionPayload = {},
|
|
): Promise<CoachPlanDto> {
|
|
const data = await sendCommand<CoachPlanDtoRaw>({
|
|
action: "ai.coach.weekly",
|
|
payload,
|
|
});
|
|
return normalizeCoachPlan(data);
|
|
}
|