This commit is contained in:
Harshith Mullapudi 2025-07-07 12:54:42 +05:30
parent a819a682a2
commit fa8d2064e1
48 changed files with 2658 additions and 1088 deletions

View File

@ -6,7 +6,7 @@ POSTGRES_PASSWORD=docker
POSTGRES_DB=core
LOGIN_ORIGIN=http://localhost:3000
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=echo"
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=core"
# This sets the URL used for direct connections to the database and should only be needed in limited circumstances
# See: https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#fields:~:text=the%20shadow%20database.-,directUrl,-No
@ -41,5 +41,5 @@ MAGIC_LINK_SECRET=27192e6432564f4788d55c15131bd5ac
NEO4J_AUTH=neo4j/27192e6432564f4788d55c15131bd5ac
OLLAMA_URL=http://ollama:11434
EMBEDDING_MODEL=bge-m3
EMBEDDING_MODEL=GPT41
MODEL=GPT41

6
.gitignore vendored
View File

@ -37,4 +37,8 @@ yarn-error.log*
.DS_Store
*.pem
docker-compose.dev.yaml
docker-compose.dev.yaml
clickhouse/
.vscode/
registry/

57
LICENSE
View File

@ -1,21 +1,44 @@
MIT License
Sol License
Copyright (c) 2024 Poozle Inc
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Copyright (c) 2025 — Poozle Inc.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Additional Terms:
As additional permission under GNU AGPL version 3 section 7, you
may combine or link a "work that uses the Library" with a publicly
distributed version of this library to produce a combined library or
application, then distribute that combined work under the terms of
your choice, with no requirement to comply with the obligations
normally placed on you by section 4 of the GNU AGPL version 3
(or the corresponding section of a later version of the GNU AGPL
version 3 license).
"Commons Clause" License Condition v1.0
The Software is provided to you by the Licensor under the License (defined below), subject to the following condition:
Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software.
For purposes of the foregoing, "Sell" means practicing any or all of the rights granted to you under the License to provide the Software to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/support services related to the Software), as part of a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice.
Software: All files in this repository.
License: GNU Affero General Public License v3.0
Licensor: Poozle Inc.

View File

@ -3,3 +3,5 @@ node_modules
/.cache
/build
.env
.trigger

View File

