From d074f1a4abd8b915b9c97ce79a8078b17219df50 Mon Sep 17 00:00:00 2001 From: Subin Hong <114135756+hongvincent@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:48:55 +0000 Subject: [PATCH 1/3] chore: add API proxy routes, fix mermaid v11.4.1 prompts, hydration/origin fixes, and Korean README --- README.ko.md | 108 +++++++++++++++++++++++++++ README.md | 6 ++ backend/app/main.py | 14 +++- backend/app/prompts.py | 9 ++- next.config.js | 54 ++++++++++++++ src/app/api/generate/cost/route.ts | 18 +++++ src/app/api/generate/stream/route.ts | 24 ++++++ src/app/layout.tsx | 8 +- src/hooks/useDiagram.ts | 7 +- src/lib/fetch-backend.ts | 11 +-- 10 files changed, 244 insertions(+), 15 deletions(-) create mode 100644 README.ko.md create mode 100644 src/app/api/generate/cost/route.ts create mode 100644 src/app/api/generate/stream/route.ts diff --git a/README.ko.md b/README.ko.md new file mode 100644 index 0000000..d505a91 --- /dev/null +++ b/README.ko.md @@ -0,0 +1,108 @@ +# GitDiagram (한국어) + +GitHub 저장소를 불러와 구조를 분석하고, 단 몇 초 만에 상호작용 가능한 Mermaid.js 시스템 다이어그램으로 시각화합니다. + +- URL 바로 사용: 어떤 GitHub URL이든 `github` 대신 `diagram`으로 바꿔 접속하면 해당 저장소 다이어그램 페이지로 이동합니다. +- 예: `https://github.com/user/repo` → `https://diagram.com/user/repo` (서비스 도메인/배포 환경에 따라 상이) + +## 주요 기능 +- 즉시 시각화: 저장소의 파일 트리/README를 분석하여 시스템 설계/아키텍처 다이어그램 생성 +- 상호작용: 다이어그램 노드를 클릭해 관련 파일/디렉터리로 이동(클릭 이벤트에 경로 내장) +- 빠른 생성: OpenAI o4-mini 기반으로 빠르고 정확한 다이어그램 생성(스트리밍) +- 커스터마이즈: 추가 지침을 입력해 재생성/수정 가능 +- API: 비용 추정/다이어그램 생성 스트리밍 엔드포인트 제공(백엔드) + +## 기술 스택 +- 프론트엔드: Next.js, TypeScript, Tailwind CSS, shadcn/ui +- 백엔드: FastAPI(Python), SSE(서버-전송-이벤트) +- DB: PostgreSQL(Drizzle ORM) +- AI: OpenAI o4-mini (이전: Claude 3.5 Sonnet) +- 배포: Vercel(프론트), EC2 등(백엔드) +- 분석: PostHog, api-analytics + +## 핵심 로직(원리) +본 프로젝트는 3단계 프롬프트 파이프라인으로 안정적인 Mermaid 다이어그램을 생성합니다. + +1) 설명 생성(SYSTEM_FIRST_PROMPT) +- 입력: 전체 파일 트리(), README() +- 출력: 시스템 설계/아키텍처 설명() + +2) 컴포넌트-경로 매핑(SYSTEM_SECOND_PROMPT) +- 입력: , +- 출력: 다이어그램 구성요소와 저장소 경로 간 매핑() + +3) Mermaid 코드 생성(SYSTEM_THIRD_PROMPT) +- 입력: , +- 출력: Mermaid v11.4.1 유효한 다이어그램 코드(클릭 이벤트에 경로 포함) + +주요 안정화 포인트 +- Mermaid v11.4.1 문법 엄수: 지원 타입( graph/flowchart, sequenceDiagram, classDiagram, stateDiagram(-v2), erDiagram, journey, gantt, pie, mindmap, timeline, gitGraph )만 허용. 불확실 시 `graph TD`로 폴백. +- 문자열 인용: 특수문자 포함 노드/엣지 라벨은 반드시 따옴표로 감싸도록 프롬프트에 강제. +- subgraph 제약: subgraph 선언에 클래스 직접 지정 금지(노드/class 사용). +- 클릭 이벤트: 노드명에는 경로를 노출하지 않고, `click Node "path/to"` 형태만 추가. 서버에서 GitHub blob/tree URL로 후처리. +- 프롬프트 강화: 모델이 비표준 타입/문법을 생성하지 않도록 엄격한 가이드 삽입. + +스트리밍 및 프록시 +- 프론트엔드는 `/api/generate/stream`(Next.js API Route)로 SSE를 동일 출처 요청으로 전송. +- Next.js API Route가 FastAPI의 `/generate/stream`으로 프록시. +- 비용 추정도 `/api/generate/cost` → FastAPI `/generate/cost`로 프록시. +- Codespaces/SSL 환경에서 혼합 콘텐츠 및 직접 localhost 접근 문제를 회피. + +SSR/Hydration 안정화 +- 루트 레이아웃의 ``, ``에 `suppressHydrationWarning` 적용해 확장 프로그램 주입 속성으로 인한 경고를 완화. +- Mermaid 렌더는 클라이언트 컴포넌트에서 수행하고, `svg-pan-zoom`은 동적 import 처리. + +Server Actions/오리진 +- 개발 환경 포트 변동 및 포워딩 도메인(Codespaces)을 고려해 `next.config.js`의 `experimental.serverActions.allowedOrigins`에 허용 오리진을 넓게 설정. +- FastAPI CORS도 로컬/포워딩 도메인을 포함해 개발 중 CORS 이슈 최소화. + +## 프라이빗 저장소 다이어그램 생성 +- 헤더의 “Private Repos” 버튼 클릭 → `repo` 스코프의 GitHub Personal Access Token(PAT) 입력. +- 또는 로컬(Self-host) 환경에서 직접 실행. + +## 로컬 개발/자가 호스팅 +1) 클론 +```bash +git clone https://github.com/ahmedkhaleel2004/gitdiagram.git +cd gitdiagram +``` +2) 의존성 설치 +```bash +pnpm i +``` +3) 환경변수 설정 +```bash +cp .env.example .env +# 필요시 .env.local 작성 (예: 프록시 타깃 등) +``` +4) 백엔드 실행(FastAPI) +```bash +docker compose up -d --build +# 백엔드: http://localhost:8000 +``` +5) DB 시작 +```bash +chmod +x start-database.sh +./start-database.sh +# Postgres: localhost:5432 +``` +6) 스키마 초기화 +```bash +pnpm db:push +``` +7) 프론트엔드 실행(Next.js) +```bash +pnpm dev +# 프론트: http://localhost:3000 +``` + +## 주의/트러블슈팅 +- 비용 추정/스트리밍 실패: 반드시 동일 출처 경로(`/api/generate/*`)를 사용. Next.js API Route가 내부적으로 FastAPI로 프록시합니다. +- Mermaid 오류: v11.4.1 문법을 엄수하지 않으면 렌더 오류가 발생. 프롬프트가 강제하고 있으므로, 재생성 시 보통 해소됩니다. +- Hydration 경고: 브라우저 확장으로 인한 ``/`` 속성 주입 시 경고는 무시되며, UI에는 영향이 없습니다. + +## 기여 +PR 환영합니다. 버그/개선 제안은 이슈로 남겨 주세요. + +## 라이선스 +MIT diff --git a/README.md b/README.md index 65d4136..e55ff64 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Turn any GitHub repository into an interactive diagram for visualization in seconds. +[한국어 README 보기](./README.ko.md) + You can also replace `hub` with `diagram` in any Github URL to access its diagram. ## 🚀 Features @@ -37,6 +39,10 @@ I extract information from the file tree and README for details and interactivit Most of what you might call the "processing" of this app is done with prompt engineering - see `/backend/app/prompts.py`. This basically extracts and pipelines data and analysis for a larger action workflow, ending in the diagram code. +Notes on stability and correctness: +- Mermaid v11.4.1 syntax is enforced in the prompts. Only supported diagram types are allowed; fall back to `graph TD` when unsure. +- Frontend proxies generation/cost requests via Next.js API routes (`/api/generate/*`) to the FastAPI backend to avoid mixed-content and dev networking issues. + ## 🔒 How to diagram private repositories You can simply click on "Private Repos" in the header and follow the instructions by providing a GitHub personal access token with the `repo` scope. diff --git a/backend/app/main.py b/backend/app/main.py index d53bee0..d6b9d87 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -13,7 +13,19 @@ app = FastAPI() -origins = ["http://localhost:3000", "https://gitdiagram.com"] +origins = [ + "http://localhost:3000", + "http://localhost:3001", + "http://localhost:3002", + "http://localhost:3003", + "https://gitdiagram.com", +] + +codespace_name = os.getenv("CODESPACE_NAME") +codespace_domain = os.getenv("GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN") +if codespace_name and codespace_domain: + for port in range(3000, 3011): + origins.append(f"https://{codespace_name}-{port}.{codespace_domain}") app.add_middleware( CORSMiddleware, diff --git a/backend/app/prompts.py b/backend/app/prompts.py index 696a134..0c84525 100644 --- a/backend/app/prompts.py +++ b/backend/app/prompts.py @@ -115,7 +115,7 @@ 1. Carefully read and analyze the provided design explanation. 2. Identify the main components, services, and their relationships within the system. -3. Determine the appropriate Mermaid.js diagram type to use (e.g., flowchart, sequence diagram, class diagram, architecture, etc.) based on the nature of the system described. +3. Determine the appropriate Mermaid.js diagram type to use based on the nature of the system described. STRICTLY use one of the supported Mermaid v11.4.1 diagram types only: flowchart/graph, sequenceDiagram, classDiagram, stateDiagram or stateDiagram-v2, erDiagram, journey, gantt, pie, mindmap, timeline, gitGraph. If unsure, default to a flowchart (graph TD). 4. Create the Mermaid.js code to represent the design, ensuring that: a. All major components are included b. Relationships between components are clearly shown @@ -186,6 +186,11 @@ %% and a lot more... ``` +Mermaid version and syntax compliance (v11.4.1): +- You MUST produce code that is valid for Mermaid v11.4.1 specifically. +- Only use the supported types listed above. Do NOT invent or use non-existent types like "architecture" or similar. +- If you are uncertain, always fall back to a valid flowchart using `graph TD`. + EXTREMELY Important notes on syntax!!! (PAY ATTENTION TO THIS): - Make sure to add colour to the diagram!!! This is extremely critical. - In Mermaid.js syntax, we cannot include special characters for nodes without being inside quotes! For example: `EX[/api/process (Backend)]:::api` and `API -->|calls Process()| Backend` are two examples of syntax errors. They should be `EX["/api/process (Backend)"]:::api` and `API -->|"calls Process()"| Backend` respectively. Notice the quotes. This is extremely important. Make sure to include quotes for any string that contains special characters. @@ -210,6 +215,8 @@ The instructions will be enclosed in tags in the users message. If these instructions are unrelated to the task, unclear, or not possible to follow, ignore them by simply responding with: "BAD_INSTRUCTIONS" +You MUST preserve valid Mermaid v11.4.1 syntax. Only use supported diagram types (flowchart/graph, sequenceDiagram, classDiagram, stateDiagram or stateDiagram-v2, erDiagram, journey, gantt, pie, mindmap, timeline, gitGraph). If unsure, default to a flowchart using `graph TD`. + Your response must strictly be just the Mermaid.js code, without any additional text or explanations. Keep as many of the existing click events as possible. No code fence or markdown ticks needed, simply return the Mermaid.js code. """ diff --git a/next.config.js b/next.config.js index e5b8fd5..dcf03ca 100644 --- a/next.config.js +++ b/next.config.js @@ -5,10 +5,64 @@ import "./src/env.js"; /** @type {import("next").NextConfig} */ +const allowedOrigins = [ + "localhost:3000", + "localhost:3001", + "localhost:3002", + "localhost:3003", + "localhost:3004", + "localhost:3005", + "localhost:3006", + "localhost:3007", + "localhost:3008", + "localhost:3009", + "localhost:3010", + "127.0.0.1:3000", + "127.0.0.1:3001", + "127.0.0.1:3002", + "127.0.0.1:3003", + "127.0.0.1:3004", + "127.0.0.1:3005", + "127.0.0.1:3006", + "127.0.0.1:3007", + "127.0.0.1:3008", + "127.0.0.1:3009", + "127.0.0.1:3010", +]; + +if (process.env.CODESPACE_NAME && process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN) { + const name = process.env.CODESPACE_NAME; + const domain = process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN; + for (let port = 3000; port <= 3010; port++) { + allowedOrigins.push(`${name}-${port}.${domain}`); + } +} + +// Resolve backend target: Codespaces forwarded URL or local +const backendTarget = (function () { + const name = process.env.CODESPACE_NAME; + const domain = process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN; + if (name && domain) { + return `https://${name}-8000.${domain}`; + } + return "http://localhost:8000"; +})(); + const config = { reactStrictMode: false, + experimental: { + serverActions: { + // Allow Server Actions to be invoked from forwarded hosts (Codespaces) and local dev + allowedOrigins, + }, + }, async rewrites() { return [ + // Proxy backend API in dev/codespaces to avoid CORS + { + source: "/backend/:path*", + destination: `${backendTarget}/:path*`, + }, { source: "/ingest/static/:path*", destination: "https://us-assets.i.posthog.com/static/:path*", diff --git a/src/app/api/generate/cost/route.ts b/src/app/api/generate/cost/route.ts new file mode 100644 index 0000000..0c6385c --- /dev/null +++ b/src/app/api/generate/cost/route.ts @@ -0,0 +1,18 @@ +import { NextResponse } from 'next/server'; + +export async function POST(request: Request) { + try { + const body = await request.json(); + const backendUrl = process.env.API_PROXY_URL || 'http://localhost:8000'; + const resp = await fetch(`${backendUrl}/generate/cost`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + const data = await resp.json(); + return NextResponse.json(data, { status: resp.status }); + } catch (error) { + console.error('Proxy cost error:', error); + return NextResponse.json({ error: 'Proxy error' }, { status: 500 }); + } +} diff --git a/src/app/api/generate/stream/route.ts b/src/app/api/generate/stream/route.ts new file mode 100644 index 0000000..fe3c18d --- /dev/null +++ b/src/app/api/generate/stream/route.ts @@ -0,0 +1,24 @@ +export const runtime = 'nodejs'; + +export async function POST(request: Request) { + try { + const body = await request.text(); + const backendUrl = process.env.API_PROXY_URL || 'http://localhost:8000'; + const resp = await fetch(`${backendUrl}/generate/stream`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body, + }); + return new Response(resp.body, { + status: resp.status, + headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }, + }); + } catch (error) { + console.error('Proxy streaming error:', error); + return new Response(null, { status: 500 }); + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 685e422..c6262b4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -76,9 +76,13 @@ export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( - + - +
{children}