OpenRAG • Setup inicial
++ Preencha os campos mínimos e inicie o stack. +
+ ++ Opções avançadas +
+
+ {raw}
+
+ diff --git a/Dockerfile.init-ui b/Dockerfile.init-ui new file mode 100644 index 00000000..03572ad6 --- /dev/null +++ b/Dockerfile.init-ui @@ -0,0 +1,26 @@ +FROM node:20-alpine + +# dependências +RUN apk add --no-cache bash curl ca-certificates jq + +# instala docker cli +RUN curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-26.1.3.tgz \ + | tar -xz -C /usr/local/bin --strip-components=1 docker/docker + +# instala docker compose v2 plugin +RUN mkdir -p /usr/lib/docker/cli-plugins \ + && curl -fsSL -o /usr/lib/docker/cli-plugins/docker-compose \ + https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-x86_64 \ + && chmod +x /usr/lib/docker/cli-plugins/docker-compose + + WORKDIR /app + + COPY init-ui/package.json init-ui/package-lock.json* ./ + RUN npm ci + + COPY init-ui/ . + + RUN npm run build + EXPOSE 8080 + CMD ["npm", "run", "start"] + \ No newline at end of file diff --git a/init-ui/.gitignore b/init-ui/.gitignore new file mode 100644 index 00000000..5ef6a520 --- /dev/null +++ b/init-ui/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/init-ui/README.md b/init-ui/README.md new file mode 100644 index 00000000..e215bc4c --- /dev/null +++ b/init-ui/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/init-ui/eslint.config.mjs b/init-ui/eslint.config.mjs new file mode 100644 index 00000000..719cea2b --- /dev/null +++ b/init-ui/eslint.config.mjs @@ -0,0 +1,25 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), + { + ignores: [ + "node_modules/**", + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ], + }, +]; + +export default eslintConfig; diff --git a/init-ui/next.config.ts b/init-ui/next.config.ts new file mode 100644 index 00000000..68a6c64d --- /dev/null +++ b/init-ui/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + output: "standalone", +}; + +export default nextConfig; diff --git a/init-ui/postcss.config.mjs b/init-ui/postcss.config.mjs new file mode 100644 index 00000000..c7bcb4b1 --- /dev/null +++ b/init-ui/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/init-ui/public/file.svg b/init-ui/public/file.svg new file mode 100644 index 00000000..004145cd --- /dev/null +++ b/init-ui/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/init-ui/public/globe.svg b/init-ui/public/globe.svg new file mode 100644 index 00000000..567f17b0 --- /dev/null +++ b/init-ui/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/init-ui/public/next.svg b/init-ui/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/init-ui/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/init-ui/public/vercel.svg b/init-ui/public/vercel.svg new file mode 100644 index 00000000..77053960 --- /dev/null +++ b/init-ui/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/init-ui/public/window.svg b/init-ui/public/window.svg new file mode 100644 index 00000000..b2b2a44f --- /dev/null +++ b/init-ui/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/init-ui/src/app/api/config/route.ts b/init-ui/src/app/api/config/route.ts new file mode 100644 index 00000000..f816ab2b --- /dev/null +++ b/init-ui/src/app/api/config/route.ts @@ -0,0 +1,51 @@ +// init-ui/src/app/api/config/route.ts + +import fs from "node:fs/promises"; +import { NextResponse } from "next/server"; +import { z } from "zod"; + +const ENV_PATH = process.env.APP_ENV_FILE || "/app/.env"; + +const schema = z.object({ + OPENSEARCH_PASSWORD: z.string().min(1, "Obrigatório"), + LANGFLOW_SECRET_KEY: z.string().min(1, "Obrigatório"), + OPENAI_API_KEY: z.string().min(1, "Obrigatório"), + GOOGLE_OAUTH_CLIENT_ID: z.string().optional(), + GOOGLE_OAUTH_CLIENT_SECRET: z.string().optional(), + MICROSOFT_GRAPH_OAUTH_CLIENT_ID: z.string().optional(), + MICROSOFT_GRAPH_OAUTH_CLIENT_SECRET: z.string().optional(), + WEBHOOK_BASE_URL: z.string().optional(), + AWS_ACCESS_KEY_ID: z.string().optional(), + AWS_SECRET_ACCESS_KEY: z.string().optional(), + LANGFLOW_PUBLIC_URL: z.string().optional(), + LANGFLOW_SUPERUSER: z.string().optional(), + LANGFLOW_SUPERUSER_PASSWORD: z.string().optional(), +}); + +function setKV(text: string, key: string, value: string) { + const re = new RegExp(`^${key}=.*$`, "m"); + const line = `${key}=${value}`; + return re.test(text) + ? text.replace(re, line) + : text.replace(/\s*$/, "") + `\n${line}\n`; +} + +export async function POST(req: Request) { + const body = await req.json(); + const data = schema.parse(body); + + let envText = ""; + try { + envText = await fs.readFile(ENV_PATH, "utf8"); + } catch {} + + // grava campos relevantes + for (const [k, v] of Object.entries(data)) { + if (v && typeof v === "string") envText = setKV(envText, k, v); + } + // troca profile p/ app + envText = setKV(envText, "COMPOSE_PROFILES", "app"); + + await fs.writeFile(ENV_PATH, envText, "utf8"); + return NextResponse.json({ ok: true }); +} diff --git a/init-ui/src/app/api/status/route.ts b/init-ui/src/app/api/status/route.ts new file mode 100644 index 00000000..d95f6743 --- /dev/null +++ b/init-ui/src/app/api/status/route.ts @@ -0,0 +1,11 @@ +// init-ui/src/app/api/status/route.ts +import { NextResponse } from "next/server"; +import { listProjectContainers, progressFromHealth } from "@/docker"; + +const PROJECT = process.env.COMPOSE_PROJECT_NAME || "mystack"; + +export async function GET() { + const items = await listProjectContainers(PROJECT); + const progress = progressFromHealth(items); + return NextResponse.json({ items, progress }); +} diff --git a/init-ui/src/app/favicon.ico b/init-ui/src/app/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/init-ui/src/app/favicon.ico differ diff --git a/init-ui/src/app/globals.css b/init-ui/src/app/globals.css new file mode 100644 index 00000000..97afb5e8 --- /dev/null +++ b/init-ui/src/app/globals.css @@ -0,0 +1,122 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.141 0.005 285.823); + --card: oklch(1 0 0); + --card-foreground: oklch(0.141 0.005 285.823); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.141 0.005 285.823); + --primary: oklch(0.21 0.006 285.885); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.967 0.001 286.375); + --muted-foreground: oklch(0.552 0.016 285.938); + --accent: oklch(0.967 0.001 286.375); + --accent-foreground: oklch(0.21 0.006 285.885); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.92 0.004 286.32); + --input: oklch(0.92 0.004 286.32); + --ring: oklch(0.705 0.015 286.067); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.141 0.005 285.823); + --sidebar-primary: oklch(0.21 0.006 285.885); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); + --sidebar-border: oklch(0.92 0.004 286.32); + --sidebar-ring: oklch(0.705 0.015 286.067); +} + +.dark { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.92 0.004 286.32); + --primary-foreground: oklch(0.21 0.006 285.885); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.552 0.016 285.938); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.552 0.016 285.938); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/init-ui/src/app/layout.tsx b/init-ui/src/app/layout.tsx new file mode 100644 index 00000000..f7fa87eb --- /dev/null +++ b/init-ui/src/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + +
+ {children} + + + ); +} diff --git a/init-ui/src/app/page.tsx b/init-ui/src/app/page.tsx new file mode 100644 index 00000000..e63a4c71 --- /dev/null +++ b/init-ui/src/app/page.tsx @@ -0,0 +1,182 @@ +"use client"; +import { useEffect, useRef, useState } from "react"; + +type FormState = { + OPENSEARCH_PASSWORD: string; + LANGFLOW_SECRET_KEY: string; + OPENAI_API_KEY: string; + GOOGLE_OAUTH_CLIENT_ID?: string; + GOOGLE_OAUTH_CLIENT_SECRET?: string; + MICROSOFT_GRAPH_OAUTH_CLIENT_ID?: string; + MICROSOFT_GRAPH_OAUTH_CLIENT_SECRET?: string; + WEBHOOK_BASE_URL?: string; + AWS_ACCESS_KEY_ID?: string; + AWS_SECRET_ACCESS_KEY?: string; + LANGFLOW_PUBLIC_URL?: string; + LANGFLOW_SUPERUSER?: string; + LANGFLOW_SUPERUSER_PASSWORD?: string; +}; + +export default function Page() { + const [form, setForm] = useState+ Preencha os campos mínimos e inicie o stack. +
+ +
+ {raw}
+
+