mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 18:08:27 +00:00
bump: new version 0.1.22
This commit is contained in:
parent
8a4ada2c09
commit
019a5aaaaa
@ -1,4 +1,4 @@
|
|||||||
VERSION=0.1.21
|
VERSION=0.1.22
|
||||||
|
|
||||||
# Nest run in docker, change host to database container name
|
# Nest run in docker, change host to database container name
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
|
|||||||
@ -1,16 +1,65 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, ReactNode } from "react";
|
||||||
import { useFetcher } from "@remix-run/react";
|
import { useFetcher } from "@remix-run/react";
|
||||||
import { AlertCircle, Loader2 } from "lucide-react";
|
import { AlertCircle, Loader2 } from "lucide-react";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
|
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 { type LogItem } from "~/hooks/use-logs";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
|
import { getIconForAuthorise } from "../icon-utils";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
import { getStatusColor } from "./utils";
|
||||||
|
|
||||||
interface LogDetailsProps {
|
interface LogDetailsProps {
|
||||||
error?: string;
|
|
||||||
log: LogItem;
|
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 (
|
||||||
|
<div className="flex items-center py-1">
|
||||||
|
<span className="text-muted-foreground min-w-[160px]">{label}</span>
|
||||||
|
|
||||||
|
{variant === "status" ? (
|
||||||
|
<Badge
|
||||||
|
className={cn(
|
||||||
|
"!bg-grayAlpha-100 text-muted-foreground h-7 rounded px-4 text-xs",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{statusColor && (
|
||||||
|
<BadgeColor className={cn(statusColor, "h-2.5 w-2.5")} />
|
||||||
|
)}
|
||||||
|
{typeof value === "string"
|
||||||
|
? value.charAt(0).toUpperCase() + value.slice(1).toLowerCase()
|
||||||
|
: value}
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge variant={variant} className={cn("h-7 rounded px-4", className)}>
|
||||||
|
{icon}
|
||||||
|
{value}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface EpisodeFact {
|
interface EpisodeFact {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
fact: string;
|
fact: string;
|
||||||
@ -24,7 +73,7 @@ interface EpisodeFactsResponse {
|
|||||||
invalidFacts: EpisodeFact[];
|
invalidFacts: EpisodeFact[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LogDetails({ error, log }: LogDetailsProps) {
|
export function LogDetails({ log }: LogDetailsProps) {
|
||||||
const [facts, setFacts] = useState<any[]>([]);
|
const [facts, setFacts] = useState<any[]>([]);
|
||||||
const [invalidFacts, setInvalidFacts] = useState<any[]>([]);
|
const [invalidFacts, setInvalidFacts] = useState<any[]>([]);
|
||||||
const [factsLoading, setFactsLoading] = useState(false);
|
const [factsLoading, setFactsLoading] = useState(false);
|
||||||
@ -32,11 +81,35 @@ export function LogDetails({ error, log }: LogDetailsProps) {
|
|||||||
|
|
||||||
// Fetch episode facts when dialog opens and episodeUUID exists
|
// Fetch episode facts when dialog opens and episodeUUID exists
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (log.episodeUUID && facts.length === 0) {
|
if (facts.length === 0) {
|
||||||
setFactsLoading(true);
|
if (log.data?.type === "DOCUMENT" && log.data?.episodes?.length > 0) {
|
||||||
fetcher.load(`/api/v1/episodes/${log.episodeUUID}/facts`);
|
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
|
// Handle fetcher response
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -49,37 +122,80 @@ export function LogDetails({ error, log }: LogDetailsProps) {
|
|||||||
}, [fetcher.data, fetcher.state]);
|
}, [fetcher.data, fetcher.state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl">
|
<div className="flex w-full flex-col items-center">
|
||||||
<div className="px-4 pt-4">
|
<div className="w-4xl">
|
||||||
<div className="mb-4 flex w-full items-center justify-between">
|
<div className="px-4 pt-4">
|
||||||
<span>Log Details</span>
|
<div className="mb-4 flex w-full items-center justify-between">
|
||||||
<div className="flex gap-0.5">
|
<span>Episode Details</span>
|
||||||
{log.episodeUUID && (
|
|
||||||
<Badge variant="secondary" className="rounded text-xs">
|
|
||||||
Episode: {log.episodeUUID.slice(0, 8)}...
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{log.source && (
|
|
||||||
<Badge variant="secondary" className="rounded text-xs">
|
|
||||||
Source: {log.source}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-h-[90vh] overflow-auto p-4 pt-0">
|
<div className="mb-10 px-4">
|
||||||
{/* Log Content */}
|
<div className="space-y-3">
|
||||||
<div className="mb-4 text-sm break-words whitespace-pre-wrap">
|
{log.data?.type === "DOCUMENT" && log.data?.episodes ? (
|
||||||
<div className="rounded-md">
|
<PropertyItem
|
||||||
<Markdown>{log.ingestText}</Markdown>
|
label="Episodes"
|
||||||
|
value={
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{log.data.episodes.map(
|
||||||
|
(episodeId: string, index: number) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{episodeId}
|
||||||
|
</Badge>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PropertyItem
|
||||||
|
label="Episode Id"
|
||||||
|
value={log.episodeUUID}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<PropertyItem
|
||||||
|
label="Session Id"
|
||||||
|
value={log.data?.sessionId?.toLowerCase()}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
<PropertyItem
|
||||||
|
label="Type"
|
||||||
|
value={
|
||||||
|
log.data?.type ? log.data.type.toLowerCase() : "conversation"
|
||||||
|
}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
<PropertyItem
|
||||||
|
label="Source"
|
||||||
|
value={log.source?.toLowerCase()}
|
||||||
|
icon={
|
||||||
|
log.source &&
|
||||||
|
getIconForAuthorise(log.source.toLowerCase(), 16, undefined)
|
||||||
|
}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PropertyItem
|
||||||
|
label="Status"
|
||||||
|
value={log.status}
|
||||||
|
variant="status"
|
||||||
|
statusColor={log.status && getStatusColor(log.status)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Error Details */}
|
{/* Error Details */}
|
||||||
{log.error && (
|
{log.error && (
|
||||||
<div className="mb-4">
|
<div className="mb-6 px-4">
|
||||||
<h3 className="mb-2 text-sm font-medium">Error Details</h3>
|
<div className="mb-2 flex w-full items-center justify-between">
|
||||||
|
<span>Error Details</span>
|
||||||
|
</div>
|
||||||
<div className="bg-destructive/10 rounded-md p-3">
|
<div className="bg-destructive/10 rounded-md p-3">
|
||||||
<div className="flex items-start gap-2 text-red-600">
|
<div className="flex items-start gap-2 text-red-600">
|
||||||
<AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
<AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||||
@ -92,68 +208,77 @@ export function LogDetails({ error, log }: LogDetailsProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Episode Facts */}
|
{/* Episode Facts */}
|
||||||
{log.episodeUUID && (
|
<div className="mb-6 px-4">
|
||||||
<div className="mb-4">
|
<div className="mb-2 flex w-full items-center justify-between">
|
||||||
<h3 className="text-muted-foreground mb-2 text-sm">Facts</h3>
|
<span>Facts</span>
|
||||||
<div className="rounded-md">
|
</div>
|
||||||
{factsLoading ? (
|
<div className="rounded-md">
|
||||||
<div className="flex items-center justify-center gap-2 p-4 text-sm">
|
{factsLoading ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<div className="flex items-center justify-center gap-2 p-4 text-sm">
|
||||||
</div>
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
) : facts.length > 0 ? (
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
) : facts.length > 0 ? (
|
||||||
{facts.map((fact) => (
|
<div className="flex flex-col gap-2">
|
||||||
<div
|
{facts.map((fact) => (
|
||||||
key={fact.uuid}
|
<div
|
||||||
className="bg-grayAlpha-100 rounded-md p-3"
|
key={fact.uuid}
|
||||||
>
|
className="bg-grayAlpha-100 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="mb-1 text-sm">{fact.fact}</p>
|
||||||
|
<div className="text-muted-foreground flex items-center gap-2 text-xs">
|
||||||
|
<span>
|
||||||
|
Valid: {new Date(fact.validAt).toLocaleString()}
|
||||||
|
</span>
|
||||||
|
{fact.invalidAt && (
|
||||||
<span>
|
<span>
|
||||||
Valid: {new Date(fact.validAt).toLocaleString()}
|
Invalid: {new Date(fact.invalidAt).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
{fact.invalidAt && (
|
)}
|
||||||
<span>
|
{Object.keys(fact.attributes).length > 0 && (
|
||||||
Invalid: {new Date(fact.invalidAt).toLocaleString()}
|
<Badge variant="secondary" className="text-xs">
|
||||||
</span>
|
{Object.keys(fact.attributes).length} attributes
|
||||||
)}
|
</Badge>
|
||||||
{Object.keys(fact.attributes).length > 0 && (
|
)}
|
||||||
<Badge variant="secondary" className="text-xs">
|
|
||||||
{Object.keys(fact.attributes).length} attributes
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
{invalidFacts.map((fact) => (
|
))}
|
||||||
<div
|
{invalidFacts.map((fact) => (
|
||||||
key={fact.uuid}
|
<div
|
||||||
className="bg-grayAlpha-100 rounded-md p-3"
|
key={fact.uuid}
|
||||||
>
|
className="bg-grayAlpha-100 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="mb-1 text-sm">{fact.fact}</p>
|
||||||
{fact.invalidAt && (
|
<div className="text-muted-foreground flex items-center gap-2 text-xs">
|
||||||
<span>
|
{fact.invalidAt && (
|
||||||
Invalid: {new Date(fact.invalidAt).toLocaleString()}
|
<span>
|
||||||
</span>
|
Invalid: {new Date(fact.invalidAt).toLocaleString()}
|
||||||
)}
|
</span>
|
||||||
{Object.keys(fact.attributes).length > 0 && (
|
)}
|
||||||
<Badge variant="secondary" className="text-xs">
|
{Object.keys(fact.attributes).length > 0 && (
|
||||||
{Object.keys(fact.attributes).length} attributes
|
<Badge variant="secondary" className="text-xs">
|
||||||
</Badge>
|
{Object.keys(fact.attributes).length} attributes
|
||||||
)}
|
</Badge>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
))}
|
||||||
) : (
|
</div>
|
||||||
<div className="text-muted-foreground p-4 text-center text-sm">
|
) : (
|
||||||
No facts found for this episode
|
<div className="text-muted-foreground p-4 text-center text-sm">
|
||||||
</div>
|
No facts found for this episode
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Badge, BadgeColor } from "../ui/badge";
|
|||||||
import { type LogItem } from "~/hooks/use-logs";
|
import { type LogItem } from "~/hooks/use-logs";
|
||||||
import { getIconForAuthorise } from "../icon-utils";
|
import { getIconForAuthorise } from "../icon-utils";
|
||||||
import { useNavigate, useParams } from "@remix-run/react";
|
import { useNavigate, useParams } from "@remix-run/react";
|
||||||
|
import { getStatusColor } from "./utils";
|
||||||
|
|
||||||
interface LogTextCollapseProps {
|
interface LogTextCollapseProps {
|
||||||
text?: string;
|
text?: string;
|
||||||
@ -13,21 +14,6 @@ interface LogTextCollapseProps {
|
|||||||
reset?: () => void;
|
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) {
|
export function LogTextCollapse({ text, log }: LogTextCollapseProps) {
|
||||||
const { logId } = useParams();
|
const { logId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@ -13,8 +13,10 @@ interface LogsFiltersProps {
|
|||||||
availableSources: Array<{ name: string; slug: string }>;
|
availableSources: Array<{ name: string; slug: string }>;
|
||||||
selectedSource?: string;
|
selectedSource?: string;
|
||||||
selectedStatus?: string;
|
selectedStatus?: string;
|
||||||
|
selectedType?: string;
|
||||||
onSourceChange: (source?: string) => void;
|
onSourceChange: (source?: string) => void;
|
||||||
onStatusChange: (status?: string) => void;
|
onStatusChange: (status?: string) => void;
|
||||||
|
onTypeChange: (type?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
@ -23,14 +25,21 @@ const statusOptions = [
|
|||||||
{ value: "COMPLETED", label: "Completed" },
|
{ 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({
|
export function LogsFilters({
|
||||||
availableSources,
|
availableSources,
|
||||||
selectedSource,
|
selectedSource,
|
||||||
selectedStatus,
|
selectedStatus,
|
||||||
|
selectedType,
|
||||||
onSourceChange,
|
onSourceChange,
|
||||||
onStatusChange,
|
onStatusChange,
|
||||||
|
onTypeChange,
|
||||||
}: LogsFiltersProps) {
|
}: LogsFiltersProps) {
|
||||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||||
const [step, setStep] = useState<FilterStep>("main");
|
const [step, setStep] = useState<FilterStep>("main");
|
||||||
@ -44,8 +53,11 @@ export function LogsFilters({
|
|||||||
const selectedStatusLabel = statusOptions.find(
|
const selectedStatusLabel = statusOptions.find(
|
||||||
(s) => s.value === selectedStatus,
|
(s) => s.value === selectedStatus,
|
||||||
)?.label;
|
)?.label;
|
||||||
|
const selectedTypeLabel = typeOptions.find(
|
||||||
|
(s) => s.value === selectedType,
|
||||||
|
)?.label;
|
||||||
|
|
||||||
const hasFilters = selectedSource || selectedStatus;
|
const hasFilters = selectedSource || selectedStatus || selectedType;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-2 flex w-full items-center justify-start gap-2 px-3">
|
<div className="mb-2 flex w-full items-center justify-start gap-2 px-3">
|
||||||
@ -85,6 +97,13 @@ export function LogsFilters({
|
|||||||
>
|
>
|
||||||
Status
|
Status
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="justify-start"
|
||||||
|
onClick={() => setStep("type")}
|
||||||
|
>
|
||||||
|
Type
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -155,6 +174,40 @@ export function LogsFilters({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{step === "type" && (
|
||||||
|
<div className="flex flex-col gap-1 p-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-start"
|
||||||
|
onClick={() => {
|
||||||
|
onTypeChange(undefined);
|
||||||
|
setPopoverOpen(false);
|
||||||
|
setStep("main");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
All types
|
||||||
|
</Button>
|
||||||
|
{typeOptions.map((type) => (
|
||||||
|
<Button
|
||||||
|
key={type.value}
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-start"
|
||||||
|
onClick={() => {
|
||||||
|
onTypeChange(
|
||||||
|
type.value === selectedType
|
||||||
|
? undefined
|
||||||
|
: type.value,
|
||||||
|
);
|
||||||
|
setPopoverOpen(false);
|
||||||
|
setStep("main");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{type.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</PopoverPortal>
|
</PopoverPortal>
|
||||||
</Popover>
|
</Popover>
|
||||||
@ -180,6 +233,15 @@ export function LogsFilters({
|
|||||||
/>
|
/>
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
{selectedType && (
|
||||||
|
<Badge variant="secondary" className="h-7 gap-1 rounded px-2">
|
||||||
|
{selectedTypeLabel}
|
||||||
|
<X
|
||||||
|
className="hover:text-destructive h-3.5 w-3.5 cursor-pointer"
|
||||||
|
onClick={() => onTypeChange(undefined)}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
16
apps/webapp/app/components/logs/utils.ts
Normal file
16
apps/webapp/app/components/logs/utils.ts
Normal file
@ -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";
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -8,7 +8,7 @@ import { extensionsForConversation } from "../conversation/editor-extensions";
|
|||||||
export const SpaceSummary = ({ summary }: { summary?: string | null }) => {
|
export const SpaceSummary = ({ summary }: { summary?: string | null }) => {
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [...extensionsForConversation, skillExtension],
|
extensions: [...extensionsForConversation, skillExtension],
|
||||||
editable: true,
|
editable: false,
|
||||||
content: summary,
|
content: summary,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,7 @@ const EnvironmentSchema = z.object({
|
|||||||
//OpenAI
|
//OpenAI
|
||||||
OPENAI_API_KEY: z.string(),
|
OPENAI_API_KEY: z.string(),
|
||||||
|
|
||||||
EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(),
|
EMAIL_TRANSPORT: z.string().optional(),
|
||||||
FROM_EMAIL: z.string().optional(),
|
FROM_EMAIL: z.string().optional(),
|
||||||
REPLY_TO_EMAIL: z.string().optional(),
|
REPLY_TO_EMAIL: z.string().optional(),
|
||||||
RESEND_API_KEY: z.string().optional(),
|
RESEND_API_KEY: z.string().optional(),
|
||||||
|
|||||||
@ -30,9 +30,10 @@ export interface UseLogsOptions {
|
|||||||
endpoint: string; // '/api/v1/logs/all' or '/api/v1/logs/activity'
|
endpoint: string; // '/api/v1/logs/all' or '/api/v1/logs/activity'
|
||||||
source?: string;
|
source?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLogs({ endpoint, source, status }: UseLogsOptions) {
|
export function useLogs({ endpoint, source, status, type }: UseLogsOptions) {
|
||||||
const fetcher = useFetcher<LogsResponse>();
|
const fetcher = useFetcher<LogsResponse>();
|
||||||
const [logs, setLogs] = useState<LogItem[]>([]);
|
const [logs, setLogs] = useState<LogItem[]>([]);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
@ -49,9 +50,10 @@ export function useLogs({ endpoint, source, status }: UseLogsOptions) {
|
|||||||
params.set("limit", "5");
|
params.set("limit", "5");
|
||||||
if (source) params.set("source", source);
|
if (source) params.set("source", source);
|
||||||
if (status) params.set("status", status);
|
if (status) params.set("status", status);
|
||||||
|
if (type) params.set("type", type);
|
||||||
return `${endpoint}?${params.toString()}`;
|
return `${endpoint}?${params.toString()}`;
|
||||||
},
|
},
|
||||||
[endpoint, source, status],
|
[endpoint, source, status, type],
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
@ -100,7 +102,7 @@ export function useLogs({ endpoint, source, status }: UseLogsOptions) {
|
|||||||
setHasMore(true);
|
setHasMore(true);
|
||||||
setIsInitialLoad(true);
|
setIsInitialLoad(true);
|
||||||
fetcher.load(buildUrl(1));
|
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
|
// Initial load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
const limit = parseInt(url.searchParams.get("limit") || "20");
|
const limit = parseInt(url.searchParams.get("limit") || "20");
|
||||||
const source = url.searchParams.get("source");
|
const source = url.searchParams.get("source");
|
||||||
const status = url.searchParams.get("status");
|
const status = url.searchParams.get("status");
|
||||||
|
const type = url.searchParams.get("type");
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
// Get user and workspace in one query
|
// Get user and workspace in one query
|
||||||
@ -39,6 +40,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
whereClause.status = status;
|
whereClause.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
whereClause.data = {
|
||||||
|
path: ["type"],
|
||||||
|
equals: type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// If source filter is provided, filter by integration source
|
// If source filter is provided, filter by integration source
|
||||||
if (source) {
|
if (source) {
|
||||||
whereClause.activity = {
|
whereClause.activity = {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { cn } from "~/lib/utils";
|
|||||||
export default function LogsAll() {
|
export default function LogsAll() {
|
||||||
const [selectedSource, setSelectedSource] = useState<string | undefined>();
|
const [selectedSource, setSelectedSource] = useState<string | undefined>();
|
||||||
const [selectedStatus, setSelectedStatus] = useState<string | undefined>();
|
const [selectedStatus, setSelectedStatus] = useState<string | undefined>();
|
||||||
|
const [selectedType, setSelectedType] = useState<string | undefined>();
|
||||||
|
|
||||||
const { logId } = useParams();
|
const { logId } = useParams();
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ export default function LogsAll() {
|
|||||||
endpoint: "/api/v1/logs",
|
endpoint: "/api/v1/logs",
|
||||||
source: selectedSource,
|
source: selectedSource,
|
||||||
status: selectedStatus,
|
status: selectedStatus,
|
||||||
|
type: selectedType,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -37,15 +39,15 @@ export default function LogsAll() {
|
|||||||
<ResizablePanelGroup direction="horizontal">
|
<ResizablePanelGroup direction="horizontal">
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
maxSize={50}
|
maxSize={50}
|
||||||
defaultSize={35}
|
defaultSize={30}
|
||||||
minSize={35}
|
minSize={30}
|
||||||
collapsible
|
collapsible
|
||||||
collapsedSize={35}
|
collapsedSize={30}
|
||||||
>
|
>
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<PageHeader title="Inbox" />
|
<PageHeader title="Inbox" />
|
||||||
|
|
||||||
<div className="flex h-[calc(100vh_-_56px)] w-full flex-col items-center space-y-6 pt-3 pb-4">
|
<div className="flex h-[calc(100vh_-_56px)] w-full flex-col items-center space-y-6 pt-3">
|
||||||
{isInitialLoad ? (
|
{isInitialLoad ? (
|
||||||
<>
|
<>
|
||||||
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />
|
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />
|
||||||
@ -58,12 +60,14 @@ export default function LogsAll() {
|
|||||||
availableSources={availableSources}
|
availableSources={availableSources}
|
||||||
selectedSource={selectedSource}
|
selectedSource={selectedSource}
|
||||||
selectedStatus={selectedStatus}
|
selectedStatus={selectedStatus}
|
||||||
|
selectedType={selectedType}
|
||||||
onSourceChange={setSelectedSource}
|
onSourceChange={setSelectedSource}
|
||||||
onStatusChange={setSelectedStatus}
|
onStatusChange={setSelectedStatus}
|
||||||
|
onTypeChange={setSelectedType}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Logs List */}
|
{/* Logs List */}
|
||||||
<div className="flex h-full w-full space-y-4">
|
<div className="flex h-full w-full space-y-4 pb-2">
|
||||||
{logs.length === 0 ? (
|
{logs.length === 0 ? (
|
||||||
<Card className="bg-background-2 w-full">
|
<Card className="bg-background-2 w-full">
|
||||||
<CardContent className="bg-background-2 flex w-full items-center justify-center py-16">
|
<CardContent className="bg-background-2 flex w-full items-center justify-center py-16">
|
||||||
@ -73,7 +77,7 @@ export default function LogsAll() {
|
|||||||
No logs found
|
No logs found
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
{selectedSource || selectedStatus
|
{selectedSource || selectedStatus || selectedType
|
||||||
? "Try adjusting your filters to see more results."
|
? "Try adjusting your filters to see more results."
|
||||||
: "No ingestion logs are available yet."}
|
: "No ingestion logs are available yet."}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export default function Facts() {
|
|||||||
onSpaceFilterChange={setSelectedSpaceFilter}
|
onSpaceFilterChange={setSelectedSpaceFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex h-[calc(100vh_-_140px)] w-full">
|
<div className="flex h-[calc(100vh_-_56px)] w-full">
|
||||||
<ClientOnly
|
<ClientOnly
|
||||||
fallback={<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
|
fallback={<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export default function Patterns() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col pt-2">
|
<div className="flex h-full w-full flex-col pt-2">
|
||||||
<div className="flex h-[calc(100vh_-_140px)] w-full">
|
<div className="flex h-[calc(100vh_-_56px)] w-full">
|
||||||
<ClientOnly
|
<ClientOnly
|
||||||
fallback={<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
|
fallback={<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { runQuery } from "~/lib/neo4j.server";
|
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<string> {
|
export async function saveEpisode(episode: EpisodicNode): Promise<string> {
|
||||||
const query = `
|
const query = `
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { callMemoryTool, memoryTools } from "~/utils/mcp/memory";
|
|||||||
import { logger } from "~/services/logger.service";
|
import { logger } from "~/services/logger.service";
|
||||||
import { type Response, type Request } from "express";
|
import { type Response, type Request } from "express";
|
||||||
import { getWorkspaceByUser } from "~/models/workspace.server";
|
import { getWorkspaceByUser } from "~/models/workspace.server";
|
||||||
import { Workspace } from "@prisma/client";
|
|
||||||
|
|
||||||
const QueryParams = z.object({
|
const QueryParams = z.object({
|
||||||
source: z.string().optional(),
|
source: z.string().optional(),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
VERSION=0.1.21
|
VERSION=0.1.22
|
||||||
|
|
||||||
# Nest run in docker, change host to database container name
|
# Nest run in docker, change host to database container name
|
||||||
DB_HOST=postgres
|
DB_HOST=postgres
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "core",
|
"name": "core",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.21",
|
"version": "0.1.22",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user