From a25b92e384a6cc69199ed26a08a520ec94b5a42a Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Thu, 18 Sep 2025 23:21:26 +0530 Subject: [PATCH] fix: UI for document logs feat: added logs API to delete the episode --- .../conversation/conversation-list.tsx | 4 +- .../components/graph/graph-visualization.tsx | 30 --- .../app/components/logs/log-details.tsx | 66 ++++--- .../app/components/logs/log-options.tsx | 6 +- .../app/components/logs/log-text-collapse.tsx | 5 +- apps/webapp/app/components/logs/utils.ts | 10 + apps/webapp/app/env.server.ts | 1 + apps/webapp/app/lib/neo4j.server.ts | 7 +- apps/webapp/app/lib/utils.ts | 14 +- .../routes/api.v1.ingestion_queue.delete.tsx | 16 ++ apps/webapp/app/routes/api.v1.logs.$logId.tsx | 182 +++++++++++------- apps/webapp/app/routes/api.v1.logs.tsx | 3 +- apps/webapp/app/routes/api.v1.storage.tsx | 10 - apps/webapp/app/routes/home.inbox.$logId.tsx | 18 +- apps/webapp/app/services/space.server.ts | 2 +- apps/webapp/openapi-docs.yaml | 149 ++++++++++++++ apps/webapp/openapi.yaml | 149 ++++++++++++++ hosting/docker/.env | 2 +- 18 files changed, 515 insertions(+), 159 deletions(-) diff --git a/apps/webapp/app/components/conversation/conversation-list.tsx b/apps/webapp/app/components/conversation/conversation-list.tsx index 8760bd4..5d4e50b 100644 --- a/apps/webapp/app/components/conversation/conversation-list.tsx +++ b/apps/webapp/app/components/conversation/conversation-list.tsx @@ -57,9 +57,7 @@ export const ConversationList = ({ limit: "5", // Increased for better density }); - fetcher.load(`/api/v1/conversations?${searchParams}`, { - flushSync: true, - }); + fetcher.load(`/api/v1/conversations?${searchParams}`); }, [isLoading, fetcher], ); diff --git a/apps/webapp/app/components/graph/graph-visualization.tsx b/apps/webapp/app/components/graph/graph-visualization.tsx index 251ecc8..3e24d43 100644 --- a/apps/webapp/app/components/graph/graph-visualization.tsx +++ b/apps/webapp/app/components/graph/graph-visualization.tsx @@ -114,36 +114,6 @@ export const GraphVisualization = forwardRef( return (
{/* Entity Types Legend Button */} -
- {/* - - - - -
-
- {allLabels.map((label) => ( -
-
- {label} -
- ))} -
-
- - */} -
{triplets.length > 0 ? ( )} - {typeof value === "string" - ? value.charAt(0).toUpperCase() + value.slice(1).toLowerCase() - : value} + {value} ) : ( @@ -73,6 +71,14 @@ interface EpisodeFactsResponse { invalidFacts: EpisodeFact[]; } +function getStatusValue(status: string) { + if (status === "PENDING") { + return "In Queue"; + } + + return status; +} + export function LogDetails({ log }: LogDetailsProps) { const [facts, setFacts] = useState([]); const [invalidFacts, setInvalidFacts] = useState([]); @@ -122,8 +128,8 @@ export function LogDetails({ log }: LogDetailsProps) { }, [fetcher.data, fetcher.state]); return ( -
-
+
+
Episode Details @@ -131,7 +137,7 @@ export function LogDetails({ log }: LogDetailsProps) {
-
+
{log.data?.type === "DOCUMENT" && log.data?.episodes ? ( @@ -207,6 +213,18 @@ export function LogDetails({ log }: LogDetailsProps) {
)} +
+
+ Content +
+ {/* Log Content */} +
+
+ {log.ingestText} +
+
+
+ {/* Episode Facts */}
@@ -218,20 +236,21 @@ export function LogDetails({ log }: LogDetailsProps) {
) : facts.length > 0 ? ( -
+
{facts.map((fact) => (
-

{fact.fact}

-
+

{fact.fact}

+
- Valid: {new Date(fact.validAt).toLocaleString()} + Valid: {format(new Date(fact.validAt), "dd/MM/yyyy")} {fact.invalidAt && ( - Invalid: {new Date(fact.invalidAt).toLocaleString()} + Invalid:{" "} + {format(new Date(fact.invalidAt), "dd/MM/yyyy")} )} {Object.keys(fact.attributes).length > 0 && ( @@ -270,15 +289,6 @@ export function LogDetails({ log }: LogDetailsProps) { )}
- -
- {/* Log Content */} -
-
- {log.ingestText} -
-
-
); diff --git a/apps/webapp/app/components/logs/log-options.tsx b/apps/webapp/app/components/logs/log-options.tsx index c9b25d5..859f938 100644 --- a/apps/webapp/app/components/logs/log-options.tsx +++ b/apps/webapp/app/components/logs/log-options.tsx @@ -17,7 +17,7 @@ import { AlertDialogTitle, } from "../ui/alert-dialog"; import { useState, useEffect } from "react"; -import { redirect, useFetcher } from "@remix-run/react"; +import { useFetcher, useNavigate } from "@remix-run/react"; interface LogOptionsProps { id: string; @@ -26,6 +26,7 @@ interface LogOptionsProps { export const LogOptions = ({ id }: LogOptionsProps) => { const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const deleteFetcher = useFetcher<{ success: boolean }>(); + const navigate = useNavigate(); const handleDelete = () => { deleteFetcher.submit( @@ -40,8 +41,9 @@ export const LogOptions = ({ id }: LogOptionsProps) => { }; useEffect(() => { + console.log(deleteFetcher.state, deleteFetcher.data); if (deleteFetcher.state === "idle" && deleteFetcher.data?.success) { - redirect(`/home/inbox`); + navigate(`/home/inbox`); } }, [deleteFetcher.state, deleteFetcher.data]); diff --git a/apps/webapp/app/components/logs/log-text-collapse.tsx b/apps/webapp/app/components/logs/log-text-collapse.tsx index 2671e10..0bc0139 100644 --- a/apps/webapp/app/components/logs/log-text-collapse.tsx +++ b/apps/webapp/app/components/logs/log-text-collapse.tsx @@ -3,7 +3,7 @@ import { Badge, BadgeColor } from "../ui/badge"; import { type LogItem } from "~/hooks/use-logs"; import { getIconForAuthorise } from "../icon-utils"; import { useNavigate, useParams } from "@remix-run/react"; -import { getStatusColor } from "./utils"; +import { getStatusColor, getStatusValue } from "./utils"; interface LogTextCollapseProps { text?: string; @@ -83,8 +83,7 @@ export function LogTextCollapse({ text, log }: LogTextCollapseProps) { )} > - {log.status.charAt(0).toUpperCase() + - log.status.slice(1).toLowerCase()} + {getStatusValue(log.status)}
diff --git a/apps/webapp/app/components/logs/utils.ts b/apps/webapp/app/components/logs/utils.ts index 39942e2..71b6b8d 100644 --- a/apps/webapp/app/components/logs/utils.ts +++ b/apps/webapp/app/components/logs/utils.ts @@ -1,3 +1,5 @@ +import { formatString } from "~/lib/utils"; + export const getStatusColor = (status: string) => { switch (status) { case "PROCESSING": @@ -14,3 +16,11 @@ export const getStatusColor = (status: string) => { return "bg-gray-800"; } }; + +export function getStatusValue(status: string) { + if (status === "PENDING") { + return formatString("In Queue"); + } + + return status; +} diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 01dc727..7948964 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -86,6 +86,7 @@ const EnvironmentSchema = z.object({ // Model envs MODEL: z.string().default(LLMModelEnum.GPT41), EMBEDDING_MODEL: z.string().default("mxbai-embed-large"), + EMBEDDING_MODEL_SIZE: z.string().default("1024"), OLLAMA_URL: z.string().optional(), COHERE_API_KEY: z.string().optional(), }); diff --git a/apps/webapp/app/lib/neo4j.server.ts b/apps/webapp/app/lib/neo4j.server.ts index bb4d4b6..2d044de 100644 --- a/apps/webapp/app/lib/neo4j.server.ts +++ b/apps/webapp/app/lib/neo4j.server.ts @@ -5,6 +5,7 @@ import { singleton } from "~/utils/singleton"; // Create a singleton driver instance const driver = singleton("neo4j", getDriver); +const EMBEDDING_MODEL_SIZE = process.env.EMBEDDING_MODEL_SIZE ?? "1024"; function getDriver() { return neo4j.driver( @@ -373,17 +374,17 @@ const initializeSchema = async () => { // Create vector indexes for semantic search (if using Neo4j 5.0+) await runQuery(` CREATE VECTOR INDEX entity_embedding IF NOT EXISTS FOR (n:Entity) ON n.nameEmbedding - OPTIONS {indexConfig: {\`vector.dimensions\`: 1024, \`vector.similarity_function\`: 'cosine', \`vector.hnsw.ef_construction\`: 400, \`vector.hnsw.m\`: 32}} + OPTIONS {indexConfig: {\`vector.dimensions\`: ${EMBEDDING_MODEL_SIZE}, \`vector.similarity_function\`: 'cosine', \`vector.hnsw.ef_construction\`: 400, \`vector.hnsw.m\`: 32}} `); await runQuery(` CREATE VECTOR INDEX statement_embedding IF NOT EXISTS FOR (n:Statement) ON n.factEmbedding - OPTIONS {indexConfig: {\`vector.dimensions\`: 1024, \`vector.similarity_function\`: 'cosine', \`vector.hnsw.ef_construction\`: 400, \`vector.hnsw.m\`: 32}} + OPTIONS {indexConfig: {\`vector.dimensions\`: ${EMBEDDING_MODEL_SIZE}, \`vector.similarity_function\`: 'cosine', \`vector.hnsw.ef_construction\`: 400, \`vector.hnsw.m\`: 32}} `); await runQuery(` CREATE VECTOR INDEX episode_embedding IF NOT EXISTS FOR (n:Episode) ON n.contentEmbedding - OPTIONS {indexConfig: {\`vector.dimensions\`: 1024, \`vector.similarity_function\`: 'cosine', \`vector.hnsw.ef_construction\`: 400, \`vector.hnsw.m\`: 32}} + OPTIONS {indexConfig: {\`vector.dimensions\`: ${EMBEDDING_MODEL_SIZE}, \`vector.similarity_function\`: 'cosine', \`vector.hnsw.ef_construction\`: 400, \`vector.hnsw.m\`: 32}} `); // Create fulltext indexes for BM25 search diff --git a/apps/webapp/app/lib/utils.ts b/apps/webapp/app/lib/utils.ts index bd0c391..e4a209d 100644 --- a/apps/webapp/app/lib/utils.ts +++ b/apps/webapp/app/lib/utils.ts @@ -1,6 +1,14 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); +} + +export function formatString(input: string): string { + if (!input) return ""; + return input + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" "); } diff --git a/apps/webapp/app/routes/api.v1.ingestion_queue.delete.tsx b/apps/webapp/app/routes/api.v1.ingestion_queue.delete.tsx index 0ca3fef..fc760e9 100644 --- a/apps/webapp/app/routes/api.v1.ingestion_queue.delete.tsx +++ b/apps/webapp/app/routes/api.v1.ingestion_queue.delete.tsx @@ -6,6 +6,7 @@ import { deleteIngestionQueue, getIngestionQueue, } from "~/services/ingestionLogs.server"; +import { runs, tasks } from "@trigger.dev/sdk"; export const DeleteEpisodeBodyRequest = z.object({ id: z.string(), @@ -36,6 +37,21 @@ const { action, loader } = createHybridActionApiRoute( } const output = ingestionQueue.output as any; + const runningTasks = await runs.list({ + tag: [authentication.userId, ingestionQueue.id], + taskIdentifier: "ingest-episode", + }); + + const latestTask = runningTasks.data.find( + (task) => + task.tags.includes(authentication.userId) && + task.tags.includes(ingestionQueue.id), + ); + + if (latestTask && !latestTask?.isCompleted) { + runs.cancel(latestTask?.id as string); + } + let result; if (output?.episodeUuid) { diff --git a/apps/webapp/app/routes/api.v1.logs.$logId.tsx b/apps/webapp/app/routes/api.v1.logs.$logId.tsx index 828598b..8abb24b 100644 --- a/apps/webapp/app/routes/api.v1.logs.$logId.tsx +++ b/apps/webapp/app/routes/api.v1.logs.$logId.tsx @@ -1,78 +1,122 @@ -import { type LoaderFunctionArgs, json } from "@remix-run/node"; -import { prisma } from "~/db.server"; -import { requireUserId } from "~/services/session.server"; +import { json } from "@remix-run/node"; +import { runs } from "@trigger.dev/sdk"; +import { z } from "zod"; +import { deleteEpisodeWithRelatedNodes } from "~/services/graphModels/episode"; +import { + deleteIngestionQueue, + getIngestionQueue, + getIngestionQueueForFrontend, +} from "~/services/ingestionLogs.server"; +import { + createHybridActionApiRoute, + createHybridLoaderApiRoute, +} from "~/services/routeBuilders/apiBuilder.server"; -export async function loader({ request, params }: LoaderFunctionArgs) { - const userId = await requireUserId(request); - const logId = params.logId; +// Schema for space ID parameter +const LogParamsSchema = z.object({ + logId: z.string(), +}); - // Get user and workspace in one query - const user = await prisma.user.findUnique({ - where: { id: userId }, - select: { Workspace: { select: { id: true } } }, - }); +const loader = createHybridLoaderApiRoute( + { + params: LogParamsSchema, + findResource: async () => 1, + corsStrategy: "all", + allowJWT: true, + }, + async ({ params, authentication }) => { + const formattedLog = await getIngestionQueueForFrontend(params.logId); - if (!user?.Workspace) { - throw new Response("Workspace not found", { status: 404 }); - } + return json({ log: formattedLog }); + }, +); - // Fetch the specific log by logId - const log = await prisma.ingestionQueue.findUnique({ - where: { id: logId }, - select: { - id: true, - createdAt: true, - processedAt: true, - status: true, - error: true, - type: true, - output: true, - data: true, - activity: { - select: { - text: true, - sourceURL: true, - integrationAccount: { - select: { - integrationDefinition: { - select: { - name: true, - slug: true, - }, - }, - }, - }, - }, - }, +export const DeleteEpisodeBodyRequest = z.object({ + id: z.string(), +}); + +const { action } = createHybridActionApiRoute( + { + body: DeleteEpisodeBodyRequest, + allowJWT: true, + method: "DELETE", + authorization: { + action: "delete", }, - }); + corsStrategy: "all", + }, + async ({ body, authentication }) => { + try { + const ingestionQueue = await getIngestionQueue(body.id); - if (!log) { - throw new Response("Log not found", { status: 404 }); - } + if (!ingestionQueue) { + return json( + { + error: "Episode not found or unauthorized", + code: "not_found", + }, + { status: 404 }, + ); + } - // Format the response - const integrationDef = - log.activity?.integrationAccount?.integrationDefinition; - const logData = log.data as any; + const output = ingestionQueue.output as any; + const runningTasks = await runs.list({ + tag: [authentication.userId, ingestionQueue.id], + taskIdentifier: "ingest-episode", + }); - const formattedLog = { - id: log.id, - source: integrationDef?.name || logData?.source || "Unknown", - ingestText: - log.activity?.text || - logData?.episodeBody || - logData?.text || - "No content", - time: log.createdAt, - processedAt: log.processedAt, - episodeUUID: (log.output as any)?.episodeUuid, - status: log.status, - error: log.error, - sourceURL: log.activity?.sourceURL, - integrationSlug: integrationDef?.slug, - data: log.data, - }; + const latestTask = runningTasks.data.find( + (task) => + task.tags.includes(authentication.userId) && + task.tags.includes(ingestionQueue.id), + ); - return json({ log: formattedLog }); -} + if (latestTask && !latestTask?.isCompleted) { + runs.cancel(latestTask?.id); + } + + let result; + + if (output?.episodeUuid) { + result = await deleteEpisodeWithRelatedNodes({ + episodeUuid: output?.episodeUuid, + userId: authentication.userId, + }); + + if (!result.episodeDeleted) { + return json( + { + error: "Episode not found or unauthorized", + code: "not_found", + }, + { status: 404 }, + ); + } + } + + await deleteIngestionQueue(ingestionQueue.id); + + return json({ + success: true, + message: "Episode deleted successfully", + deleted: { + episode: result?.episodeDeleted, + statements: result?.statementsDeleted, + entities: result?.entitiesDeleted, + facts: result?.factsDeleted, + }, + }); + } catch (error) { + console.error("Error deleting episode:", error); + return json( + { + error: "Failed to delete episode", + code: "internal_error", + }, + { status: 500 }, + ); + } + }, +); + +export { action, loader }; diff --git a/apps/webapp/app/routes/api.v1.logs.tsx b/apps/webapp/app/routes/api.v1.logs.tsx index 3ae2639..d9f1942 100644 --- a/apps/webapp/app/routes/api.v1.logs.tsx +++ b/apps/webapp/app/routes/api.v1.logs.tsx @@ -15,7 +15,7 @@ export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const page = parseInt(url.searchParams.get("page") || "1"); - const limit = parseInt(url.searchParams.get("limit") || "20"); + const limit = parseInt(url.searchParams.get("limit") || "100"); const source = url.searchParams.get("source"); const status = url.searchParams.get("status"); const type = url.searchParams.get("type"); @@ -92,6 +92,7 @@ export async function loader({ request }: LoaderFunctionArgs) { createdAt: "desc", }, skip, + take: limit, }), prisma.ingestionQueue.count({ diff --git a/apps/webapp/app/routes/api.v1.storage.tsx b/apps/webapp/app/routes/api.v1.storage.tsx index 1f0b187..be776d0 100644 --- a/apps/webapp/app/routes/api.v1.storage.tsx +++ b/apps/webapp/app/routes/api.v1.storage.tsx @@ -1,4 +1,3 @@ -import { z } from "zod"; import { json } from "@remix-run/node"; import { createHybridActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { uploadFileToS3 } from "~/lib/storage.server"; @@ -14,15 +13,6 @@ const { action, loader } = createHybridActionApiRoute( let fileName = "unnamed-file"; let contentType = "application/octet-stream"; - return json({ - success: true, - - url: "http://localhost:3033/api/v1/storage/69bd1e11-552b-4708-91b0-bad006f41ddb", - filename: fileName, - - contentType: contentType, - }); - try { const contentTypeHeader = request.headers.get("Content-Type") || ""; diff --git a/apps/webapp/app/routes/home.inbox.$logId.tsx b/apps/webapp/app/routes/home.inbox.$logId.tsx index 3c3da0d..4f9b13f 100644 --- a/apps/webapp/app/routes/home.inbox.$logId.tsx +++ b/apps/webapp/app/routes/home.inbox.$logId.tsx @@ -3,6 +3,7 @@ import { useLoaderData } from "@remix-run/react"; import { Inbox } from "lucide-react"; import { PageHeader } from "~/components/common/page-header"; import { LogDetails } from "~/components/logs/log-details"; +import { LogOptions } from "~/components/logs/log-options"; import { getIngestionQueueForFrontend } from "~/services/ingestionLogs.server"; import { requireUserId } from "~/services/session.server"; @@ -11,9 +12,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) { await requireUserId(request); const logId = params.logId; - const log = await getIngestionQueueForFrontend(logId as string); - - return json({ log: log }); + try { + const log = await getIngestionQueueForFrontend(logId as string); + return json({ log: log }); + } catch (e) { + return json({ log: null }); + } } export default function InboxNotSelected() { @@ -32,8 +36,12 @@ export default function InboxNotSelected() { } return ( -
- +
+ } + />
diff --git a/apps/webapp/app/services/space.server.ts b/apps/webapp/app/services/space.server.ts index a08ff7c..f28cc1f 100644 --- a/apps/webapp/app/services/space.server.ts +++ b/apps/webapp/app/services/space.server.ts @@ -56,7 +56,7 @@ export class SpaceService { name: params.name.trim(), description: params.description?.trim(), workspaceId: params.workspaceId, - status: "pending", + status: "ready", }, }); diff --git a/apps/webapp/openapi-docs.yaml b/apps/webapp/openapi-docs.yaml index 8864b69..98b8566 100644 --- a/apps/webapp/openapi-docs.yaml +++ b/apps/webapp/openapi-docs.yaml @@ -459,6 +459,81 @@ components: type: string format: date-time + Log: + type: object + properties: + id: + type: string + description: Log identifier + source: + type: string + description: Log source name + ingestText: + type: string + description: Text content that was ingested + time: + type: string + format: date-time + description: Creation timestamp + processedAt: + type: string + format: date-time + nullable: true + description: Processing completion timestamp + episodeUUID: + type: string + nullable: true + description: Associated episode UUID if processed + status: + type: string + enum: [pending, processing, completed, failed] + description: Processing status + error: + type: string + nullable: true + description: Error message if processing failed + sourceURL: + type: string + format: uri + nullable: true + description: Source URL if applicable + integrationSlug: + type: string + nullable: true + description: Integration slug identifier + data: + type: object + nullable: true + description: Additional log data + + DeleteLogRequest: + type: object + required: + - id + properties: + id: + type: string + description: Log ID to delete + + DeleteLogResponse: + type: object + properties: + success: + type: boolean + message: + type: string + deleted: + type: object + properties: + episode: + type: boolean + statements: + type: integer + entities: + type: integer + facts: + type: integer + paths: # OAuth2 & Authentication /oauth/authorize: @@ -1186,6 +1261,80 @@ paths: pagination: type: object + /api/v1/logs/{logId}: + parameters: + - name: logId + in: path + required: true + schema: + type: string + description: Log identifier + + get: + summary: Get Specific Log + description: Retrieve detailed information about a specific ingestion log + security: + - bearerAuth: [] + responses: + "200": + description: Log details + content: + application/json: + schema: + type: object + properties: + log: + $ref: "#/components/schemas/Log" + "404": + description: Log not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + delete: + summary: Delete Log Entry + description: Delete a specific log entry and associated episode if it exists + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteLogRequest" + responses: + "200": + description: Log deleted successfully + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteLogResponse" + "404": + description: Log not found or unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /api/v1/ingestion-queue/status: get: summary: Get Ingestion Queue Status diff --git a/apps/webapp/openapi.yaml b/apps/webapp/openapi.yaml index c2e2c90..3a453ae 100644 --- a/apps/webapp/openapi.yaml +++ b/apps/webapp/openapi.yaml @@ -459,6 +459,81 @@ components: type: string format: date-time + Log: + type: object + properties: + id: + type: string + description: Log identifier + source: + type: string + description: Log source name + ingestText: + type: string + description: Text content that was ingested + time: + type: string + format: date-time + description: Creation timestamp + processedAt: + type: string + format: date-time + nullable: true + description: Processing completion timestamp + episodeUUID: + type: string + nullable: true + description: Associated episode UUID if processed + status: + type: string + enum: [pending, processing, completed, failed] + description: Processing status + error: + type: string + nullable: true + description: Error message if processing failed + sourceURL: + type: string + format: uri + nullable: true + description: Source URL if applicable + integrationSlug: + type: string + nullable: true + description: Integration slug identifier + data: + type: object + nullable: true + description: Additional log data + + DeleteLogRequest: + type: object + required: + - id + properties: + id: + type: string + description: Log ID to delete + + DeleteLogResponse: + type: object + properties: + success: + type: boolean + message: + type: string + deleted: + type: object + properties: + episode: + type: boolean + statements: + type: integer + entities: + type: integer + facts: + type: integer + paths: # OAuth2 & Authentication /oauth/authorize: @@ -1536,6 +1611,80 @@ paths: pagination: type: object + /api/v1/logs/{logId}: + parameters: + - name: logId + in: path + required: true + schema: + type: string + description: Log identifier + + get: + summary: Get Specific Log + description: Retrieve detailed information about a specific ingestion log + security: + - bearerAuth: [] + responses: + "200": + description: Log details + content: + application/json: + schema: + type: object + properties: + log: + $ref: "#/components/schemas/Log" + "404": + description: Log not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + delete: + summary: Delete Log Entry + description: Delete a specific log entry and associated episode if it exists + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteLogRequest" + responses: + "200": + description: Log deleted successfully + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteLogResponse" + "404": + description: Log not found or unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /api/v1/ingestion-queue/status: get: summary: Get Ingestion Queue Status diff --git a/hosting/docker/.env b/hosting/docker/.env index d7abbd9..b49fc09 100644 --- a/hosting/docker/.env +++ b/hosting/docker/.env @@ -43,7 +43,7 @@ NEO4J_PASSWORD=27192e6432564f4788d55c15131bd5ac NEO4J_AUTH=neo4j/27192e6432564f4788d55c15131bd5ac OPENAI_API_KEY= -OLLAMA_URL=http://ollama:11434 +OLLAMA_URL= EMBEDDING_MODEL=text-embedding-3-small MODEL=gpt-4.1-2025-04-14