fix: UI for document logs

feat: added logs API to delete the episode
This commit is contained in:
Harshith Mullapudi 2025-09-18 23:21:26 +05:30
parent 43c3482351
commit 840ca64174
18 changed files with 515 additions and 159 deletions

View File

@ -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],
);

View File

@ -114,36 +114,6 @@ export const GraphVisualization = forwardRef<GraphRef, GraphVisualizationProps>(
return (
<div className={className}>
{/* Entity Types Legend Button */}
<div className="absolute top-4 left-4 z-50">
{/* <HoverCard>
<HoverCardTrigger asChild>
<button className="bg-primary/10 text-primary hover:bg-primary/20 rounded-md px-2.5 py-1 text-xs transition-colors">
Entity Types
</button>
</HoverCardTrigger>
<HoverCardContent className="w-40" side="bottom" align="start">
<div className="space-y-2">
<div className="max-h-[300px] space-y-1.5 overflow-y-auto pr-2">
{allLabels.map((label) => (
<div key={label} className="flex items-center gap-2">
<div
className="h-4 w-4 flex-shrink-0 rounded-full"
style={{
backgroundColor: getNodeColor(
label,
isDarkMode,
sharedLabelColorMap,
),
}}
/>
<span className="text-xs">{label}</span>
</div>
))}
</div>
</div>
</HoverCardContent>
</HoverCard> */}
</div>
{triplets.length > 0 ? (
<Graph

View File

@ -1,13 +1,13 @@
import { useState, useEffect, type ReactNode } from "react";
import { useFetcher } from "@remix-run/react";
import { AlertCircle, Loader2 } from "lucide-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
import { Badge, BadgeColor } from "../ui/badge";
import { type LogItem } from "~/hooks/use-logs";
import Markdown from "react-markdown";
import { getIconForAuthorise } from "../icon-utils";
import { cn } from "~/lib/utils";
import { cn, formatString } from "~/lib/utils";
import { getStatusColor } from "./utils";
import { format } from "date-fns";
interface LogDetailsProps {
log: LogItem;
@ -46,9 +46,7 @@ function PropertyItem({
{statusColor && (
<BadgeColor className={cn(statusColor, "h-2.5 w-2.5")} />
)}
{typeof value === "string"
? value.charAt(0).toUpperCase() + value.slice(1).toLowerCase()
: value}
{value}
</Badge>
) : (
<Badge variant={variant} className={cn("h-7 rounded px-4", className)}>
@ -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<any[]>([]);
const [invalidFacts, setInvalidFacts] = useState<any[]>([]);
@ -122,8 +128,8 @@ export function LogDetails({ log }: LogDetailsProps) {
}, [fetcher.data, fetcher.state]);
return (
<div className="flex w-full flex-col items-center">
<div className="w-4xl">
<div className="flex h-full w-full flex-col items-center overflow-auto">
<div className="max-w-4xl">
<div className="px-4 pt-4">
<div className="mb-4 flex w-full items-center justify-between">
<span>Episode Details</span>
@ -131,7 +137,7 @@ export function LogDetails({ log }: LogDetailsProps) {
</div>
<div className="mb-10 px-4">
<div className="space-y-3">
<div className="space-y-1">
{log.data?.type === "DOCUMENT" && log.data?.episodes ? (
<PropertyItem
label="Episodes"
@ -166,14 +172,14 @@ export function LogDetails({ log }: LogDetailsProps) {
/>
<PropertyItem
label="Type"
value={
log.data?.type ? log.data.type.toLowerCase() : "conversation"
}
value={formatString(
log.data?.type ? log.data.type.toLowerCase() : "conversation",
)}
variant="secondary"
/>
<PropertyItem
label="Source"
value={log.source?.toLowerCase()}
value={formatString(log.source?.toLowerCase())}
icon={
log.source &&
getIconForAuthorise(log.source.toLowerCase(), 16, undefined)
@ -183,7 +189,7 @@ export function LogDetails({ log }: LogDetailsProps) {
<PropertyItem
label="Status"
value={log.status}
value={getStatusValue(log.status)}
variant="status"
statusColor={log.status && getStatusColor(log.status)}
/>
@ -207,6 +213,18 @@ export function LogDetails({ log }: LogDetailsProps) {
</div>
)}
<div className="flex flex-col items-center p-4 pt-0">
<div className="mb-2 flex w-full items-center justify-between">
<span>Content</span>
</div>
{/* Log Content */}
<div className="mb-4 text-sm break-words whitespace-pre-wrap">
<div className="rounded-md">
<Markdown>{log.ingestText}</Markdown>
</div>
</div>
</div>
{/* Episode Facts */}
<div className="mb-6 px-4">
<div className="mb-2 flex w-full items-center justify-between">
@ -218,20 +236,21 @@ export function LogDetails({ log }: LogDetailsProps) {
<Loader2 className="h-4 w-4 animate-spin" />
</div>
) : facts.length > 0 ? (
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
{facts.map((fact) => (
<div
key={fact.uuid}
className="bg-grayAlpha-100 rounded-md p-3"
className="bg-grayAlpha-100 flex items-center justify-between gap-2 rounded-md p-3"
>
<p className="mb-1 text-sm">{fact.fact}</p>
<div className="text-muted-foreground flex items-center gap-2 text-xs">
<p className="text-sm">{fact.fact}</p>
<div className="text-muted-foreground flex shrink-0 items-center gap-2 text-xs">
<span>
Valid: {new Date(fact.validAt).toLocaleString()}
Valid: {format(new Date(fact.validAt), "dd/MM/yyyy")}
</span>
{fact.invalidAt && (
<span>
Invalid: {new Date(fact.invalidAt).toLocaleString()}
Invalid:{" "}
{format(new Date(fact.invalidAt), "dd/MM/yyyy")}
</span>
)}
{Object.keys(fact.attributes).length > 0 && (
@ -270,15 +289,6 @@ export function LogDetails({ log }: LogDetailsProps) {
)}
</div>
</div>
<div className="flex max-h-[88vh] flex-col items-center overflow-auto p-4 pt-0">
{/* Log Content */}
<div className="mb-4 text-sm break-words whitespace-pre-wrap">
<div className="rounded-md">
<Markdown>{log.ingestText}</Markdown>
</div>
</div>
</div>
</div>
</div>
);

View File

@ -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]);

View File

@ -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) {
)}
>
<BadgeColor className={cn(getStatusColor(log.status))} />
{log.status.charAt(0).toUpperCase() +
log.status.slice(1).toLowerCase()}
{getStatusValue(log.status)}
</Badge>
</div>
</div>

View File

@ -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;
}

View File

@ -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(),
});

View File

@ -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

View File

@ -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(" ");
}

View File

@ -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) {

View File

@ -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 };

View File

@ -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({

View File

@ -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") || "";

View File

@ -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 (
<div className="flex h-full w-full flex-col">
<PageHeader title="Episode" showTrigger={false} />
<div className="flex h-[calc(100vh_-_20px)] w-full flex-col overflow-hidden">
<PageHeader
title="Episode"
showTrigger={false}
actionsNode={<LogOptions id={log.id} />}
/>
<LogDetails log={log as any} />
</div>

View File

@ -56,7 +56,7 @@ export class SpaceService {
name: params.name.trim(),
description: params.description?.trim(),
workspaceId: params.workspaceId,
status: "pending",
status: "ready",
},
});

View File

@ -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

View File

@ -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

View File

@ -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