diff --git a/ai-new-thread.md b/ai-new-thread.md index 5ca4c76a..9a48812f 100644 --- a/ai-new-thread.md +++ b/ai-new-thread.md @@ -51,23 +51,35 @@ DO NOT treat `master-ai` as a single monorepo on Gitea. You must push changes in │ Remote 'coolify_agent_gitea' -> https://git.vibnai.com/mark/vibn-agent-runner.git ├── vibn-frontend/ <-- Subfolder of master-ai. Pushes via: │ Remote 'coolify_gitea' -> https://git.vibnai.com/mark/vibn-frontend.git -└── vibn-api/ <-- Subfolder of master-ai. Pushes via: - Remote 'coolify_api_gitea' -> https://git.vibnai.com/mark/vibn-api.git +├── vibn-api/ <-- Subfolder of master-ai. Pushes via: +│ Remote 'coolify_api_gitea' -> https://git.vibnai.com/mark/vibn-api.git +└── vibn-telemetry-service/ <-- Subfolder of master-ai (Training Data Microservice). Pushes via: + Remote 'coolify_telemetry_gitea' -> https://git.vibnai.com/mark/vibn-telemetry-service.git ``` ### Git Remotes Reference (Configured in `/Users/markhenderson/master-ai`): -* `coolify_agent_gitea` : `https://git.vibnai.com/mark/vibn-agent-runner.git` -* `coolify_gitea` : `https://git.vibnai.com/mark/vibn-frontend.git` -* `coolify_api_gitea` : `https://git.vibnai.com/mark/vibn-api.git` -* `gitea` : `https://git.vibnai.com/mark/master-ai.git` *(share-only: for a coworker's local setup; **builds do NOT use this**)* -* `origin` : `https://github.com/MawkOne/master-ai.git` *(GitHub mirror)* +* `coolify_agent_gitea` : `https://git.vibnai.com/mark/vibn-agent-runner.git` +* `coolify_gitea` : `https://git.vibnai.com/mark/vibn-frontend.git` +* `coolify_api_gitea` : `https://git.vibnai.com/mark/vibn-api.git` +* `coolify_telemetry_gitea` : `https://git.vibnai.com/mark/vibn-telemetry-service.git` +* `gitea` : `https://git.vibnai.com/mark/master-ai.git` *(share-only: for a coworker's local setup; **builds do NOT use this**)* +* `origin` : `https://github.com/MawkOne/master-ai.git` *(GitHub mirror)* **How deploys actually work:** `master-ai` is a single git repo. Each cloud app builds from its **own** Gitea remote, from the matching subfolder. To ship a change, commit in `master-ai`, then -`git push HEAD:main` (e.g. `git push coolify_agent_gitea HEAD:main` for the runner), then trigger the +`git push HEAD:main` (e.g. `git push coolify_telemetry_gitea HEAD:main`), then trigger the Coolify deploy for that app (see `VIBNDEV.md`). `vibn-code` is a nested submodule with its own `.git` — commit & push it via its own `origin`. Secret `.env*` files at the repo root are gitignored — never commit them. +### Deploying the Telemetry Service manually via Coolify UI: +Because Coolify's API strictly blocks the programmatic creation of GitHub/Gitea Apps, the Telemetry service must be linked manually once: +1. Open [Coolify Dashboard -> vibn-infrastructure -> production](https://coolify.vibnai.com/project/f4owwggokksgw0ogo0844os0/environment/foskksoccksk0kc4g8sk88ok) +2. Click **+ Add -> Application -> Private Repository (with Gitea)**. +3. Select `vibn-telemetry-service` and branch `main`. +4. Set Build Pack to `Dockerfile` and Ports Exposes to `4000`. +5. Under Environment Variables, add `DATABASE_URL=postgresql://:@/` +6. Deploy it, then add `TELEMETRY_SERVICE_URL=http://:4000` to the `vibn-frontend` environments. + --- ## 3. Key Tech Stacks & Development Tools diff --git a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx index c5e88069..78e9deac 100644 --- a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx +++ b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx @@ -19,15 +19,47 @@ function sandboxIframe(src: string, origin: string): boolean { } } +/** How long a deployment has been running, formatted as "1m 23s" */ +function useElapsed(sinceIso: string | undefined) { + const [elapsed, setElapsed] = useState(""); + useEffect(() => { + // No ISO timestamp — nothing to tick. The caller won't render + // the elapsed string when sinceIso is undefined anyway. + if (!sinceIso) return; + const update = () => { + const ms = Date.now() - new Date(sinceIso).getTime(); + if (ms < 0) return; + const s = Math.floor(ms / 1000); + const m = Math.floor(s / 60); + setElapsed(m > 0 ? `${m}m ${s % 60}s` : `${s}s`); + }; + update(); + const id = setInterval(update, 1000); + return () => { + clearInterval(id); + setElapsed(""); + }; + }, [sinceIso]); + return elapsed; +} + export default function PreviewTab() { const params = useParams(); const projectId = params.projectId as string; - const { anatomy, loading } = useAnatomy(projectId, { pollMs: 0 }); + + // Poll every 5 s so build-state transitions surface without a manual refresh. + const { anatomy, loading } = useAnatomy(projectId, { pollMs: 5000 }); const previews = anatomy?.hosting.previews ?? []; - // Find the port 3000 preview if it exists, otherwise fall back to null const primaryPreview = previews.find((p) => p.port === 3000); + // Derive in-flight / recently-failed build from prod apps. + const liveApps = anatomy?.hosting.live ?? []; + const inFlightApp = liveApps.find((a) => a.inFlightBuild); + const failedApp = !inFlightApp + ? liveApps.find((a) => a.lastBuild?.status === "failed") + : undefined; + const [iframeSrc, setIframeSrc] = useState(null); const iframeDomRef = useRef(null); const bridge = usePreviewBridge(); @@ -45,6 +77,14 @@ export default function PreviewTab() { bridge.registerPreviewIframe(iframeDomRef.current, iframeSrc); }, [bridge, iframeSrc]); + // Derive content for the empty state. + const emptyContent = (() => { + if (loading && !anatomy) return ; + if (inFlightApp) return ; + if (failedApp) return ; + return ; + })(); + return (
{deviceMode === "mobile" && } - {loading && !iframeSrc ? ( -
- -
- ) : iframeSrc ? ( + {iframeSrc ? (