(chore) Introduced regions
This commit is contained in:
parent
ae70fbdae9
commit
b0cd38e54d
@ -1,8 +1,7 @@
|
|||||||
import { sendCommand } from "./client";
|
import { sendCommand } from "./client";
|
||||||
import { pickCase } from "./normalize";
|
import { pickCase } from "./normalize";
|
||||||
|
|
||||||
// ── Public types ────────────────────────────────────────────────
|
//#region Public Types
|
||||||
|
|
||||||
export type AiHealthDto = {
|
export type AiHealthDto = {
|
||||||
provider: string;
|
provider: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -45,9 +44,9 @@ export type CoachSessionPayload = {
|
|||||||
recentFragments?: string[];
|
recentFragments?: string[];
|
||||||
preferences?: CoachPreferencesDto;
|
preferences?: CoachPreferencesDto;
|
||||||
};
|
};
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Raw (PascalCase) variants for normalization ─────────────────
|
//#region PascalCase Normalizers
|
||||||
|
|
||||||
type AiHealthDtoRaw = {
|
type AiHealthDtoRaw = {
|
||||||
provider?: string;
|
provider?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
@ -93,9 +92,9 @@ type CoachPlanDtoRaw = {
|
|||||||
Evidence?: CoachEvidenceDtoRaw[];
|
Evidence?: CoachEvidenceDtoRaw[];
|
||||||
PatchProposal?: CoachPatchProposalDtoRaw | null;
|
PatchProposal?: CoachPatchProposalDtoRaw | null;
|
||||||
};
|
};
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Normalizers ─────────────────────────────────────────────────
|
//#region Normalizers
|
||||||
|
|
||||||
function normalizeHealth(raw: AiHealthDtoRaw): AiHealthDto {
|
function normalizeHealth(raw: AiHealthDtoRaw): AiHealthDto {
|
||||||
return {
|
return {
|
||||||
provider: pickCase(raw, "provider", "Provider", ""),
|
provider: pickCase(raw, "provider", "Provider", ""),
|
||||||
@ -163,9 +162,9 @@ function normalizeCoachPlan(raw: CoachPlanDtoRaw): CoachPlanDto {
|
|||||||
patchProposal: normalizePatchProposal(patchRaw),
|
patchProposal: normalizePatchProposal(patchRaw),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── API functions ───────────────────────────────────────────────
|
//#region API Functions
|
||||||
|
|
||||||
export async function aiHealth(): Promise<AiHealthDto> {
|
export async function aiHealth(): Promise<AiHealthDto> {
|
||||||
const data = await sendCommand<AiHealthDtoRaw>({
|
const data = await sendCommand<AiHealthDtoRaw>({
|
||||||
action: "ai.health",
|
action: "ai.health",
|
||||||
@ -220,3 +219,4 @@ export async function coachWeekly(
|
|||||||
});
|
});
|
||||||
return normalizeCoachPlan(data);
|
return normalizeCoachPlan(data);
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { sendCommand } from "./client";
|
import { sendCommand } from "./client";
|
||||||
import { pickCase } from "./normalize";
|
import { pickCase } from "./normalize";
|
||||||
|
|
||||||
// ── Public types ────────────────────────────────────────────────
|
//#region Public Types
|
||||||
|
|
||||||
export type ConversationDto = {
|
export type ConversationDto = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -29,9 +28,9 @@ export type ConversationChatResult = {
|
|||||||
userMessage: ConversationMessageDto;
|
userMessage: ConversationMessageDto;
|
||||||
assistantMessage: ConversationMessageDto;
|
assistantMessage: ConversationMessageDto;
|
||||||
};
|
};
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Raw (PascalCase) variants ───────────────────────────────────
|
//#region PascalCase Normalizers
|
||||||
|
|
||||||
type ConversationDtoRaw = {
|
type ConversationDtoRaw = {
|
||||||
id?: string;
|
id?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -73,9 +72,9 @@ type ConversationChatResultRaw = {
|
|||||||
UserMessage?: ConversationMessageDtoRaw;
|
UserMessage?: ConversationMessageDtoRaw;
|
||||||
AssistantMessage?: ConversationMessageDtoRaw;
|
AssistantMessage?: ConversationMessageDtoRaw;
|
||||||
};
|
};
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Normalizers ─────────────────────────────────────────────────
|
//#region Normalizers
|
||||||
|
|
||||||
function normalizeMessage(
|
function normalizeMessage(
|
||||||
raw: ConversationMessageDtoRaw,
|
raw: ConversationMessageDtoRaw,
|
||||||
): ConversationMessageDto {
|
): ConversationMessageDto {
|
||||||
@ -132,9 +131,9 @@ function normalizeChatResult(
|
|||||||
assistantMessage: normalizeMessage(assistantRaw),
|
assistantMessage: normalizeMessage(assistantRaw),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── API functions ───────────────────────────────────────────────
|
//#region API Functions
|
||||||
|
|
||||||
export async function listConversations(): Promise<ConversationDto[]> {
|
export async function listConversations(): Promise<ConversationDto[]> {
|
||||||
const data = await sendCommand<ConversationDtoRaw[]>({
|
const data = await sendCommand<ConversationDtoRaw[]>({
|
||||||
action: "conversations.list",
|
action: "conversations.list",
|
||||||
@ -193,3 +192,4 @@ export async function conversationChat(
|
|||||||
});
|
});
|
||||||
return normalizeChatResult(data);
|
return normalizeChatResult(data);
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|||||||
@ -1279,6 +1279,7 @@
|
|||||||
{#each $conversationsStore.items as conv}
|
{#each $conversationsStore.items as conv}
|
||||||
<li class:is-active={conv.id === $activeConversationStore.id}>
|
<li class:is-active={conv.id === $activeConversationStore.id}>
|
||||||
{#if editingConversationId === conv.id}
|
{#if editingConversationId === conv.id}
|
||||||
|
<!-- svelte-ignore a11y_autofocus -->
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="rename-input"
|
class="rename-input"
|
||||||
|
|||||||
@ -10,8 +10,7 @@ import {
|
|||||||
type CoachSessionPayload,
|
type CoachSessionPayload,
|
||||||
} from "$lib/backend/ai";
|
} from "$lib/backend/ai";
|
||||||
|
|
||||||
// ── Store shapes ────────────────────────────────────────────────
|
//#region Store Shapes
|
||||||
|
|
||||||
type AiStatusState = {
|
type AiStatusState = {
|
||||||
checking: boolean;
|
checking: boolean;
|
||||||
health: AiHealthDto | null;
|
health: AiHealthDto | null;
|
||||||
@ -33,9 +32,9 @@ type ChatState = {
|
|||||||
busy: boolean;
|
busy: boolean;
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
};
|
};
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Stores ──────────────────────────────────────────────────────
|
//#region Stores
|
||||||
|
|
||||||
export const aiStatusStore = writable<AiStatusState>({
|
export const aiStatusStore = writable<AiStatusState>({
|
||||||
checking: false,
|
checking: false,
|
||||||
health: null,
|
health: null,
|
||||||
@ -52,9 +51,9 @@ export const chatStateStore = writable<ChatState>({
|
|||||||
busy: false,
|
busy: false,
|
||||||
messages: [],
|
messages: [],
|
||||||
});
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Actions ─────────────────────────────────────────────────────
|
//#region Actions
|
||||||
|
|
||||||
export async function checkAiHealth(): Promise<void> {
|
export async function checkAiHealth(): Promise<void> {
|
||||||
aiStatusStore.update((s) => ({ ...s, checking: true }));
|
aiStatusStore.update((s) => ({ ...s, checking: true }));
|
||||||
try {
|
try {
|
||||||
@ -138,3 +137,4 @@ export function clearCoachPlan(): void {
|
|||||||
export function clearChat(): void {
|
export function clearChat(): void {
|
||||||
chatStateStore.set({ busy: false, messages: [] });
|
chatStateStore.set({ busy: false, messages: [] });
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|||||||
@ -10,8 +10,7 @@ import {
|
|||||||
type ConversationMessageDto,
|
type ConversationMessageDto,
|
||||||
} from "$lib/backend/conversations";
|
} from "$lib/backend/conversations";
|
||||||
|
|
||||||
// ── Store shapes ────────────────────────────────────────────────
|
//#region Store Shapes
|
||||||
|
|
||||||
type ConversationsState = {
|
type ConversationsState = {
|
||||||
items: ConversationDto[];
|
items: ConversationDto[];
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
@ -25,9 +24,9 @@ type ActiveConversationState = {
|
|||||||
busy: boolean;
|
busy: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
};
|
};
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Stores ──────────────────────────────────────────────────────
|
//#region Stores
|
||||||
|
|
||||||
export const conversationsStore = writable<ConversationsState>({
|
export const conversationsStore = writable<ConversationsState>({
|
||||||
items: [],
|
items: [],
|
||||||
busy: false,
|
busy: false,
|
||||||
@ -41,9 +40,9 @@ export const activeConversationStore = writable<ActiveConversationState>({
|
|||||||
busy: false,
|
busy: false,
|
||||||
error: "",
|
error: "",
|
||||||
});
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Actions ─────────────────────────────────────────────────────
|
//#region Actions
|
||||||
|
|
||||||
export async function loadConversations(): Promise<void> {
|
export async function loadConversations(): Promise<void> {
|
||||||
conversationsStore.update((s) => ({ ...s, busy: true, error: "" }));
|
conversationsStore.update((s) => ({ ...s, busy: true, error: "" }));
|
||||||
try {
|
try {
|
||||||
@ -67,7 +66,6 @@ export async function createNewConversation(
|
|||||||
...s,
|
...s,
|
||||||
items: [conv, ...s.items],
|
items: [conv, ...s.items],
|
||||||
}));
|
}));
|
||||||
// Auto-open the new conversation
|
|
||||||
activeConversationStore.set({
|
activeConversationStore.set({
|
||||||
id: conv.id,
|
id: conv.id,
|
||||||
title: conv.title,
|
title: conv.title,
|
||||||
@ -113,7 +111,6 @@ export async function openConversation(id: string): Promise<void> {
|
|||||||
export async function sendConversationMessage(prompt: string): Promise<void> {
|
export async function sendConversationMessage(prompt: string): Promise<void> {
|
||||||
const state = get(activeConversationStore);
|
const state = get(activeConversationStore);
|
||||||
if (!state.id) {
|
if (!state.id) {
|
||||||
// Auto-create a conversation from the first message
|
|
||||||
const title = prompt.length > 40 ? prompt.slice(0, 37) + "..." : prompt;
|
const title = prompt.length > 40 ? prompt.slice(0, 37) + "..." : prompt;
|
||||||
const convId = await createNewConversation(title);
|
const convId = await createNewConversation(title);
|
||||||
if (!convId) return;
|
if (!convId) return;
|
||||||
@ -122,7 +119,6 @@ export async function sendConversationMessage(prompt: string): Promise<void> {
|
|||||||
const currentId = get(activeConversationStore).id;
|
const currentId = get(activeConversationStore).id;
|
||||||
if (!currentId) return;
|
if (!currentId) return;
|
||||||
|
|
||||||
// Optimistically add user message
|
|
||||||
const tempUserMsg: ConversationMessageDto = {
|
const tempUserMsg: ConversationMessageDto = {
|
||||||
id: `temp-${Date.now()}`,
|
id: `temp-${Date.now()}`,
|
||||||
role: "user",
|
role: "user",
|
||||||
@ -142,14 +138,12 @@ export async function sendConversationMessage(prompt: string): Promise<void> {
|
|||||||
activeConversationStore.update((s) => ({
|
activeConversationStore.update((s) => ({
|
||||||
...s,
|
...s,
|
||||||
busy: false,
|
busy: false,
|
||||||
// Replace temp user message + add assistant message
|
|
||||||
messages: [
|
messages: [
|
||||||
...s.messages.filter((m) => m.id !== tempUserMsg.id),
|
...s.messages.filter((m) => m.id !== tempUserMsg.id),
|
||||||
result.userMessage,
|
result.userMessage,
|
||||||
result.assistantMessage,
|
result.assistantMessage,
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
// Update conversation list (updated_at changes)
|
|
||||||
void loadConversations();
|
void loadConversations();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg: ConversationMessageDto = {
|
const errorMsg: ConversationMessageDto = {
|
||||||
@ -195,7 +189,6 @@ export async function removeConversation(id: string): Promise<void> {
|
|||||||
...s,
|
...s,
|
||||||
items: s.items.filter((c) => c.id !== id),
|
items: s.items.filter((c) => c.id !== id),
|
||||||
}));
|
}));
|
||||||
// If this was the active conversation, clear it
|
|
||||||
const active = get(activeConversationStore);
|
const active = get(activeConversationStore);
|
||||||
if (active.id === id) {
|
if (active.id === id) {
|
||||||
clearActiveConversation();
|
clearActiveConversation();
|
||||||
@ -217,3 +210,4 @@ export function clearActiveConversation(): void {
|
|||||||
error: "",
|
error: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|||||||
@ -10,8 +10,6 @@ import {
|
|||||||
type TodoListDto,
|
type TodoListDto,
|
||||||
} from "$lib/backend/todos";
|
} from "$lib/backend/todos";
|
||||||
|
|
||||||
// TodoItem keeps a numeric `id` for local array operations (used by EditorPanel)
|
|
||||||
// plus a `backendId` (guid string) for backend persistence.
|
|
||||||
export type TodoItem = {
|
export type TodoItem = {
|
||||||
id: number;
|
id: number;
|
||||||
text: string;
|
text: string;
|
||||||
@ -24,8 +22,7 @@ export const todoListsStore = writable<TodoListMeta[]>([]);
|
|||||||
export const todosStore = writable<Record<string, TodoItem[]>>({});
|
export const todosStore = writable<Record<string, TodoItem[]>>({});
|
||||||
export const todosBusyStore = writable(false);
|
export const todosBusyStore = writable(false);
|
||||||
|
|
||||||
// ── ID helpers ───────────────────────────────────────────────────
|
//#region ID Helpers
|
||||||
|
|
||||||
function toStoreId(guid: string): string {
|
function toStoreId(guid: string): string {
|
||||||
return `todos/${guid}`;
|
return `todos/${guid}`;
|
||||||
}
|
}
|
||||||
@ -40,9 +37,9 @@ function toBackendId(storeId: string): string | null {
|
|||||||
export function createTodoId(): number {
|
export function createTodoId(): number {
|
||||||
return Date.now() + Math.floor(Math.random() * 1000);
|
return Date.now() + Math.floor(Math.random() * 1000);
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── DTO mapping ──────────────────────────────────────────────────
|
//#region DTO Mapping
|
||||||
|
|
||||||
function dtoToMeta(dto: TodoListDto): TodoListMeta {
|
function dtoToMeta(dto: TodoListDto): TodoListMeta {
|
||||||
return {
|
return {
|
||||||
id: toStoreId(dto.id),
|
id: toStoreId(dto.id),
|
||||||
@ -59,9 +56,9 @@ function dtoToItems(dto: TodoListDto): TodoItem[] {
|
|||||||
backendId: item.id,
|
backendId: item.id,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Hydration ────────────────────────────────────────────────────
|
//#region Hydration
|
||||||
|
|
||||||
export async function hydrateTodos(): Promise<void> {
|
export async function hydrateTodos(): Promise<void> {
|
||||||
todosBusyStore.set(true);
|
todosBusyStore.set(true);
|
||||||
try {
|
try {
|
||||||
@ -82,9 +79,9 @@ export async function hydrateTodos(): Promise<void> {
|
|||||||
todosBusyStore.set(false);
|
todosBusyStore.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── List CRUD ────────────────────────────────────────────────────
|
//#region List CRUD
|
||||||
|
|
||||||
export async function createTodoListFromLabel(
|
export async function createTodoListFromLabel(
|
||||||
label: string,
|
label: string,
|
||||||
): Promise<{ meta: TodoListMeta; items: TodoItem[] }> {
|
): Promise<{ meta: TodoListMeta; items: TodoItem[] }> {
|
||||||
@ -113,9 +110,9 @@ export async function deleteTodoListByStoreId(
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Item CRUD (backend-backed) ───────────────────────────────────
|
//#region Item CRUD
|
||||||
|
|
||||||
export async function addTodoItemBackend(
|
export async function addTodoItemBackend(
|
||||||
storeId: string,
|
storeId: string,
|
||||||
text: string,
|
text: string,
|
||||||
@ -204,9 +201,9 @@ export async function removeTodoItemBackend(
|
|||||||
}));
|
}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// ── Pure helpers (used by EditorPanel for local state) ───────────
|
//#region Pure Helpers
|
||||||
|
|
||||||
export function serializeTodoList(title: string, todos: TodoItem[]): string {
|
export function serializeTodoList(title: string, todos: TodoItem[]): string {
|
||||||
const heading = title?.trim() ? `# ${title}` : "# To-Do List";
|
const heading = title?.trim() ? `# ${title}` : "# To-Do List";
|
||||||
const lines = todos.map(
|
const lines = todos.map(
|
||||||
@ -285,3 +282,4 @@ export function createTodoListDraft(): {
|
|||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|||||||
@ -10,7 +10,6 @@ export function escapeHtml(input: string): string {
|
|||||||
export function parseInline(input: string): string {
|
export function parseInline(input: string): string {
|
||||||
let value = escapeHtml(input);
|
let value = escapeHtml(input);
|
||||||
|
|
||||||
// Render tag token groups like [[work, vibe]] as visual chips in preview.
|
|
||||||
value = value.replace(/\[\[([^[\]]+)\]\]/g, (match, rawGroup: string) => {
|
value = value.replace(/\[\[([^[\]]+)\]\]/g, (match, rawGroup: string) => {
|
||||||
const tags = rawGroup
|
const tags = rawGroup
|
||||||
.split(",")
|
.split(",")
|
||||||
@ -25,7 +24,6 @@ export function parseInline(input: string): string {
|
|||||||
return `<span class="markdown-tag-list">${chips}</span>`;
|
return `<span class="markdown-tag-list">${chips}</span>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render hashtag-style tags (#Work) as chips in preview.
|
|
||||||
value = value.replace(
|
value = value.replace(
|
||||||
/(^|\s)#([A-Za-z0-9][A-Za-z0-9_-]*)\b/g,
|
/(^|\s)#([A-Za-z0-9][A-Za-z0-9_-]*)\b/g,
|
||||||
(_, leading: string, tag: string) =>
|
(_, leading: string, tag: string) =>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user