LangGraph μμ΄μ νΈλ₯Ό μν Next.js κΈ°λ° μ±ν μΈν°νμ΄μ€μ λλ€. YAML κΈ°λ° μ€μ μ ν΅ν λμ 컀μ€ν°λ§μ΄μ§ μ΅μ μ μ 곡ν©λλ€.
μλ³Έ νλ‘μ νΈμΈ https://github.com/langchain-ai/agent-chat-ui μ μ°Έκ³ νμ¬ μμ ν νλ‘μ νΈ μ λλ€.
Agent Chat UIλ Next.js 15λ‘ κ΅¬μΆλ νλ‘λμ μμ€μ μ±ν μΈν°νμ΄μ€λ‘, LangGraph μλ²μμ μνν μνΈμμ©μ μ§μν©λλ€. YAML κΈ°λ° μ€μ μ ν΅ν UI 컀μ€ν°λ§μ΄μ§, νμΌ μ λ‘λ, λν κΈ°λ‘ κ΄λ¦¬, μ€μκ° μ€νΈλ¦¬λ° μλ΅ λ±μ κΈ°λ₯μ μ 곡ν©λλ€.
- Node.js 18.x μ΄μ
- npm 9.x μ΄μ (ν¨ν€μ§ λ§€λμ )
- Python 3.11 μ΄μ
- uv (Python ν¨ν€μ§ λ§€λμ )
- OpenAI API ν€ (λλ λ€λ₯Έ LLM API ν€)
git clone https://github.com/teddylee777/agent-chat-ui.git
cd agent-chat-uinpm installμ΄ μ±ν UIλ LangGraph λ°±μλ μλ²μ μ°κ²°νμ¬ λμν©λλ€. λ‘컬 κ°λ° νκ²½μμ ν μ€νΈνλ €λ©΄ λ¨Όμ λ°±μλ μλ²λ₯Ό μ€μ ν΄μΌ ν©λλ€.
# λ°±μλ μ μ₯μ ν΄λ‘
git clone https://github.com/teddylee777/react-agent
cd react-agent
# νκ²½ λ³μ μ€μ
cp .env.example .env
# .env νμΌμ νΈμ§νμ¬ νμν API ν€ μ€μ (OPENAI_API_KEY λ±)
# LangGraph κ°λ° μλ² μ€ν
uv run langgraph devλ°±μλ μλ²κ° http://localhost:2024μμ μ€νλ©λλ€.
agent-chat-ui λλ ν λ¦¬λ‘ λμμμ .env νμΌμ μμ±ν©λλ€:
cd ../agent-chat-ui
cp .env.example .envλ‘컬 κ°λ°μ μν νκ²½ λ³μλ₯Ό μ€μ ν©λλ€:
# νμ: LangGraph API μλν¬μΈνΈ (λ‘컬 κ°λ°μ©)
NEXT_PUBLIC_API_URL=http://localhost:2024
# νμ: Assistant/Graph ID
NEXT_PUBLIC_ASSISTANT_ID=agent
# μ ν: νλ‘λμ
λ°°ν¬μ©
LANGGRAPH_API_URL=https://your-deployment.langgraph.app
LANGSMITH_API_KEY=lsv2_...1λ¨κ³: LangGraph λ°±μλ μλ² μ€ν
λ¨Όμ λ°±μλ μλ²λ₯Ό μ€νν©λλ€ (react-agent λλ ν 리μμ):
cd react-agent
uv run langgraph devλ°±μλ μλ²κ° http://localhost:2024μμ μ€νλ©λλ€.
2λ¨κ³: νλ‘ νΈμλ κ°λ° μλ² μ€ν
μ ν°λ―Έλ μ°½μ μ΄μ΄ νλ‘ νΈμλ μλ²λ₯Ό μμν©λλ€:
cd agent-chat-ui
npm run devνλ‘ νΈμλ μ ν리μΌμ΄μ
μ΄ http://localhost:3000μμ μ€νλ©λλ€.
μ΄μ λΈλΌμ°μ μμ http://localhost:3000μ μ μνλ©΄ LangGraph λ°±μλμ μ°κ²°λ μ±ν
μΈν°νμ΄μ€λ₯Ό μ¬μ©ν μ μμ΅λλ€.
νλ‘λμ μλ²λ₯Ό λΉλνκ³ μμν©λλ€:
npm run build
npm start# λ¦°ν° μ€ν
npm run lint
# λ¦°ν
λ¬Έμ μλ μμ
npm run lint:fix
# Prettierλ‘ μ½λ ν¬λ§·ν
npm run format
# μ½λ ν¬λ§· κ²μ¬
npm run format:checkμ ν리μΌμ΄μ
μ public λλ ν 리μ YAML νμΌλ€μ ν΅ν΄ μ€μ λ©λλ€. μ΄ νμΌλ€μ λΈλλ©λΆν° UI λμκΉμ§ μ±ν
μΈν°νμ΄μ€μ λͺ¨λ μΈ‘λ©΄μ μ μ΄ν©λλ€.
-
public/chat-config.yaml- μ±ν μΈν°νμ΄μ€ μ 체 μ€μ - λΈλλ©, λ²νΌ, λꡬ, λ©μμ§, μ€λ λ, ν λ§, UI λμ λ±μ μ μ΄
-
public/chat-openers.yaml- λν μμ μμ μ§λ¬Έ- λλ© νμ΄μ§μ νμλ μ§λ¬Έ λͺ©λ‘ (μ΅λ 4κ° κΆμ₯)
| μ΅μ | νμ | μ€λͺ | μμ |
|---|---|---|---|
appName |
string | ν€λμ νμλλ μ ν리μΌμ΄μ μ΄λ¦ | "Agent Chat" |
logoPath |
string | public λλ ν 리 λ΄ λ‘κ³ μ΄λ―Έμ§ κ²½λ‘ | "/logo.png" |
logoWidth |
number | λ‘κ³ λλΉ(ν½μ ) | 32 |
logoHeight |
number | λ‘κ³ λμ΄(ν½μ ) | 32 |
| μ΅μ | νμ | μ€λͺ | κΈ°λ³Έκ° |
|---|---|---|---|
enableFileUpload |
boolean | νμΌ μ λ‘λ λ²νΌ νμ/μ¨κΉ | true |
fileUploadText |
string | νμΌ μ λ‘λ λ²νΌ ν μ€νΈ | "Upload PDF or Image" |
submitButtonText |
string | μ μ‘ λ²νΌ ν μ€νΈ | "Send" |
cancelButtonText |
string | μ·¨μ λ²νΌ ν μ€νΈ | "Cancel" |
| μ΅μ | νμ | μ€λͺ | κΈ°λ³Έκ° |
|---|---|---|---|
showToolCalls |
boolean | κΈ°λ³Έμ μΌλ‘ λꡬ νΈμΆ νμ | true |
displayMode |
string | "detailed" λλ "compact" |
"detailed" |
enabledTools |
string[] | νμ±νν λꡬ λͺ©λ‘ (λΉμ΄μμΌλ©΄ λͺ¨λ νμ±ν) | [] |
disabledTools |
string[] | λΉνμ±νν λꡬ λͺ©λ‘ | [] |
| μ΅μ | νμ | μ€λͺ | κΈ°λ³Έκ° |
|---|---|---|---|
maxWidth |
number | λ©μμ§ μ»¨ν μ΄λ μ΅λ λλΉ(ν½μ ) | 768 |
enableMarkdown |
boolean | λ§ν¬λ€μ΄ λ λλ§ νμ±ν | true |
enableMath |
boolean | LaTeX μμ λ λλ§ νμ±ν | true |
enableCodeHighlight |
boolean | μ½λ ꡬ문 κ°μ‘° νμ±ν | true |
enableTables |
boolean | ν μ΄λΈ λ λλ§ νμ±ν | true |
| μ΅μ | νμ | μ€λͺ | κΈ°λ³Έκ° |
|---|---|---|---|
showHistory |
boolean | λν κΈ°λ‘ μ¬μ΄λλ° νμ | false |
enableDeletion |
boolean | λν μμ κΈ°λ₯ νμ© | true |
enableTitleEdit |
boolean | λν μ λͺ© λ³κ²½ κΈ°λ₯ νμ© | true |
autoGenerateTitles |
boolean | λν μ λͺ© μλ μμ± | true |
| μ΅μ | νμ | κ° | κΈ°λ³Έκ° |
|---|---|---|---|
fontFamily |
string | "sans", "serif", "mono" |
"sans" |
fontSize |
string | "small", "medium", "large" |
"medium" |
colorScheme |
string | "light", "dark", "auto" |
"light" |
| μ΅μ | νμ | μ€λͺ | κΈ°λ³Έκ° |
|---|---|---|---|
autoCollapseToolCalls |
boolean | μλ£ ν λꡬ νΈμΆ μλ μ κΈ° | true |
chatWidth |
string | "default" (768px) λλ "wide" (1280px) |
"default" |
| μ΅μ | νμ | μ€λͺ | κΈ°λ³Έκ° |
|---|---|---|---|
artifactViewer |
boolean | Artifact λ·°μ΄ νμ±ν | true |
fileUploads |
boolean | νμΌ μ λ‘λ νμ±ν | true |
imagePreview |
boolean | μ΄λ―Έμ§ 미리보기 νμ±ν | true |
pdfPreview |
boolean | PDF 미리보기 νμ±ν | true |
λν μμ μμ μ§λ¬Έ λͺ©λ‘μ μ€μ ν©λλ€. λλ© νμ΄μ§μ λ²νΌ ννλ‘ νμλ©λλ€.
| μ΅μ | νμ | μ€λͺ |
|---|---|---|
chatOpeners |
string[] | μμ μ§λ¬Έ λͺ©λ‘ (μ΅λ 4κ° κΆμ₯) |
μμ:
chatOpeners:
- "μ€λμ λ μ¨λ μ΄λ?"
- "AI λ΄μ€ κ²μν΄μ€. νλ‘ μ 리ν΄μ€."
- "LLM λμ μλ¦¬κ° κΆκΈν΄."
- "μμ΄μ νΈκ° λμΌ?"# Application branding
branding:
appName: "ν
λλ
ΈνΈ μ±"
logoPath: "/logo.png"
logoWidth: 32
logoHeight: 32
# Chat interface buttons
buttons:
enableFileUpload: true
fileUploadText: "PDF λλ μ΄λ―Έμ§ μ
λ‘λ"
submitButtonText: "μ μ‘"
cancelButtonText: "μ·¨μ"
# Tool display settings
tools:
showToolCalls: true
displayMode: "detailed"
enabledTools: []
disabledTools: []
# Message display settings
messages:
maxWidth: 768
enableMarkdown: true
enableMath: true
enableCodeHighlight: true
enableTables: true
# Thread/Conversation settings
threads:
showHistory: false
enableDeletion: true
enableTitleEdit: true
autoGenerateTitles: true
# UI Theme settings
theme:
fontFamily: "sans"
fontSize: "medium"
colorScheme: "light"
# UI Behavior settings
ui:
autoCollapseToolCalls: true
chatWidth: "default"
# Feature flags
features:
artifactViewer: true
fileUploads: true
imagePreview: true
pdfPreview: truechatOpeners:
- "μ€λμ λ μ¨λ μ΄λ?"
- "AI λ΄μ€ κ²μν΄μ€. νλ‘ μ 리ν΄μ€."
- "LLM λμ μλ¦¬κ° κΆκΈν΄."
- "μμ΄μ νΈκ° λμΌ?"
- "λ
ΈνΈλ₯Ό μ°Ύμμ€."
- "LangGraphμ λν΄ μλ €μ€."μ 체 μ¬μ©μ κ°μ΄λλ public/full-description.mdμ μμΉν©λλ€. μ΄ λ§ν¬λ€μ΄ νμΌμ μ¬μ©μκ° λλ© νμ΄μ§μμ "μμΈν μ€λͺ
보기" λ²νΌμ ν΄λ¦ν λ νμλ©λλ€.
public/full-description.mdνμΌμ νΈμ§ν©λλ€- νμ€ λ§ν¬λ€μ΄ λ¬Έλ²μ μ¬μ©ν©λλ€
- νμΌμ μ μ₯νλ©΄ λ³κ²½μ¬νμ΄ μ¦μ λ°μλ©λλ€ (μ¬λΉλ λΆνμ)
- μμνκΈ°: κΈ°λ³Έ μ¬μ© λ°©λ²
- μ£Όμ κΈ°λ₯: μμΈν κΈ°λ₯ μ€λͺ
- μ€μ : μ΅μ’ μ¬μ©μλ₯Ό μν μ€μ μ΅μ
- νκ³Ό μλ Ή: κ³ κΈ μ¬μ© ν¨ν΄
- λ¬Έμ ν΄κ²°: μΌλ°μ μΈ λ¬Έμ λ° ν΄κ²° λ°©λ²
μ ν리μΌμ΄μ μ λ€μ νμΌ νμμ μ§μν©λλ€:
- μ΄λ―Έμ§: JPEG, PNG, GIF, WebP
- λ¬Έμ: PDF
νμΌ μ λ‘λ λ°©λ²:
- μ±ν μ λ ₯μ°½μ ν΄λ¦½ μμ΄μ½ ν΄λ¦
- νμΌμ μ±ν μΈν°νμ΄μ€λ‘ λλκ·Έ μ€ λλ‘
- ν΄λ¦½λ³΄λμμ μ΄λ―Έμ§ λΆμ¬λ£κΈ°
# Public λ³μ (ν΄λΌμ΄μΈνΈμ λ
ΈμΆ)
NEXT_PUBLIC_ASSISTANT_ID=agent
NEXT_PUBLIC_API_URL=https://your-site.com/api
# Private λ³μ (μλ² μΈ‘ μ μ©)
LANGGRAPH_API_URL=https://your-agent.langgraph.app
LANGSMITH_API_KEY=lsv2_...μ ν리μΌμ΄μ μ λ€μ νλ«νΌμ λ°°ν¬ν μ μμ΅λλ€:
- Vercel: Next.js μ ν리μΌμ΄μ μ κΆμ₯
- Netlify: Next.js κΈ°λ₯ μλ²½ μ§μ
- Docker: 컨ν μ΄λνλ λ°°ν¬ μ΅μ
- μ ν΅μ μΈ νΈμ€ν : λͺ¨λ Node.js νΈμ€ν μ 곡μ 체
npm run buildμ΄ λͺ
λ Ήμ .next λλ ν 리μ μ΅μ νλ νλ‘λμ
λΉλλ₯Ό μμ±ν©λλ€.
- νλ μμν¬: Next.js 15.x (React 19)
- μ€νμΌλ§: Tailwind CSS 4.x
- μν κ΄λ¦¬: React Hooks + nuqs (URL μν)
- UI μ»΄ν¬λνΈ: Radix UI primitives
- λ§ν¬λ€μ΄: react-markdown (remark/rehype νλ¬κ·ΈμΈ)
- μ½λ νμ΄λΌμ΄ν : react-syntax-highlighter
- μμ λ λλ§: KaTeX
- API ν΅ν©: LangGraph SDK
@langchain/langgraph-sdk: LangGraph ν΄λΌμ΄μΈνΈ λΌμ΄λΈλ¬λ¦¬framer-motion: μ λλ©μ΄μ λΌμ΄λΈλ¬λ¦¬next-themes: ν λ§ κ΄λ¦¬sonner: ν μ€νΈ μλ¦Όjs-yaml: YAML μ€μ νμ±
μ±ν μΈν°νμ΄μ€λ μ¬μ΄λ ν¨λμμ artifact (μ½λ, λ¬Έμ, μκ°ν)λ₯Ό λ λλ§ν μ μμ΅λλ€. Artifactλ LangGraph μλ² μλ΅ λ©νλ°μ΄ν°λ₯Ό ν΅ν΄ κ΄λ¦¬λ©λλ€.
μ¬μ©μλ μ±ν μ λ ₯μ°½μ λ μΉ μμ΄μ½μ μ¬μ©νμ¬ λꡬ νΈμΆμ κ°μμ±μ μ νν μ μμ΅λλ€. μ¨κΉ μνμμλ μ΅μ’ μλ΅λ§ νμλμ΄ κΉλν μΈν°νμ΄μ€λ₯Ό μ 곡ν©λλ€.
μ€μ μμ autoCollapseToolCallsκ° νμ±νλλ©΄ AI μλ΅μ΄ μλ£λ ν λꡬ νΈμΆ μΈλΆμ¬νμ΄ μλμΌλ‘ μ ν λν κΈ°λ‘μ κΉλνκ² μ μ§ν©λλ€.
- λνλ μμ±λ μ λͺ©κ³Ό ν¨κ» μλμΌλ‘ μ μ₯λ©λλ€
- μ¬μ©μλ λν μ΄λ¦μ λ³κ²½νκ±°λ μμ ν μ μμ΅λλ€
- μ¬μ΄λλ°μμ λν κΈ°λ‘μ λΉ λ₯΄κ² μ κ·Όν μ μμ΅λλ€
- μ€λ λ μνλ λΈλΌμ°μ μΈμ κ°μ μ μ§λ©λλ€
λ¬Έμ : νλ‘ νΈμλ μ ν리μΌμ΄μ
μ΄ μμλμ§ μμ
ν΄κ²°: Node.js λ²μ (18+)μ νμΈνκ³ npm installμ λ€μ μ€ννμΈμ
λ¬Έμ : LangGraph λ°±μλ μλ²μ μ°κ²°ν μ μμ ν΄κ²°:
NEXT_PUBLIC_API_URLνκ²½ λ³μκ°http://localhost:2024λ‘ μ€μ λμ΄ μλμ§ νμΈνμΈμ- λ°±μλ μλ²κ° μ€ν μ€μΈμ§ νμΈνμΈμ (
cd react-agent && uv run langgraph dev) - λ°±μλ μλ² ν°λ―Έλμμ μλ¬ λ©μμ§λ₯Ό νμΈνμΈμ
λ¬Έμ : λ°±μλ μλ²κ° μ€νλμ§ μμ ν΄κ²°:
- Python λ²μ (3.11+)κ³Ό uvκ° μ€μΉλμ΄ μλμ§ νμΈνμΈμ
react-agent/.envνμΌμ νμν API ν€κ° μ€μ λμ΄ μλμ§ νμΈνμΈμuv syncλͺ λ Ήμ΄λ‘ μμ‘΄μ±μ λ€μ μ€μΉνμΈμ
λ¬Έμ : νμΌ μ λ‘λκ° μλνμ§ μμ ν΄κ²°:
public/chat-config.yamlμμbuttons.enableFileUpload: trueλλfeatures.fileUploads: trueλ‘ μ€μ λμ΄ μλμ§ νμΈνμΈμ- μ€μ λ³κ²½ ν λΈλΌμ°μ λ₯Ό κ°λ ₯ μλ‘κ³ μΉ¨νμΈμ
λ¬Έμ : λν μμ μμκ° νμλμ§ μμ ν΄κ²°:
public/chat-openers.yamlνμΌμ΄ μ‘΄μ¬νλμ§ νμΈνμΈμchatOpenersλ°°μ΄μ μ΅μ 1κ° μ΄μμ μ§λ¬Έμ΄ μλμ§ νμΈνμΈμ- λΈλΌμ°μ λ₯Ό κ°λ ₯ μλ‘κ³ μΉ¨νμΈμ
λ¬Έμ : μ€μ λ³κ²½μ¬νμ΄ λ°μλμ§ μμ ν΄κ²°:
public/chat-config.yamlλλpublic/chat-openers.yamlνμΌμ μμ ν ν λΈλΌμ°μ λ₯Ό κ°λ ₯ μλ‘κ³ μΉ¨(Ctrl+Shift+R / Cmd+Shift+R)νμ¬ μΊμλ₯Ό μ§μ°μΈμ- κ°λ° μλ²λ₯Ό μ¬μμν΄λ³΄μΈμ:
npm run dev
MIT License
Copyright (c) 2025 TeddyNote
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
- YouTube μ±λ: ν λλ ΈνΈ
- LangChain λ¬Έμ: LangChain Documentation
- Next.js λ¬Έμ: nextjs.org/docs
