From 019a5aaaaadffe8efe63d1e2749d1171d877d5bb Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Mon, 15 Sep 2025 10:54:47 +0530 Subject: [PATCH] bump: new version 0.1.22 --- .env.example | 2 +- .../app/components/logs/log-details.tsx | 299 +++++++++++++----- .../app/components/logs/log-text-collapse.tsx | 16 +- .../app/components/logs/logs-filters.tsx | 66 +++- apps/webapp/app/components/logs/utils.ts | 16 + .../spaces/space-summary.client.tsx | 2 +- apps/webapp/app/env.server.ts | 2 +- apps/webapp/app/hooks/use-logs.tsx | 8 +- apps/webapp/app/routes/api.v1.logs.tsx | 8 + apps/webapp/app/routes/home.inbox.tsx | 16 +- .../app/routes/home.space.$spaceId.facts.tsx | 2 +- .../routes/home.space.$spaceId.patterns.tsx | 2 +- .../app/services/episodeFacts.server.ts | 41 --- .../app/services/graphModels/episode.ts | 2 +- apps/webapp/app/services/mcp.server.ts | 1 - hosting/docker/.env | 2 +- package.json | 2 +- 17 files changed, 324 insertions(+), 163 deletions(-) create mode 100644 apps/webapp/app/components/logs/utils.ts diff --git a/.env.example b/.env.example index 1eae60e..8716d28 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -VERSION=0.1.21 +VERSION=0.1.22 # Nest run in docker, change host to database container name DB_HOST=localhost diff --git a/apps/webapp/app/components/logs/log-details.tsx b/apps/webapp/app/components/logs/log-details.tsx index 85de444..0f65cf5 100644 --- a/apps/webapp/app/components/logs/log-details.tsx +++ b/apps/webapp/app/components/logs/log-details.tsx @@ -1,16 +1,65 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, 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 } from "../ui/badge"; +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 { getStatusColor } from "./utils"; interface LogDetailsProps { - error?: string; log: LogItem; } +interface PropertyItemProps { + label: string; + value?: string | ReactNode; + icon?: ReactNode; + variant?: "default" | "secondary" | "outline" | "status"; + statusColor?: string; + className?: string; +} + +function PropertyItem({ + label, + value, + icon, + variant = "secondary", + statusColor, + className, +}: PropertyItemProps) { + if (!value) return null; + + return ( +
+ {label} + + {variant === "status" ? ( + + {statusColor && ( + + )} + {typeof value === "string" + ? value.charAt(0).toUpperCase() + value.slice(1).toLowerCase() + : value} + + ) : ( + + {icon} + {value} + + )} +
+ ); +} + interface EpisodeFact { uuid: string; fact: string; @@ -24,7 +73,7 @@ interface EpisodeFactsResponse { invalidFacts: EpisodeFact[]; } -export function LogDetails({ error, log }: LogDetailsProps) { +export function LogDetails({ log }: LogDetailsProps) { const [facts, setFacts] = useState([]); const [invalidFacts, setInvalidFacts] = useState([]); const [factsLoading, setFactsLoading] = useState(false); @@ -32,11 +81,35 @@ export function LogDetails({ error, log }: LogDetailsProps) { // Fetch episode facts when dialog opens and episodeUUID exists useEffect(() => { - if (log.episodeUUID && facts.length === 0) { - setFactsLoading(true); - fetcher.load(`/api/v1/episodes/${log.episodeUUID}/facts`); + if (facts.length === 0) { + if (log.data?.type === "DOCUMENT" && log.data?.episodes?.length > 0) { + setFactsLoading(true); + // Fetch facts for all episodes in DOCUMENT type + Promise.all( + log.data.episodes.map((episodeId: string) => + fetch(`/api/v1/episodes/${episodeId}/facts`).then((res) => + res.json(), + ), + ), + ) + .then((results) => { + const allFacts = results.flatMap((result) => result.facts || []); + const allInvalidFacts = results.flatMap( + (result) => result.invalidFacts || [], + ); + setFacts(allFacts); + setInvalidFacts(allInvalidFacts); + setFactsLoading(false); + }) + .catch(() => { + setFactsLoading(false); + }); + } else if (log.episodeUUID) { + setFactsLoading(true); + fetcher.load(`/api/v1/episodes/${log.episodeUUID}/facts`); + } } - }, [log.episodeUUID, facts.length]); + }, [log.episodeUUID, log.data?.type, log.data?.episodes, facts.length]); // Handle fetcher response useEffect(() => { @@ -49,37 +122,80 @@ export function LogDetails({ error, log }: LogDetailsProps) { }, [fetcher.data, fetcher.state]); return ( -
-
-
- Log Details -
- {log.episodeUUID && ( - - Episode: {log.episodeUUID.slice(0, 8)}... - - )} - {log.source && ( - - Source: {log.source} - - )} +
+
+
+
+ Episode Details
-
-
- {/* Log Content */} -
-
- {log.ingestText} +
+
+ {log.data?.type === "DOCUMENT" && log.data?.episodes ? ( + + {log.data.episodes.map( + (episodeId: string, index: number) => ( + + {episodeId} + + ), + )} +
+ } + variant="secondary" + /> + ) : ( + + )} + + + + +
{/* Error Details */} {log.error && ( -
-

Error Details

+
+
+ Error Details +
@@ -92,68 +208,77 @@ export function LogDetails({ error, log }: LogDetailsProps) { )} {/* Episode Facts */} - {log.episodeUUID && ( -
-

Facts

-
- {factsLoading ? ( -
- -
- ) : facts.length > 0 ? ( -
- {facts.map((fact) => ( -
-

{fact.fact}

-
+
+
+ Facts +
+
+ {factsLoading ? ( +
+ +
+ ) : facts.length > 0 ? ( +
+ {facts.map((fact) => ( +
+

{fact.fact}

+
+ + Valid: {new Date(fact.validAt).toLocaleString()} + + {fact.invalidAt && ( - Valid: {new Date(fact.validAt).toLocaleString()} + Invalid: {new Date(fact.invalidAt).toLocaleString()} - {fact.invalidAt && ( - - Invalid: {new Date(fact.invalidAt).toLocaleString()} - - )} - {Object.keys(fact.attributes).length > 0 && ( - - {Object.keys(fact.attributes).length} attributes - - )} -
+ )} + {Object.keys(fact.attributes).length > 0 && ( + + {Object.keys(fact.attributes).length} attributes + + )}
- ))} - {invalidFacts.map((fact) => ( -
-

{fact.fact}

-
- {fact.invalidAt && ( - - Invalid: {new Date(fact.invalidAt).toLocaleString()} - - )} - {Object.keys(fact.attributes).length > 0 && ( - - {Object.keys(fact.attributes).length} attributes - - )} -
+
+ ))} + {invalidFacts.map((fact) => ( +
+

{fact.fact}

+
+ {fact.invalidAt && ( + + Invalid: {new Date(fact.invalidAt).toLocaleString()} + + )} + {Object.keys(fact.attributes).length > 0 && ( + + {Object.keys(fact.attributes).length} attributes + + )}
- ))} -
- ) : ( -
- No facts found for this episode -
- )} +
+ ))} +
+ ) : ( +
+ No facts found for this episode +
+ )} +
+
+ +
+ {/* Log Content */} +
+
+ {log.ingestText}
- )} +
); diff --git a/apps/webapp/app/components/logs/log-text-collapse.tsx b/apps/webapp/app/components/logs/log-text-collapse.tsx index a5ab8d3..2671e10 100644 --- a/apps/webapp/app/components/logs/log-text-collapse.tsx +++ b/apps/webapp/app/components/logs/log-text-collapse.tsx @@ -3,6 +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"; interface LogTextCollapseProps { text?: string; @@ -13,21 +14,6 @@ interface LogTextCollapseProps { reset?: () => void; } -const getStatusColor = (status: string) => { - switch (status) { - case "PROCESSING": - return "bg-blue-800"; - case "PENDING": - return "bg-warning"; - case "FAILED": - return "bg-destructive"; - case "CANCELLED": - return "bg-gray-800"; - default: - return "bg-gray-800"; - } -}; - export function LogTextCollapse({ text, log }: LogTextCollapseProps) { const { logId } = useParams(); const navigate = useNavigate(); diff --git a/apps/webapp/app/components/logs/logs-filters.tsx b/apps/webapp/app/components/logs/logs-filters.tsx index fb6df49..e7017df 100644 --- a/apps/webapp/app/components/logs/logs-filters.tsx +++ b/apps/webapp/app/components/logs/logs-filters.tsx @@ -13,8 +13,10 @@ interface LogsFiltersProps { availableSources: Array<{ name: string; slug: string }>; selectedSource?: string; selectedStatus?: string; + selectedType?: string; onSourceChange: (source?: string) => void; onStatusChange: (status?: string) => void; + onTypeChange: (type?: string) => void; } const statusOptions = [ @@ -23,14 +25,21 @@ const statusOptions = [ { value: "COMPLETED", label: "Completed" }, ]; -type FilterStep = "main" | "source" | "status"; +const typeOptions = [ + { value: "CONVERSATION", label: "Conversation" }, + { value: "DOCUMENT", label: "Document" }, +]; + +type FilterStep = "main" | "source" | "status" | "type"; export function LogsFilters({ availableSources, selectedSource, selectedStatus, + selectedType, onSourceChange, onStatusChange, + onTypeChange, }: LogsFiltersProps) { const [popoverOpen, setPopoverOpen] = useState(false); const [step, setStep] = useState("main"); @@ -44,8 +53,11 @@ export function LogsFilters({ const selectedStatusLabel = statusOptions.find( (s) => s.value === selectedStatus, )?.label; + const selectedTypeLabel = typeOptions.find( + (s) => s.value === selectedType, + )?.label; - const hasFilters = selectedSource || selectedStatus; + const hasFilters = selectedSource || selectedStatus || selectedType; return (
@@ -85,6 +97,13 @@ export function LogsFilters({ > Status +
)} @@ -155,6 +174,40 @@ export function LogsFilters({ ))}
)} + + {step === "type" && ( +
+ + {typeOptions.map((type) => ( + + ))} +
+ )} @@ -180,6 +233,15 @@ export function LogsFilters({ /> )} + {selectedType && ( + + {selectedTypeLabel} + onTypeChange(undefined)} + /> + + )}
)}
diff --git a/apps/webapp/app/components/logs/utils.ts b/apps/webapp/app/components/logs/utils.ts new file mode 100644 index 0000000..39942e2 --- /dev/null +++ b/apps/webapp/app/components/logs/utils.ts @@ -0,0 +1,16 @@ +export const getStatusColor = (status: string) => { + switch (status) { + case "PROCESSING": + return "bg-blue-800"; + case "PENDING": + return "bg-warning"; + case "COMPLETED": + return "bg-success"; + case "FAILED": + return "bg-destructive"; + case "CANCELLED": + return "bg-gray-800"; + default: + return "bg-gray-800"; + } +}; diff --git a/apps/webapp/app/components/spaces/space-summary.client.tsx b/apps/webapp/app/components/spaces/space-summary.client.tsx index affb2b8..e3f15d6 100644 --- a/apps/webapp/app/components/spaces/space-summary.client.tsx +++ b/apps/webapp/app/components/spaces/space-summary.client.tsx @@ -8,7 +8,7 @@ import { extensionsForConversation } from "../conversation/editor-extensions"; export const SpaceSummary = ({ summary }: { summary?: string | null }) => { const editor = useEditor({ extensions: [...extensionsForConversation, skillExtension], - editable: true, + editable: false, content: summary, }); diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index c074a51..01dc727 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -67,7 +67,7 @@ const EnvironmentSchema = z.object({ //OpenAI OPENAI_API_KEY: z.string(), - EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(), + EMAIL_TRANSPORT: z.string().optional(), FROM_EMAIL: z.string().optional(), REPLY_TO_EMAIL: z.string().optional(), RESEND_API_KEY: z.string().optional(), diff --git a/apps/webapp/app/hooks/use-logs.tsx b/apps/webapp/app/hooks/use-logs.tsx index ca4d830..8e0dfb6 100644 --- a/apps/webapp/app/hooks/use-logs.tsx +++ b/apps/webapp/app/hooks/use-logs.tsx @@ -30,9 +30,10 @@ export interface UseLogsOptions { endpoint: string; // '/api/v1/logs/all' or '/api/v1/logs/activity' source?: string; status?: string; + type?: string; } -export function useLogs({ endpoint, source, status }: UseLogsOptions) { +export function useLogs({ endpoint, source, status, type }: UseLogsOptions) { const fetcher = useFetcher(); const [logs, setLogs] = useState([]); const [page, setPage] = useState(1); @@ -49,9 +50,10 @@ export function useLogs({ endpoint, source, status }: UseLogsOptions) { params.set("limit", "5"); if (source) params.set("source", source); if (status) params.set("status", status); + if (type) params.set("type", type); return `${endpoint}?${params.toString()}`; }, - [endpoint, source, status], + [endpoint, source, status, type], ); const loadMore = useCallback(() => { @@ -100,7 +102,7 @@ export function useLogs({ endpoint, source, status }: UseLogsOptions) { setHasMore(true); setIsInitialLoad(true); fetcher.load(buildUrl(1)); - }, [source, status, buildUrl]); // Inline reset logic to avoid dependency issues + }, [source, status, type, buildUrl]); // Inline reset logic to avoid dependency issues // Initial load useEffect(() => { diff --git a/apps/webapp/app/routes/api.v1.logs.tsx b/apps/webapp/app/routes/api.v1.logs.tsx index a8bf7b6..3ae2639 100644 --- a/apps/webapp/app/routes/api.v1.logs.tsx +++ b/apps/webapp/app/routes/api.v1.logs.tsx @@ -18,6 +18,7 @@ export async function loader({ request }: LoaderFunctionArgs) { const limit = parseInt(url.searchParams.get("limit") || "20"); const source = url.searchParams.get("source"); const status = url.searchParams.get("status"); + const type = url.searchParams.get("type"); const skip = (page - 1) * limit; // Get user and workspace in one query @@ -39,6 +40,13 @@ export async function loader({ request }: LoaderFunctionArgs) { whereClause.status = status; } + if (type) { + whereClause.data = { + path: ["type"], + equals: type, + }; + } + // If source filter is provided, filter by integration source if (source) { whereClause.activity = { diff --git a/apps/webapp/app/routes/home.inbox.tsx b/apps/webapp/app/routes/home.inbox.tsx index 28eaf75..5b73d8f 100644 --- a/apps/webapp/app/routes/home.inbox.tsx +++ b/apps/webapp/app/routes/home.inbox.tsx @@ -16,6 +16,7 @@ import { cn } from "~/lib/utils"; export default function LogsAll() { const [selectedSource, setSelectedSource] = useState(); const [selectedStatus, setSelectedStatus] = useState(); + const [selectedType, setSelectedType] = useState(); const { logId } = useParams(); @@ -30,6 +31,7 @@ export default function LogsAll() { endpoint: "/api/v1/logs", source: selectedSource, status: selectedStatus, + type: selectedType, }); return ( @@ -37,15 +39,15 @@ export default function LogsAll() {
-
+
{isInitialLoad ? ( <> @@ -58,12 +60,14 @@ export default function LogsAll() { availableSources={availableSources} selectedSource={selectedSource} selectedStatus={selectedStatus} + selectedType={selectedType} onSourceChange={setSelectedSource} onStatusChange={setSelectedStatus} + onTypeChange={setSelectedType} /> {/* Logs List */} -
+
{logs.length === 0 ? ( @@ -73,7 +77,7 @@ export default function LogsAll() { No logs found

- {selectedSource || selectedStatus + {selectedSource || selectedStatus || selectedType ? "Try adjusting your filters to see more results." : "No ingestion logs are available yet."}

diff --git a/apps/webapp/app/routes/home.space.$spaceId.facts.tsx b/apps/webapp/app/routes/home.space.$spaceId.facts.tsx index ec420db..8d894f7 100644 --- a/apps/webapp/app/routes/home.space.$spaceId.facts.tsx +++ b/apps/webapp/app/routes/home.space.$spaceId.facts.tsx @@ -88,7 +88,7 @@ export default function Facts() { onSpaceFilterChange={setSelectedSpaceFilter} /> -
+
} > diff --git a/apps/webapp/app/routes/home.space.$spaceId.patterns.tsx b/apps/webapp/app/routes/home.space.$spaceId.patterns.tsx index 31d511b..7facdda 100644 --- a/apps/webapp/app/routes/home.space.$spaceId.patterns.tsx +++ b/apps/webapp/app/routes/home.space.$spaceId.patterns.tsx @@ -88,7 +88,7 @@ export default function Patterns() { return (
-
+
} > diff --git a/apps/webapp/app/services/episodeFacts.server.ts b/apps/webapp/app/services/episodeFacts.server.ts index e97c066..e589505 100644 --- a/apps/webapp/app/services/episodeFacts.server.ts +++ b/apps/webapp/app/services/episodeFacts.server.ts @@ -43,44 +43,3 @@ export async function getEpisodeFacts(episodeUuid: string, userId: string) { }; } } - -export async function getDocumentFacts(documentId: string, userId: string) { - try { - const facts = await getEpisodeStatements({ - episodeUuid, - userId, - }); - - const invalidFacts = await getStatementsInvalidatedByEpisode({ - episodeUuid, - userId, - }); - - return { - success: true, - facts: facts.map((fact) => ({ - uuid: fact.uuid, - fact: fact.fact, - createdAt: fact.createdAt.toISOString(), - validAt: fact.validAt.toISOString(), - invalidAt: fact.invalidAt ? fact.invalidAt.toISOString() : null, - attributes: fact.attributes, - })), - invalidFacts: invalidFacts.map((fact) => ({ - uuid: fact.uuid, - fact: fact.fact, - createdAt: fact.createdAt.toISOString(), - validAt: fact.validAt.toISOString(), - invalidAt: fact.invalidAt ? fact.invalidAt.toISOString() : null, - attributes: fact.attributes, - })), - }; - } catch (error) { - console.error("Error fetching episode facts:", error); - return { - success: false, - error: "Failed to fetch episode facts", - facts: [], - }; - } -} diff --git a/apps/webapp/app/services/graphModels/episode.ts b/apps/webapp/app/services/graphModels/episode.ts index 8ff53e3..acf59a5 100644 --- a/apps/webapp/app/services/graphModels/episode.ts +++ b/apps/webapp/app/services/graphModels/episode.ts @@ -1,5 +1,5 @@ import { runQuery } from "~/lib/neo4j.server"; -import { type EntityNode, EpisodeType, type EpisodicNode } from "@core/types"; +import { type EntityNode, type EpisodicNode } from "@core/types"; export async function saveEpisode(episode: EpisodicNode): Promise { const query = ` diff --git a/apps/webapp/app/services/mcp.server.ts b/apps/webapp/app/services/mcp.server.ts index 54f6c74..1327582 100644 --- a/apps/webapp/app/services/mcp.server.ts +++ b/apps/webapp/app/services/mcp.server.ts @@ -14,7 +14,6 @@ import { callMemoryTool, memoryTools } from "~/utils/mcp/memory"; import { logger } from "~/services/logger.service"; import { type Response, type Request } from "express"; import { getWorkspaceByUser } from "~/models/workspace.server"; -import { Workspace } from "@prisma/client"; const QueryParams = z.object({ source: z.string().optional(), diff --git a/hosting/docker/.env b/hosting/docker/.env index 890ce3f..d7abbd9 100644 --- a/hosting/docker/.env +++ b/hosting/docker/.env @@ -1,4 +1,4 @@ -VERSION=0.1.21 +VERSION=0.1.22 # Nest run in docker, change host to database container name DB_HOST=postgres diff --git a/package.json b/package.json index 6103f3f..bdb5348 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "core", "private": true, - "version": "0.1.21", + "version": "0.1.22", "workspaces": [ "apps/*", "packages/*"