mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-12 01:48:27 +00:00
Feat: v2
This commit is contained in:
parent
a819a682a2
commit
fa8d2064e1
@ -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
6
.gitignore
vendored
@ -37,4 +37,8 @@ yarn-error.log*
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
docker-compose.dev.yaml
|
||||
docker-compose.dev.yaml
|
||||
|
||||
clickhouse/
|
||||
.vscode/
|
||||
registry/
|
||||
57
LICENSE
57
LICENSE
@ -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.
|
||||
|
||||
2
apps/webapp/.gitignore
vendored
2
apps/webapp/.gitignore
vendored
@ -3,3 +3,5 @@ node_modules
|
||||
/.cache
|
||||
/build
|
||||
.env
|
||||
|
||||
.trigger
|
||||
@ -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>}
|
||||
|
||||
20
apps/webapp/app/components/graph/graph-client.tsx
Normal file
20
apps/webapp/app/components/graph/graph-client.tsx
Normal 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} />;
|
||||
}
|
||||
@ -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}
|
||||
|
||||
@ -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
@ -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",
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
42
apps/webapp/app/components/logo/core.svg
Normal file
42
apps/webapp/app/components/logo/core.svg
Normal 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
@ -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>
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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
|
||||
|
||||
47
apps/webapp/app/components/ui/header.tsx
Normal file
47
apps/webapp/app/components/ui/header.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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: {
|
||||
|
||||
@ -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))]",
|
||||
},
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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 />
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
112
apps/webapp/app/routes/settings.tsx
Normal file
112
apps/webapp/app/routes/settings.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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({
|
||||
|
||||
@ -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: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
0
apps/webapp/app/trigger/.gitkeep
Normal file
0
apps/webapp/app/trigger/.gitkeep
Normal 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,
|
||||
});
|
||||
},
|
||||
});
|
||||
90
apps/webapp/app/trigger/integrations/integration-run.ts
Normal file
90
apps/webapp/app/trigger/integrations/integration-run.ts
Normal 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 : {}),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
64
apps/webapp/app/trigger/integrations/scheduler.ts
Normal file
64
apps/webapp/app/trigger/integrations/scheduler.ts
Normal 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";
|
||||
},
|
||||
});
|
||||
@ -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",
|
||||
|
||||
23
apps/webapp/trigger.config.ts
Normal file
23
apps/webapp/trigger.config.ts
Normal 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"],
|
||||
});
|
||||
@ -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"],
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
@ -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
841
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
138
trigger/.env.example
Normal file
138
trigger/.env.example
Normal 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
245
trigger/docker-compose.yaml
Normal 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
|
||||
@ -60,6 +60,9 @@
|
||||
"MAGIC_LINK_SECRET",
|
||||
"ENABLE_EMAIL_LOGIN",
|
||||
"MODEL",
|
||||
"OLLAMA_URL"
|
||||
"OLLAMA_URL",
|
||||
"TRIGGER_PROJECT_ID",
|
||||
"TRIGGER_API_URL",
|
||||
"TRIGGER_API_KEY"
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user