@ -48,7 +48,7 @@ type DisplayOptionsProps = {
export function ErrorDisplay({ title, message, button }: DisplayOptionsProps) {
return (
<div className="bg-background relative flex min-h-screen flex-col items-center justify-center">
<div className="bg-background-2 relative flex min-h-screen flex-col items-center justify-center">
<div className="z-10 mt-[30vh] flex flex-col items-center gap-8">
<Header1>{title}</Header1>
{message && <Paragraph>{message}</Paragraph>}

View File

@ -0,0 +1,20 @@
import { type GraphVisualizationProps } from "./graph-visualization";
import { useState, useMemo, forwardRef, useRef, useEffect } from "react";
export function GraphVisualizationClient(props: GraphVisualizationProps) {
const [Component, setComponent] = useState<any>(undefined);
useEffect(() => {
if (typeof window === "undefined") return;
import("./graph-visualization").then(({ GraphVisualization }) => {
setComponent(GraphVisualization);
});
}, []);
if (!Component) {
return null;
}
return <Component {...props} />;
}

View File

@ -93,7 +93,7 @@ export function GraphPopovers({
<div className="pointer-events-none h-4 w-4" />
</PopoverTrigger>
<PopoverContent
className="w-80 overflow-hidden"
className="h-60 max-w-80 overflow-auto"
side="bottom"
align="end"
sideOffset={5}

View File

@ -1,4 +1,4 @@
import { useState, useMemo, forwardRef } from "react";
import { useState, useMemo, forwardRef, useRef, useEffect } from "react";
import { Graph, type GraphRef } from "./graph";
import { GraphPopovers } from "./graph-popover";
import type { RawTriplet, NodePopupContent, EdgePopupContent } from "./type";
@ -7,7 +7,7 @@ import { createLabelColorMap } from "./node-colors";
import { toGraphTriplets } from "./utils";
interface GraphVisualizationProps {
export interface GraphVisualizationProps {
triplets: RawTriplet[];
width?: number;
height?: number;

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,34 @@
import colors from "tailwindcss/colors";
// Define a color palette for node coloring
// Define a color palette for node coloring using hex values directly
export const nodeColorPalette = {
light: [
"var(--custom-color-1)", // Entity (default)
"var(--custom-color-2)",
"var(--custom-color-3)",
"var(--custom-color-4)",
"var(--custom-color-5)",
"var(--custom-color-6)",
"var(--custom-color-7)",
"var(--custom-color-8)",
"var(--custom-color-9)",
"var(--custom-color-10)",
"var(--custom-color-11)",
"var(--custom-color-12)",
"#b56455", // Entity (default)
"#7b8a34",
"#1c91a8",
"#886dbc",
"#ad6e30",
"#54935b",
"#4187c0",
"#a165a1",
"#997d1d",
"#2b9684",
"#2b9684",
"#b0617c",
],
dark: [
"var(--custom-color-1)", // Entity (default)
"var(--custom-color-2)",
"var(--custom-color-3)",
"var(--custom-color-4)",
"var(--custom-color-5)",
"var(--custom-color-6)",
"var(--custom-color-7)",
"var(--custom-color-8)",
"var(--custom-color-9)",
"var(--custom-color-10)",
"var(--custom-color-11)",
"var(--custom-color-12)",
"#b56455", // Entity (default)
"#7b8a34",
"#1c91a8",
"#886dbc",
"#ad6e30",
"#54935b",
"#4187c0",
"#a165a1",
"#997d1d",
"#2b9684",
"#2b9684",
"#b0617c",
],
};

View File

@ -0,0 +1,42 @@
<svg width="282" height="282" viewBox="0 0 282 282" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M80.0827 36.8297C92.7421 5.92292 120.776 19.7406 134.464 31.4565C135.321 32.19 135.792 33.2698 135.792 34.3978V250.904C135.792 252.083 135.253 253.224 134.336 253.966C103.335 279.044 85.2828 259.211 80.0827 245.933C44.9187 241.31 43.965 210.382 47.8837 195.496C15.173 188.351 17.5591 153.64 22.841 137.178C9.34813 109.018 33.9141 91.8201 47.8837 86.7414C40.524 52.2761 66.2831 39.1064 80.0827 36.8297Z" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="94.9384" y2="-2" transform="matrix(0.594988 0.803734 -0.785925 0.618321 77.3574 39.0923)" stroke="#C15E50" stroke-width="4"/>
<path d="M49.1309 86.2527L136.212 177.224" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="90.5781" y2="-2" transform="matrix(0.81717 0.576396 -0.552987 0.83319 32.5566 144.514)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="145.522" y2="-2" transform="matrix(0.689338 -0.72444 0.703134 0.711057 35.4785 140.498)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="77.0207" y2="-2" transform="matrix(0.531085 -0.847319 0.832259 0.554387 49.1133 196.723)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="111.293" y2="-2" transform="matrix(-0.980107 0.198471 -0.187173 -0.982327 135.791 118.41)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="58.2375" y2="-2" transform="matrix(0.535143 -0.844762 0.829524 0.558472 81.252 246.924)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="64.1562" y2="-2" transform="matrix(-0.506896 -0.862007 0.848017 -0.529968 137.443 252.989)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="45.3484" y2="-2" transform="matrix(-0.0859054 0.996303 -0.995828 -0.0912537 110.471 151.542)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="87.7438" y2="-2" transform="matrix(0.998952 -0.04577 0.0430721 0.999072 49.1133 198.731)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="64.1538" y2="-2" transform="matrix(-0.166991 0.985958 -0.984183 -0.177152 100.73 68.2088)" stroke="#C15E50" stroke-width="4"/>
<circle cx="102.68" cy="67.2048" r="9.88852" fill="#C15E50"/>
<ellipse cx="91.965" cy="129.454" rx="10.7131" ry="11.0442" fill="#C15E50"/>
<circle cx="106.574" cy="194.715" r="9.88852" fill="#C15E50"/>
<ellipse cx="49.5993" cy="86.7831" rx="7.30438" ry="7.53012" fill="#C15E50"/>
<ellipse cx="81.7387" cy="38.5903" rx="6.33046" ry="6.5261" fill="#C15E50"/>
<ellipse cx="27.2" cy="141" rx="11.2" ry="11.5462" fill="#C15E50"/>
<circle cx="81.2534" cy="243.912" r="5.93311" fill="#C15E50"/>
<circle cx="52.0352" cy="194.715" r="6.92197" fill="#C15E50"/>
<path d="M201.917 245.17C189.258 276.077 161.224 262.259 147.536 250.543C146.679 249.81 146.208 248.73 146.208 247.602V31.096C146.208 29.9172 146.747 28.7757 147.664 28.0343C178.665 2.95557 196.717 22.7885 201.917 36.0669C237.081 40.6903 238.035 71.618 234.116 86.5039C266.827 93.6492 264.441 128.36 259.159 144.822C272.652 172.982 248.086 190.18 234.116 195.259C241.476 229.724 215.717 242.894 201.917 245.17Z" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="94.9384" y2="-2" transform="matrix(-0.594988 -0.803734 0.785925 -0.618321 204.643 242.908)" stroke="#C15E50" stroke-width="4"/>
<path d="M232.869 195.747L145.788 104.776" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="90.5781" y2="-2" transform="matrix(-0.81717 -0.576396 0.552987 -0.83319 249.443 137.486)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="145.522" y2="-2" transform="matrix(-0.689338 0.72444 -0.703134 -0.711057 246.521 141.502)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="77.0207" y2="-2" transform="matrix(-0.531085 0.847319 -0.832259 -0.554387 232.887 85.2771)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="111.293" y2="-2" transform="matrix(0.980107 -0.198471 0.187173 0.982327 146.209 163.59)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="58.2375" y2="-2" transform="matrix(-0.535143 0.844762 -0.829524 -0.558472 200.748 35.0763)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="64.1562" y2="-2" transform="matrix(0.506896 0.862007 -0.848017 0.529968 144.557 29.0108)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="45.3484" y2="-2" transform="matrix(0.0859054 -0.996303 0.995828 0.0912537 171.529 130.458)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="87.7438" y2="-2" transform="matrix(-0.998952 0.04577 -0.0430721 -0.999072 232.887 83.2691)" stroke="#C15E50" stroke-width="4"/>
<line y1="-2" x2="64.1538" y2="-2" transform="matrix(0.166991 -0.985958 0.984183 0.177152 181.27 213.791)" stroke="#C15E50" stroke-width="4"/>
<circle cx="179.32" cy="214.795" r="9.88852" transform="rotate(180 179.32 214.795)" fill="#C15E50"/>
<ellipse cx="190.035" cy="152.546" rx="10.7131" ry="11.0442" transform="rotate(180 190.035 152.546)" fill="#C15E50"/>
<circle cx="175.426" cy="87.2852" r="9.88852" transform="rotate(180 175.426 87.2852)" fill="#C15E50"/>
<ellipse cx="232.401" cy="195.217" rx="7.30438" ry="7.53012" transform="rotate(180 232.401 195.217)" fill="#C15E50"/>
<ellipse cx="200.261" cy="243.41" rx="6.33046" ry="6.5261" transform="rotate(180 200.261 243.41)" fill="#C15E50"/>
<ellipse cx="254.8" cy="141" rx="11.2" ry="11.5462" transform="rotate(180 254.8 141)" fill="#C15E50"/>
<circle cx="200.747" cy="38.0884" r="5.93311" transform="rotate(180 200.747 38.0884)" fill="#C15E50"/>
<circle cx="229.965" cy="87.2852" r="6.92197" transform="rotate(180 229.965 87.2852)" fill="#C15E50"/>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

View File

@ -8,43 +8,52 @@ import {
SidebarMenu,
SidebarMenuItem,
} from "../ui/sidebar";
import { DashboardIcon } from "@radix-ui/react-icons";
import { Code, Search } from "lucide-react";
import { Activity, LayoutGrid, MessageSquare, Network } from "lucide-react";
import { NavMain } from "./nav-main";
import { useUser } from "~/hooks/useUser";
import { NavUser } from "./nav-user";
import { useWorkspace } from "~/hooks/useWorkspace";
import Logo from "../logo/logo";
const data = {
navMain: [
{
title: "Dashboard",
title: "Chat",
url: "/home/chat",
icon: MessageSquare,
},
{
title: "Memory",
url: "/home/dashboard",
icon: DashboardIcon,
icon: Network,
},
{
title: "API",
url: "/home/api",
icon: Code,
title: "Activity",
url: "/home/activity",
icon: Activity,
},
{
title: "Logs",
url: "/home/logs",
icon: Search,
title: "Integrations",
url: "/home/integrations",
icon: LayoutGrid,
},
],
};
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const user = useUser();
const workspace = useWorkspace();
return (
<Sidebar collapsible="offcanvas" {...props} className="bg-background">
<Sidebar
collapsible="none"
{...props}
className="bg-background h-[100vh] w-[calc(var(--sidebar-width-icon)+1px)]! py-2"
>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<span className="text-base font-semibold">{workspace.name}</span>
<div className="mt-1 flex w-full items-center justify-center">
<Logo width={20} height={20} />
</div>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>

View File

@ -1,3 +1,4 @@
import { cn } from "~/lib/utils";
import {
SidebarGroup,
SidebarGroupContent,
@ -28,10 +29,13 @@ export const NavMain = ({
<SidebarMenuButton
tooltip={item.title}
isActive={location.pathname.includes(item.url)}
className={cn(
location.pathname.includes(item.url) &&
"!bg-grayAlpha-100 hover:bg-grayAlpha-100!",
)}
onClick={() => navigate(item.url)}
>
{item.icon && <item.icon />}
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}

View File

@ -1,4 +1,4 @@
import { LogOut } from "lucide-react";
import { LogOut, Settings } from "lucide-react";
import { AvatarText } from "../ui/avatar";
import {
DropdownMenu,
@ -8,33 +8,48 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { SidebarMenu, SidebarMenuItem, useSidebar } from "../ui/sidebar";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "../ui/sidebar";
import type { User } from "~/models/user.server";
import { Button } from "../ui";
import { cn } from "~/lib/utils";
import { useLocation, useNavigate } from "@remix-run/react";
export function NavUser({ user }: { user: User }) {
const { isMobile } = useSidebar();
const location = useLocation();
const navigate = useNavigate();
return (
<SidebarMenu>
<SidebarMenuItem className="mb-2 flex justify-center">
<Button
variant="ghost"
isActive={location.pathname.includes("settings")}
className={cn(
location.pathname.includes("settings") &&
"!bg-grayAlpha-100 hover:bg-grayAlpha-100!",
)}
onClick={() => navigate("/settings")}
>
<Settings size={18} />
</Button>
</SidebarMenuItem>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="lg"
variant="link"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground ,b-2 mb-2 gap-2 px-2"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground mb-2 gap-2 px-3"
>
<AvatarText
text={user.name ?? "User"}
className="h-6 w-6 rounded"
/>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="text-muted-foreground truncate text-xs">
{user.email}
</span>
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent

View File

@ -0,0 +1,47 @@
import { RiGithubFill } from "@remixicon/react";
import { Button } from "./button";
import { Separator } from "./separator";
import { useLocation } from "@remix-run/react";
const PAGE_TITLES: Record<string, string> = {
"/home/dashboard": "Memory graph",
"/home/chat": "Chat",
"/home/api": "API",
"/home/logs": "Logs",
};
function getHeaderTitle(pathname: string): string {
// Try to match the most specific path first
for (const key of Object.keys(PAGE_TITLES)) {
if (pathname.startsWith(key)) {
return PAGE_TITLES[key];
}
}
// Default fallback
return "Documents";
}
export function SiteHeader() {
const location = useLocation();
const title = getHeaderTitle(location.pathname);
return (
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
<div className="flex w-full items-center gap-1 px-4 lg:gap-2">
<h1 className="text-base">{title}</h1>
<div className="ml-auto flex items-center gap-2">
<Button variant="ghost" size="sm" className="hidden sm:flex">
<a
href="https://github.com/redplanethq/core"
rel="noopener noreferrer"
target="_blank"
className="dark:text-foreground"
>
<RiGithubFill size={20} />
</a>
</Button>
</div>
</div>
</header>
);
}

View File

@ -29,7 +29,7 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
"fixed z-50 gap-4 bg-background-2 p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {

View File

@ -263,8 +263,7 @@ function SidebarTrigger({
data-sidebar="trigger"
data-slot="sidebar-trigger"
variant="ghost"
size="icon"
className={cn("size-7", className)}
className={cn("size-8", className)}
onClick={(event) => {
onClick?.(event);
toggleSidebar();
@ -307,7 +306,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
<main
data-slot="sidebar-inset"
className={cn(
"bg-background relative flex w-full flex-1 flex-col",
"bg-background-3 relative flex w-full flex-1 flex-col",
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className,
)}
@ -324,7 +323,7 @@ function SidebarInput({
<Input
data-slot="sidebar-input"
data-sidebar="input"
className={cn("bg-background h-8 w-full shadow-none", className)}
className={cn("bg-background-2 h-8 w-full shadow-none", className)}
{...props}
/>
);
@ -472,11 +471,11 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
}
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:!bg-background-3 active:!text-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
default: "hover:bg-grayAlpha-100 hover:text-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},

View File

@ -71,6 +71,9 @@ const EnvironmentSchema = z.object({
SMTP_USER: z.string().optional(),
SMTP_PASSWORD: z.string().optional(),
//Trigger
TRIGGER_PROJECT_ID: z.string(),
// Model envs
MODEL: z.string().default(LLMModelEnum.GPT41),
EMBEDDING_MODEL: z.string().default("bge-m3"),

View File

@ -2,7 +2,6 @@ import { type Workspace } from "@core/database";
import { prisma } from "~/db.server";
interface CreateWorkspaceDto {
slug: string;
name: string;
integrations: string[];
userId: string;
@ -13,7 +12,7 @@ export async function createWorkspace(
): Promise<Workspace> {
const workspace = await prisma.workspace.create({
data: {
slug: input.slug,
slug: input.name,
name: input.name,
userId: input.userId,
},

View File

@ -97,7 +97,7 @@ export function ErrorBoundary() {
<Meta />
<Links />
</head>
<body className="bg-background h-full overflow-hidden">
<body className="bg-background-2 h-full overflow-hidden">
<AppContainer>
<MainCenteredContainer>
<RouteErrorDisplay />
@ -123,7 +123,7 @@ function App() {
<Links />
<PreventFlashOnWrongTheme ssrTheme={Boolean(theme)} />
</head>
<body className="bg-background h-full overflow-hidden font-sans">
<body className="bg-background-2 h-full overflow-hidden font-sans">
<Outlet />
<ScrollRestoration />

View File

@ -24,10 +24,6 @@ const schema = z.object({
.string()
.min(3, "Your workspace name must be at least 3 characters")
.max(50),
workspaceSlug: z
.string()
.min(3, "Your workspace slug must be at least 3 characters")
.max(50),
});
export async function action({ request }: ActionFunctionArgs) {
@ -40,11 +36,10 @@ export async function action({ request }: ActionFunctionArgs) {
return json(submission);
}
const { workspaceSlug, workspaceName } = submission.value;
const { workspaceName } = submission.value;
try {
await createWorkspace({
slug: workspaceSlug,
integrations: [],
name: workspaceName,
userId,
@ -109,27 +104,6 @@ export default function ConfirmBasicDetails() {
)}
</div>
<div>
<label
htmlFor="workspaceSlug"
className="text-muted-foreground mb-1 block text-sm"
>
Workspace Slug
</label>
<Input
type="text"
id="workspaceSlug"
placeholder="Give unique workspace slug"
name={fields.workspaceSlug.name}
className="mt-1 block w-full text-base"
/>
{fields.workspaceSlug.error && (
<div className="text-sm text-red-500">
{fields.workspaceSlug.error}
</div>
)}
</div>
<Button
type="submit"
variant="secondary"

View File

@ -18,10 +18,10 @@ import { addToQueue, IngestBodyRequest } from "~/lib/ingest.server";
import { getNodeLinks } from "~/lib/neo4j.server";
import { useTypedLoaderData } from "remix-typedjson";
import { GraphVisualization } from "~/components/graph/graph-visualization";
import { Search } from "~/components/dashboard";
import { SearchBodyRequest } from "./search";
import { SearchService } from "~/services/search.server";
import { GraphVisualizationClient } from "~/components/graph/graph-client";
// --- Only return userId in loader, fetch nodeLinks on client ---
export async function action({ request }: ActionFunctionArgs) {
@ -60,7 +60,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
export default function Dashboard() {
const { userId } = useTypedLoaderData<typeof loader>();
const [size, setSize] = useState(15);
// State for nodeLinks and loading
const [nodeLinks, setNodeLinks] = useState<any[] | null>(null);
@ -94,55 +93,18 @@ export default function Dashboard() {
}, [userId]);
return (
<ResizablePanelGroup direction="horizontal">
<ResizablePanel
collapsible={false}
className="h-[calc(100vh_-_20px)] overflow-hidden rounded-md"
order={1}
id="home"
>
<div className="home flex h-full flex-col overflow-y-auto p-3 text-base">
<h3 className="text-lg font-medium">Graph</h3>
<p className="text-muted-foreground"> Your memory graph </p>
<div className="bg-background-3 mt-2 flex grow items-center justify-center rounded">
{loading ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="mb-2 h-8 w-8 animate-spin rounded-full border-b-2 border-gray-400" />
<span className="text-muted-foreground">Loading graph...</span>
</div>
) : (
typeof window !== "undefined" &&
nodeLinks && <GraphVisualization triplets={nodeLinks} />
)}
<div className="home flex h-[calc(100vh_-_60px)] flex-col overflow-y-auto p-3 text-base">
<div className="flex grow items-center justify-center rounded">
{loading ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="mb-2 h-8 w-8 animate-spin rounded-full border-b-2 border-gray-400" />
<span className="text-muted-foreground">Loading graph...</span>
</div>
</div>
</ResizablePanel>
<ResizableHandle className="bg-border w-[0.5px]" />
<ResizablePanel
className="overflow-auto"
collapsible={false}
maxSize={50}
minSize={25}
defaultSize={size}
onResize={(size) => setSize(size)}
order={2}
id="rightScreen"
>
<Tabs defaultValue="ingest" className="p-3 text-base">
<TabsList>
<TabsTrigger value="ingest">Add</TabsTrigger>
<TabsTrigger value="retrieve">Retrieve</TabsTrigger>
</TabsList>
<TabsContent value="ingest">
<Ingest />
</TabsContent>
<TabsContent value="retrieve">
<Search />
</TabsContent>
</Tabs>
</ResizablePanel>
</ResizablePanelGroup>
) : (
typeof window !== "undefined" &&
nodeLinks && <GraphVisualizationClient triplets={nodeLinks} />
)}
</div>
</div>
);
}

View File

@ -7,6 +7,7 @@ import { clearRedirectTo, commitSession } from "~/services/redirectTo.server";
import { AppSidebar } from "~/components/sidebar/app-sidebar";
import { SidebarInset, SidebarProvider } from "~/components/ui/sidebar";
import { SiteHeader } from "~/components/ui/header";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
@ -32,16 +33,19 @@ export default function Home() {
{
"--sidebar-width": "calc(var(--spacing) * 54)",
"--header-height": "calc(var(--spacing) * 12)",
background: "var(--background)",
background: "var(--background-2)",
} as React.CSSProperties
}
>
<AppSidebar variant="inset" />
<SidebarInset className="bg-background-2">
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex h-full flex-col">
<Outlet />
<SidebarInset className="bg-background h-[100vh] py-2 pr-2">
<div className="bg-background-2 h-full rounded-md">
<SiteHeader />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex h-full flex-col">
<Outlet />
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,112 @@
import {
ArrowLeft,
Brain,
Building,
Clock,
Code,
User,
Workflow,
} from "lucide-react";
import React from "react";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
SidebarProvider,
} from "~/components/ui/sidebar";
import { Button } from "~/components/ui";
import { cn } from "~/lib/utils";
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { requireUser, requireWorkpace } from "~/services/session.server";
import { typedjson } from "remix-typedjson";
import { clearRedirectTo, commitSession } from "~/services/redirectTo.server";
import { Outlet, useLocation, useNavigate } from "@remix-run/react";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const workspace = await requireWorkpace(request);
return typedjson(
{
user,
workspace,
},
{
headers: {
"Set-Cookie": await commitSession(await clearRedirectTo(request)),
},
},
);
};
export default function Settings() {
const location = useLocation();
const data = {
nav: [
{ name: "Workspace", icon: Building },
{ name: "Preferences", icon: User },
{ name: "API", icon: Code },
],
};
const navigate = useNavigate();
const gotoHome = () => {
navigate("/home/dashboard");
};
return (
<div className="bg-background h-full w-full overflow-hidden p-0">
<SidebarProvider className="items-start">
<Sidebar collapsible="none" className="hidden w-[180px] md:flex">
<SidebarHeader className="flex justify-start pb-0">
<Button
variant="link"
className="flex w-fit gap-2"
onClick={gotoHome}
>
<ArrowLeft size={14} />
Back to app
</Button>
</SidebarHeader>
<SidebarContent className="bg-background">
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu className="gap-0.5">
{data.nav.map((item) => (
<SidebarMenuItem key={item.name}>
<Button
variant="secondary"
isActive={location.pathname.includes(
item.name.toLowerCase(),
)}
onClick={() =>
navigate(`/settings/${item.name.toLowerCase()}`)
}
className={cn("flex w-fit min-w-0 justify-start gap-1")}
>
<item.icon size={18} />
<span>{item.name}</span>
</Button>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<main className="flex h-[100vh] flex-1 flex-col overflow-hidden p-2 pl-0">
<div className="bg-background-2 flex h-full flex-1 flex-col overflow-y-auto rounded-md">
<Outlet />
</div>
</main>
</SidebarProvider>
</div>
);
}

View File

@ -47,7 +47,8 @@ import { createOllama } from "ollama-ai-provider";
const DEFAULT_EPISODE_WINDOW = 5;
export class KnowledgeGraphService {
async getEmbedding(text: string, useOpenAI = false) {
async getEmbedding(text: string, useOpenAI = true) {
console.log(text, useOpenAI);
if (useOpenAI) {
// Use OpenAI embedding model when explicitly requested
const { embedding } = await embed({
@ -58,7 +59,7 @@ export class KnowledgeGraphService {
}
// Default to using Ollama
const ollamaUrl = process.env.OLLAMA_URL;
const ollamaUrl = env.OLLAMA_URL;
const model = env.EMBEDDING_MODEL;
const ollama = createOllama({

View File

@ -1,4 +1,5 @@
import type { User } from "~/models/user.server";
import { createWorkspace } from "~/models/workspace.server";
import { singleton } from "~/utils/singleton";
export async function postAuthentication({
@ -10,5 +11,11 @@ export async function postAuthentication({
loginMethod: User["authenticationMethod"];
isNewUser: boolean;
}) {
// console.log(user);
if (user.name && isNewUser && loginMethod === "GOOGLE") {
await createWorkspace({
name: user.name,
userId: user.id,
integrations: [],
});
}
}

View File

@ -322,8 +322,9 @@
@layer base {
* {
@apply border-border outline-ring/50;
--header-height: 44px;
}
body {
@apply bg-background text-foreground text-base;
@apply bg-background-2 text-foreground text-base;
}
}

View File

View File

@ -0,0 +1,38 @@
import { PrismaClient } from '@prisma/client';
import { IntegrationPayloadEventType } from '@redplanethq/sol-sdk';
import { logger, schedules, tasks } from '@trigger.dev/sdk/v3';
import { integrationRun } from './integration-run';
const prisma = new PrismaClient();
export const integrationRunSchedule = schedules.task({
id: 'integration-run-schedule',
run: async (payload) => {
const { externalId } = payload;
const integrationAccount = await prisma.integrationAccount.findUnique({
where: { id: externalId },
include: {
integrationDefinition: true,
workspace: true,
},
});
if (!integrationAccount) {
const deletedSchedule = await schedules.del(externalId);
logger.info('Deleting schedule as integration account is not there');
return deletedSchedule;
}
const pat = await prisma.personalAccessToken.findFirst({
where: { userId: integrationAccount.workspace.userId, name: 'default' },
});
return await tasks.trigger<typeof integrationRun>('integration-run', {
event: IntegrationPayloadEventType.SCHEDULED_SYNC,
pat: pat.token,
integrationAccount,
integrationDefinition: integrationAccount.integrationDefinition,
});
},
});

View File

@ -0,0 +1,90 @@
import createLoadRemoteModule, {
createRequires,
} from '@paciolan/remote-module-loader';
import {
IntegrationAccount,
IntegrationDefinition,
} from '@redplanethq/sol-sdk';
import { logger, task } from '@trigger.dev/sdk/v3';
import axios from 'axios';
const fetcher = async (url: string) => {
// Handle remote URLs with axios
const response = await axios.get(url);
return response.data;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const loadRemoteModule = async (requires: any) =>
createLoadRemoteModule({ fetcher, requires });
function createAxiosInstance(token: string) {
const instance = axios.create();
instance.interceptors.request.use((config) => {
// Check if URL starts with /api and doesn't have a full host
if (config.url?.startsWith('/api')) {
config.url = `${process.env.BACKEND_HOST}${config.url.replace('/api/', '/')}`;
}
if (
config.url.includes(process.env.FRONTEND_HOST) ||
config.url.includes(process.env.BACKEND_HOST)
) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
return instance;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getRequires = (axios: any) => createRequires({ axios });
export const integrationRun = task({
id: 'integration-run',
run: async ({
pat,
eventBody,
integrationAccount,
integrationDefinition,
event,
}: {
pat: string;
// This is the event you want to pass to the integration
// eslint-disable-next-line @typescript-eslint/no-explicit-any
event: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventBody?: any;
integrationDefinition: IntegrationDefinition;
integrationAccount?: IntegrationAccount;
}) => {
const remoteModuleLoad = await loadRemoteModule(
getRequires(createAxiosInstance(pat)),
);
logger.info(
`${integrationDefinition.url}/${integrationDefinition.version}/backend/index.js`,
);
const integrationFunction = await remoteModuleLoad(
`${integrationDefinition.url}/${integrationDefinition.version}/backend/index.js`,
);
// const integrationFunction = await remoteModuleLoad(
// `${integrationDefinition.url}`,
// );
return await integrationFunction.run({
integrationAccount,
integrationDefinition,
event,
eventBody: {
...(eventBody ? eventBody : {}),
},
});
},
});

View File

@ -0,0 +1,64 @@
import { PrismaClient } from "@prisma/client";
import { logger, schedules, task } from "@trigger.dev/sdk/v3";
import { integrationRunSchedule } from "./integration-run-schedule";
const prisma = new PrismaClient();
export const scheduler = task({
id: "scheduler",
run: async (payload: { integrationAccountId: string }) => {
const { integrationAccountId } = payload;
const integrationAccount = await prisma.integrationAccount.findUnique({
where: { id: integrationAccountId, deleted: null },
include: {
integrationDefinition: true,
workspace: true,
},
});
if (!integrationAccount) {
logger.error("Integration account not found");
return null;
}
if (!integrationAccount.workspace) {
return null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const spec = integrationAccount.integrationDefinition.spec as any;
if (spec.schedule && spec.schedule.frequency) {
const createdSchedule = await schedules.create({
// The id of the scheduled task you want to attach to.
task: integrationRunSchedule.id,
// The schedule in cron format.
cron: spec.schedule.frequency,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
timezone: (integrationAccount.workspace.preferences as any).timezone,
// this is required, it prevents you from creating duplicate schedules. It will update the schedule if it already exists.
deduplicationKey: integrationAccount.id,
externalId: integrationAccount.id,
});
await prisma.integrationAccount.update({
where: {
id: integrationAccount.id,
},
data: {
settings: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(integrationAccount.settings as any),
scheduleId: createdSchedule.id,
},
},
});
return createdSchedule;
}
return "No schedule for this task";
},
});

View File

@ -17,8 +17,10 @@
"@conform-to/zod": "^0.6.1",
"@core/database": "workspace:*",
"@core/types": "workspace:*",
"@opentelemetry/api": "1.9.0",
"@mjackson/headers": "0.11.1",
"@nichtsam/remix-auth-email-link": "3.0.0",
"@opentelemetry/api": "1.9.0",
"@prisma/client": "*",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
@ -45,10 +47,10 @@
"@remix-run/server-runtime": "2.16.7",
"@remix-run/v1-meta": "^0.1.3",
"@remixicon/react": "^4.2.0",
"@tanstack/react-table": "^8.13.2",
"@prisma/client": "*",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/postcss": "^4.1.7",
"@tanstack/react-table": "^8.13.2",
"@trigger.dev/sdk": "^3.3.17",
"ai": "4.3.14",
"bullmq": "^5.53.2",
"class-variance-authority": "^0.7.1",
@ -56,10 +58,14 @@
"compression": "^1.7.4",
"cross-env": "^7.0.3",
"d3": "^7.9.0",
"dayjs": "^1.11.10",
"date-fns": "^4.1.0",
"express": "^4.18.1",
"dayjs": "^1.11.10",
"emails": "workspace:*",
"express": "^4.18.1",
"graphology": "^0.26.0",
"graphology-layout-force": "^0.2.4",
"graphology-layout-forceatlas2": "^0.10.1",
"graphology-layout-noverlap": "^0.4.2",
"ioredis": "^5.6.1",
"isbot": "^4.1.0",
"jose": "^5.2.3",
@ -72,14 +78,14 @@
"posthog-js": "^1.116.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-resizable-panels": "^1.0.9",
"react-virtualized": "^9.22.6",
"remix-auth": "^4.2.0",
"@nichtsam/remix-auth-email-link": "3.0.0",
"remix-auth-oauth2": "^3.4.1",
"remix-themes": "^1.3.1",
"remix-typedjson": "0.3.1",
"remix-utils": "^7.7.0",
"react-resizable-panels": "^1.0.9",
"react-virtualized": "^9.22.6",
"sigma": "^3.0.2",
"tailwind-merge": "^2.6.0",
"tailwind-scrollbar-hide": "^2.0.0",
"tailwindcss-animate": "^1.0.7",
@ -96,6 +102,7 @@
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.7",
"@trigger.dev/build": "^3.3.17",
"@types/compression": "^1.7.2",
"@types/d3": "^7.4.3",
"@types/express": "^4.17.13",

View File

@ -0,0 +1,23 @@
import { defineConfig } from "@trigger.dev/sdk/v3";
import { env } from "~/env.server";
export default defineConfig({
project: env.TRIGGER_PROJECT_ID,
runtime: "node",
logLevel: "log",
// The max compute seconds a task is allowed to run. If the task run exceeds this duration, it will be stopped.
// You can override this on an individual task.
// See https://trigger.dev/docs/runs/max-duration
maxDuration: 3600,
retries: {
enabledInDev: true,
default: {
maxAttempts: 3,
minTimeoutInMs: 1000,
maxTimeoutInMs: 10000,
factor: 2,
randomize: true,
},
},
dirs: ["./app/trigger"],
});

View File

@ -6,7 +6,8 @@
"**/*.ts",
"**/*.tsx",
"tailwind.config.js",
"tailwind.config.js"
"tailwind.config.js",
"trigger.config.ts"
],
"compilerOptions": {
"types": ["@remix-run/node", "vite/client"],

View File

@ -37,7 +37,7 @@ services:
postgres:
container_name: core-postgres
image: postgres:15
image: redplanethq/postgres:0.1.0
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}

View File

@ -0,0 +1,119 @@
-- CreateEnum
CREATE TYPE "WebhookDeliveryStatus" AS ENUM ('SUCCESS', 'FAILED');
-- CreateTable
CREATE TABLE "Activity" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deleted" TIMESTAMP(3),
"text" TEXT NOT NULL,
"sourceURL" TEXT,
"integrationAccountId" TEXT,
"rejectionReason" TEXT,
"workspaceId" TEXT NOT NULL,
CONSTRAINT "Activity_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "IntegrationAccount" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deleted" TIMESTAMP(3),
"integrationConfiguration" JSONB NOT NULL,
"accountId" TEXT,
"settings" JSONB,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"integratedById" TEXT NOT NULL,
"integrationDefinitionId" TEXT NOT NULL,
"workspaceId" TEXT NOT NULL,
CONSTRAINT "IntegrationAccount_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "IntegrationDefinitionV2" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deleted" TIMESTAMP(3),
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"description" TEXT NOT NULL,
"icon" TEXT NOT NULL,
"config" JSONB,
"spec" JSONB NOT NULL DEFAULT '{}',
"version" TEXT,
"url" TEXT,
"workspaceId" TEXT,
CONSTRAINT "IntegrationDefinitionV2_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WebhookConfiguration" (
"id" TEXT NOT NULL,
"url" TEXT NOT NULL,
"secret" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"eventTypes" TEXT[],
"userId" TEXT,
"workspaceId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "WebhookConfiguration_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WebhookDeliveryLog" (
"id" TEXT NOT NULL,
"webhookConfigurationId" TEXT NOT NULL,
"activityId" TEXT,
"status" "WebhookDeliveryStatus" NOT NULL,
"responseStatusCode" INTEGER,
"responseBody" TEXT,
"error" TEXT,
"deliveredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "WebhookDeliveryLog_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "IntegrationAccount_accountId_integrationDefinitionId_worksp_key" ON "IntegrationAccount"("accountId", "integrationDefinitionId", "workspaceId");
-- CreateIndex
CREATE UNIQUE INDEX "IntegrationDefinitionV2_name_key" ON "IntegrationDefinitionV2"("name");
-- AddForeignKey
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_integrationAccountId_fkey" FOREIGN KEY ("integrationAccountId") REFERENCES "IntegrationAccount"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "IntegrationAccount" ADD CONSTRAINT "IntegrationAccount_integratedById_fkey" FOREIGN KEY ("integratedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "IntegrationAccount" ADD CONSTRAINT "IntegrationAccount_integrationDefinitionId_fkey" FOREIGN KEY ("integrationDefinitionId") REFERENCES "IntegrationDefinitionV2"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "IntegrationAccount" ADD CONSTRAINT "IntegrationAccount_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "IntegrationDefinitionV2" ADD CONSTRAINT "IntegrationDefinitionV2_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WebhookConfiguration" ADD CONSTRAINT "WebhookConfiguration_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WebhookConfiguration" ADD CONSTRAINT "WebhookConfiguration_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WebhookDeliveryLog" ADD CONSTRAINT "WebhookDeliveryLog_webhookConfigurationId_fkey" FOREIGN KEY ("webhookConfigurationId") REFERENCES "WebhookConfiguration"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WebhookDeliveryLog" ADD CONSTRAINT "WebhookDeliveryLog_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "Activity"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -36,10 +36,12 @@ model User {
referralSource String?
personalAccessTokens PersonalAccessToken[]
InvitationCode InvitationCode? @relation(fields: [invitationCodeId], references: [id])
InvitationCode InvitationCode? @relation(fields: [invitationCodeId], references: [id])
invitationCodeId String?
Space Space[]
Workspace Workspace?
IntegrationAccount IntegrationAccount[]
WebhookConfiguration WebhookConfiguration[]
}
model Workspace {
@ -54,9 +56,13 @@ model Workspace {
integrations String[]
userId String? @unique
user User? @relation(fields: [userId], references: [id])
IngestionQueue IngestionQueue[]
userId String? @unique
user User? @relation(fields: [userId], references: [id])
IngestionQueue IngestionQueue[]
IntegrationAccount IntegrationAccount[]
IntegrationDefinitionV2 IntegrationDefinitionV2[]
Activity Activity[]
WebhookConfiguration WebhookConfiguration[]
}
enum AuthenticationMethod {
@ -193,6 +199,110 @@ model IngestionQueue {
processedAt DateTime?
}
// For Integrations
model Activity {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deleted DateTime?
text String
// Used to link the task or activity to external apps
sourceURL String?
integrationAccount IntegrationAccount? @relation(fields: [integrationAccountId], references: [id])
integrationAccountId String?
rejectionReason String?
workspace Workspace @relation(fields: [workspaceId], references: [id])
workspaceId String
WebhookDeliveryLog WebhookDeliveryLog[]
}
model IntegrationAccount {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deleted DateTime?
integrationConfiguration Json
accountId String?
settings Json?
isActive Boolean @default(true)
integratedBy User @relation(references: [id], fields: [integratedById])
integratedById String
integrationDefinition IntegrationDefinitionV2 @relation(references: [id], fields: [integrationDefinitionId])
integrationDefinitionId String
workspace Workspace @relation(references: [id], fields: [workspaceId])
workspaceId String
Activity Activity[]
@@unique([accountId, integrationDefinitionId, workspaceId])
}
model IntegrationDefinitionV2 {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deleted DateTime?
name String @unique
slug String
description String
icon String
config Json?
spec Json @default("{}")
version String?
url String?
workspace Workspace? @relation(references: [id], fields: [workspaceId])
workspaceId String?
IntegrationAccount IntegrationAccount[]
}
model WebhookConfiguration {
id String @id @default(cuid())
url String
secret String?
isActive Boolean @default(true)
eventTypes String[] // List of event types this webhook is interested in, e.g. ["activity.created"]
user User? @relation(fields: [userId], references: [id])
userId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
workspaceId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
WebhookDeliveryLog WebhookDeliveryLog[]
}
model WebhookDeliveryLog {
id String @id @default(cuid())
webhookConfiguration WebhookConfiguration @relation(fields: [webhookConfigurationId], references: [id])
webhookConfigurationId String
activity Activity? @relation(fields: [activityId], references: [id])
activityId String?
status WebhookDeliveryStatus
responseStatusCode Int?
responseBody String?
error String?
deliveredAt DateTime @default(now())
createdAt DateTime @default(now())
}
enum WebhookDeliveryStatus {
SUCCESS
FAILED
}
enum IngestionStatus {
PENDING
PROCESSING

841
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

138
trigger/.env.example Normal file
View File

@ -0,0 +1,138 @@
# Trigger.dev self-hosting environment variables
# - These are the default values for the self-hosting stack
# - You should change them to suit your needs, especially the secrets
# - See the docs for more information: https://trigger.dev/docs/self-hosting/overview
# Secrets
# - Do NOT use these defaults in production
# - Generate your own by running `openssl rand -hex 16` for each secret
SESSION_SECRET=2818143646516f6fffd707b36f334bbb
MAGIC_LINK_SECRET=44da78b7bbb0dfe709cf38931d25dcdd
ENCRYPTION_KEY=f686147ab967943ebbe9ed3b496e465a
MANAGED_WORKER_SECRET=447c29678f9eaf289e9c4b70d3dd8a7f
# Worker token
# - This is the token for the worker to connect to the webapp
# - When running the combined stack, this is set automatically during bootstrap
# - For the split setup, you will have to set this manually. The token is available in the webapp logs but will only be shown once.
# - See the docs for more information: https://trigger.dev/docs/self-hosting/docker
# TRIGGER_WORKER_TOKEN=
# Worker URLs
# - In split setups, uncomment and set to the public URL of your webapp
# TRIGGER_API_URL=https://trigger.example.com
# OTEL_EXPORTER_OTLP_ENDPOINT=https://trigger.example.com/otel
# Postgres
# - Do NOT use these defaults in production
# - Especially if you decide to expose the database to the internet
# POSTGRES_USER=postgres
POSTGRES_USER=docker
POSTGRES_PASSWORD=docker
TRIGGER_DB=trigger
DB_HOST=localhost
DB_PORT=5432
DB_SCHEMA=sigma
# POSTGRES_DB=postgres
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:${DB_PORT}/${TRIGGER_DB}?schema=public&sslmode=disable
DIRECT_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:${DB_PORT}/${TRIGGER_DB}?schema=public&sslmode=disable
# Trigger image tag
# - This is the version of the webapp and worker images to use, they should be locked to a specific version in production
# - For example: TRIGGER_IMAGE_TAG=v4.0.0-v4-beta.21
TRIGGER_IMAGE_TAG=v4-beta
# Webapp
# - These should generally be set to the same value
# - In production, these should be set to the public URL of your webapp, e.g. https://trigger.example.com
APP_ORIGIN=http://localhost:8030
LOGIN_ORIGIN=http://localhost:8030
API_ORIGIN=http://localhost:8030
DEV_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8030/otel
# You may need to set this when testing locally or when using the combined setup
# API_ORIGIN=http://webapp:3000
# Webapp - memory management
# - This sets the maximum memory allocation for Node.js heap in MiB (e.g. "4096" for 4GB)
# - It should be set according to your total webapp machine's memory or any container limits you have set
# - Setting this too high or low WILL cause crashes, inefficient memory utilization and high CPU usage
# - You should allow for some memory overhead, we suggest at least 20%, for example:
# - 2GB machine: NODE_MAX_OLD_SPACE_SIZE=1600
# - 4GB machine: NODE_MAX_OLD_SPACE_SIZE=3200
# - 6GB machine: NODE_MAX_OLD_SPACE_SIZE=4800
# - 8GB machine: NODE_MAX_OLD_SPACE_SIZE=6400
# NODE_MAX_OLD_SPACE_SIZE=8192
# ClickHouse
# - Do NOT use these defaults in production
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=password
CLICKHOUSE_URL=http://default:password@clickhouse:8123?secure=false
RUN_REPLICATION_CLICKHOUSE_URL=http://default:password@clickhouse:8123
# Docker Registry
# - When testing locally, the default values should be fine
# - When deploying to production, you will have to change these, especially the password and URL
# - See the docs for more information: https://trigger.dev/docs/self-hosting/docker#registry-setup
DOCKER_REGISTRY_URL=localhost:5000
DOCKER_REGISTRY_USERNAME=registry-user
DOCKER_REGISTRY_PASSWORD=very-secure-indeed
# Object store
# - You need to log into the Minio dashboard and create a bucket called "packets"
# - See the docs for more information: https://trigger.dev/docs/self-hosting/docker#object-storage
OBJECT_STORE_ACCESS_KEY_ID=admin
OBJECT_STORE_SECRET_ACCESS_KEY=very-safe-password
# You will have to uncomment and configure this for production
# OBJECT_STORE_BASE_URL=http://localhost:9000
# Credentials to access the Minio dashboard at http://localhost:9001
# - You should change these credentials and not use them for the `OBJECT_STORE_` env vars above
# - Instead, setup a non-root user with access the "packets" bucket
# MINIO_ROOT_USER=admin
# MINIO_ROOT_PASSWORD=very-safe-password
# Other image tags
# - These are the versions of the other images to use
# - You should lock these to a specific version in production
# POSTGRES_IMAGE_TAG=14
# REDIS_IMAGE_TAG=7
# ELECTRIC_IMAGE_TAG=1.0.13
# CLICKHOUSE_IMAGE_TAG=latest
# REGISTRY_IMAGE_TAG=2
# MINIO_IMAGE_TAG=latest
# DOCKER_PROXY_IMAGE_TAG=latest
# TRAEFIK_IMAGE_TAG=v3.4
# Publish IPs
# - These are the IPs to publish the services to
# - Setting to 127.0.0.1 makes the service only accessible locally
# - When deploying to production, you will have to change these, depending on your setup
# WEBAPP_PUBLISH_IP=0.0.0.0
# POSTGRES_PUBLISH_IP=127.0.0.1
# REDIS_PUBLISH_IP=127.0.0.1
# ELECTRIC_PUBLISH_IP=127.0.0.1
# CLICKHOUSE_PUBLISH_IP=127.0.0.1
# REGISTRY_PUBLISH_IP=127.0.0.1
# MINIO_PUBLISH_IP=127.0.0.1
# Restart policy
# - Applies to all services, adjust as needed
# RESTART_POLICY=unless-stopped
# Docker logging
# - See the official docs: https://docs.docker.com/engine/logging/configure/
# LOGGING_DRIVER=local
# LOGGING_MAX_SIZE=20m
# LOGGING_MAX_FILES=5
# LOGGING_COMPRESS=true
# Traefik
# - Reverse proxy settings only serve as an example and require further configuration
# - See the partial overrides in docker-compose.traefik.yml for more details
# TRAEFIK_ENTRYPOINT=websecure
# TRAEFIK_HTTP_PUBLISH_IP=0.0.0.0
# TRAEFIK_HTTPS_PUBLISH_IP=0.0.0.0
# TRAEFIK_DASHBOARD_PUBLISH_IP=127.0.0.1

245
trigger/docker-compose.yaml Normal file
View File

@ -0,0 +1,245 @@
x-logging: &logging-config
driver: ${LOGGING_DRIVER:-local}
options:
max-size: ${LOGGING_MAX_SIZE:-20m}
max-file: ${LOGGING_MAX_FILES:-5}
compress: ${LOGGING_COMPRESS:-true}
services:
webapp:
image: ghcr.io/triggerdotdev/trigger.dev:${TRIGGER_IMAGE_TAG:-v4-beta}
restart: ${RESTART_POLICY:-unless-stopped}
logging: *logging-config
ports:
- ${WEBAPP_PUBLISH_IP:-0.0.0.0}:8030:3000
depends_on:
- clickhouse
networks:
- webapp
- supervisor
volumes:
- shared:/home/node/shared
# Only needed for bootstrap
user: root
# Only needed for bootstrap
command: sh -c "chown -R node:node /home/node/shared && exec ./scripts/entrypoint.sh"
healthcheck:
test: ["CMD", "node", "-e", "http.get('http://localhost:3000/healthcheck', res => process.exit(res.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 30s
timeout: 10s
retries: 5
start_period: 10s
environment:
APP_ORIGIN: ${APP_ORIGIN:-http://localhost:8030}
LOGIN_ORIGIN: ${LOGIN_ORIGIN:-http://localhost:8030}
API_ORIGIN: ${API_ORIGIN:-http://localhost:8030}
ELECTRIC_ORIGIN: http://electric:3000
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable}
DIRECT_URL: ${DIRECT_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable}
SESSION_SECRET: ${SESSION_SECRET}
MAGIC_LINK_SECRET: ${MAGIC_LINK_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
MANAGED_WORKER_SECRET: ${MANAGED_WORKER_SECRET}
REDIS_HOST: host.docker.internal
REDIS_PORT: 6379
REDIS_TLS_DISABLED: true
APP_LOG_LEVEL: info
DEV_OTEL_EXPORTER_OTLP_ENDPOINT: ${DEV_OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:8030/otel}
DEPLOY_REGISTRY_HOST: ${DOCKER_REGISTRY_URL:-localhost:5000}
OBJECT_STORE_BASE_URL: ${OBJECT_STORE_BASE_URL:-http://minio:9000}
OBJECT_STORE_ACCESS_KEY_ID: ${OBJECT_STORE_ACCESS_KEY_ID}
OBJECT_STORE_SECRET_ACCESS_KEY: ${OBJECT_STORE_SECRET_ACCESS_KEY}
GRACEFUL_SHUTDOWN_TIMEOUT: 1000
# Bootstrap - this will automatically set up a worker group for you
# This will NOT work for split deployments
TRIGGER_BOOTSTRAP_ENABLED: 1
TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME: bootstrap
TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH: /home/node/shared/worker_token
# ClickHouse configuration
CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://default:password@clickhouse:8123?secure=false}
CLICKHOUSE_LOG_LEVEL: ${CLICKHOUSE_LOG_LEVEL:-info}
# Run replication
RUN_REPLICATION_ENABLED: ${RUN_REPLICATION_ENABLED:-1}
RUN_REPLICATION_CLICKHOUSE_URL: ${RUN_REPLICATION_CLICKHOUSE_URL:-http://default:password@clickhouse:8123}
RUN_REPLICATION_LOG_LEVEL: ${RUN_REPLICATION_LOG_LEVEL:-info}
# Limits
# TASK_PAYLOAD_OFFLOAD_THRESHOLD: 524288 # 512KB
# TASK_PAYLOAD_MAXIMUM_SIZE: 3145728 # 3MB
# BATCH_TASK_PAYLOAD_MAXIMUM_SIZE: 1000000 # 1MB
# TASK_RUN_METADATA_MAXIMUM_SIZE: 262144 # 256KB
# DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: 100
# DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: 100
# Internal OTEL configuration
INTERNAL_OTEL_TRACE_LOGGING_ENABLED: ${INTERNAL_OTEL_TRACE_LOGGING_ENABLED:-0}
electric:
image: electricsql/electric:${ELECTRIC_IMAGE_TAG:-1.0.13}
restart: ${RESTART_POLICY:-unless-stopped}
logging: *logging-config
depends_on:
- postgres
networks:
- webapp
environment:
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/main?schema=public&sslmode=disable}
ELECTRIC_INSECURE: true
ELECTRIC_USAGE_REPORTING: false
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/v1/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
clickhouse:
image: bitnami/clickhouse:${CLICKHOUSE_IMAGE_TAG:-latest}
restart: ${RESTART_POLICY:-unless-stopped}
logging: *logging-config
ports:
- ${CLICKHOUSE_PUBLISH_IP:-127.0.0.1}:9123:8123
- ${CLICKHOUSE_PUBLISH_IP:-127.0.0.1}:9090:9000
environment:
CLICKHOUSE_ADMIN_USER: ${CLICKHOUSE_USER:-default}
CLICKHOUSE_ADMIN_PASSWORD: ${CLICKHOUSE_PASSWORD:-password}
volumes:
- clickhouse:/bitnami/clickhouse
- ../clickhouse/override.xml:/bitnami/clickhouse/etc/config.d/override.xml:ro
networks:
- webapp
healthcheck:
test: ["CMD", "clickhouse-client", "--host", "localhost", "--port", "9000", "--user", "default", "--password", "password", "--query", "SELECT 1"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
registry:
image: registry:${REGISTRY_IMAGE_TAG:-2}
restart: ${RESTART_POLICY:-unless-stopped}
logging: *logging-config
ports:
- ${REGISTRY_PUBLISH_IP:-127.0.0.1}:5000:5000
networks:
- webapp
volumes:
# registry-user:very-secure-indeed
- ../registry/auth.htpasswd:/auth/htpasswd:ro
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:5000/"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
minio:
image: bitnami/minio:${MINIO_IMAGE_TAG:-latest}
restart: ${RESTART_POLICY:-unless-stopped}
logging: *logging-config
ports:
- ${MINIO_PUBLISH_IP:-127.0.0.1}:9000:9000
- ${MINIO_PUBLISH_IP:-127.0.0.1}:9001:9001
networks:
- webapp
volumes:
- minio:/bitnami/minio/data
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-admin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-very-safe-password}
MINIO_DEFAULT_BUCKETS: packets
MINIO_BROWSER: "on"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 5s
timeout: 10s
retries: 5
start_period: 10s
# Worker related
supervisor:
image: ghcr.io/triggerdotdev/supervisor:${TRIGGER_IMAGE_TAG:-v4-beta}
restart: ${RESTART_POLICY:-unless-stopped}
logging: *logging-config
depends_on:
- docker-proxy
networks:
- supervisor
- docker-proxy
- webapp
volumes:
- shared:/home/node/shared
# Only needed for bootstrap
user: root
# Only needed for bootstrap
command: sh -c "chown -R node:node /home/node/shared && exec /usr/bin/dumb-init -- pnpm run --filter supervisor start"
environment:
# This needs to match the token of the worker group you want to connect to
# TRIGGER_WORKER_TOKEN: ${TRIGGER_WORKER_TOKEN}
# Use the bootstrap token created by the webapp
TRIGGER_WORKER_TOKEN: file:///home/node/shared/worker_token
MANAGED_WORKER_SECRET: ${MANAGED_WORKER_SECRET}
TRIGGER_API_URL: ${TRIGGER_API_URL:-http://webapp:3000}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://webapp:3000/otel}
TRIGGER_WORKLOAD_API_DOMAIN: supervisor
TRIGGER_WORKLOAD_API_PORT_EXTERNAL: 8020
# Optional settings
DEBUG: 1
ENFORCE_MACHINE_PRESETS: 1
TRIGGER_DEQUEUE_INTERVAL_MS: 1000
DOCKER_HOST: tcp://docker-proxy:2375
DOCKER_RUNNER_NETWORKS: webapp,supervisor
DOCKER_REGISTRY_URL: ${DOCKER_REGISTRY_URL:-localhost:5000}
DOCKER_REGISTRY_USERNAME: ${DOCKER_REGISTRY_USERNAME:-}
DOCKER_REGISTRY_PASSWORD: ${DOCKER_REGISTRY_PASSWORD:-}
DOCKER_AUTOREMOVE_EXITED_CONTAINERS: 0
healthcheck:
test:
[
"CMD",
"node",
"-e",
"http.get('http://localhost:8020/health', res => process.exit(res.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))",
]
interval: 30s
timeout: 10s
retries: 5
start_period: 10s
docker-proxy:
image: tecnativa/docker-socket-proxy:${DOCKER_PROXY_IMAGE_TAG:-latest}
restart: ${RESTART_POLICY:-unless-stopped}
logging: *logging-config
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- docker-proxy
environment:
- LOG_LEVEL=info
- POST=1
- CONTAINERS=1
- IMAGES=1
- INFO=1
- NETWORKS=1
healthcheck:
test: ["CMD", "nc", "-z", "127.0.0.1", "2375"]
interval: 30s
timeout: 5s
retries: 5
start_period: 5s
volumes:
shared:
clickhouse:
shared:
minio:
networks:
docker-proxy:
name: docker-proxy
supervisor:
name: supervisor
webapp:
name: webapp

View File

@ -60,6 +60,9 @@
"MAGIC_LINK_SECRET",
"ENABLE_EMAIL_LOGIN",
"MODEL",
"OLLAMA_URL"
"OLLAMA_URL",
"TRIGGER_PROJECT_ID",
"TRIGGER_API_URL",
"TRIGGER_API_KEY"
]
}