stack/apps/dashboard/src/lib/conversations.ts
BilalG1 609579abab
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482)
2026-05-29 15:21:59 -07:00

121 lines
3.9 KiB
TypeScript

import { buildStackAuthHeaders, type CurrentUser } from "@/lib/api-headers";
import { getPublicEnvVar } from "@/lib/env";
import type {
ConversationDetailResponse,
ConversationListResponse,
ConversationPriority,
ConversationStatus,
} from "@/lib/conversation-types";
import { throwErr } from "@hexclave/shared/dist/utils/errors";
type ListConversationsOptions = {
projectId: string,
query?: string,
status?: ConversationStatus,
userId?: string,
limit?: number,
offset?: number,
};
function getBaseUrl() {
return getPublicEnvVar("NEXT_PUBLIC_STACK_API_URL") ?? throwErr("NEXT_PUBLIC_STACK_API_URL is not set");
}
async function apiFetch(
currentUser: CurrentUser | null,
path: string,
options: RequestInit = {},
) {
const headers = await buildStackAuthHeaders(currentUser);
const response = await fetch(`${getBaseUrl()}/api/latest/internal/conversations${path}`, {
...options,
headers: {
...(options.body != null ? { "content-type": "application/json" } : {}),
...headers,
...options.headers,
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText || `Conversations API error: ${response.status}`);
}
return response;
}
export async function listConversations(currentUser: CurrentUser | null, options: ListConversationsOptions) {
const params = new URLSearchParams();
params.set("projectId", options.projectId);
if (options.query) params.set("query", options.query);
if (options.status) params.set("status", options.status);
if (options.userId) params.set("userId", options.userId);
if (options.limit != null) params.set("limit", options.limit.toString());
if (options.offset != null) params.set("offset", options.offset.toString());
const response = await apiFetch(currentUser, `?${params.toString()}`);
return await response.json() as ConversationListResponse;
}
export async function getConversation(currentUser: CurrentUser | null, options: {
projectId: string,
conversationId: string,
}) {
const params = new URLSearchParams();
params.set("projectId", options.projectId);
const response = await apiFetch(currentUser, `/${encodeURIComponent(options.conversationId)}?${params.toString()}`);
return await response.json() as ConversationDetailResponse;
}
export async function createConversation(currentUser: CurrentUser | null, options: {
projectId: string,
userId: string,
subject: string,
initialMessage: string,
priority: ConversationPriority,
}) {
const response = await apiFetch(currentUser, "", {
method: "POST",
body: JSON.stringify(options),
});
return await response.json() as { conversationId: string };
}
export async function appendConversationUpdate(currentUser: CurrentUser | null, options:
| { projectId: string, conversationId: string, type: "internal-note", body: string }
| { projectId: string, conversationId: string, type: "reply", body: string }
| { projectId: string, conversationId: string, type: "status", status: ConversationStatus }
| {
projectId: string,
conversationId: string,
type: "metadata",
assignedToUserId?: string | null,
assignedToDisplayName?: string | null,
priority?: ConversationPriority,
tags?: string[],
}
) {
const payload = (() => {
if ("body" in options) {
return { body: options.body };
}
if ("status" in options) {
return { status: options.status };
}
return {
assignedToUserId: options.assignedToUserId,
assignedToDisplayName: options.assignedToDisplayName,
priority: options.priority,
tags: options.tags,
};
})();
const response = await apiFetch(currentUser, `/${encodeURIComponent(options.conversationId)}`, {
method: "PATCH",
body: JSON.stringify({
projectId: options.projectId,
type: options.type,
...payload,
}),
});
return await response.json() as ConversationDetailResponse;
}