πŸ€– Computer Science

Generative UI & AI

date
Jun 20, 2024
slug
generative-ui
author
status
Public
tags
Next.js
GPT
summary
type
Post
thumbnail
DALLΒ·E 2024-06-25 13.46.34 - A clean and spacious blog thumbnail illustrating the concept of 'Generative UI'. Show a futuristic interface being dynamically generated by AI in real.webp
category
πŸ€– Computer Science
updatedAt
Jun 25, 2024 05:58 AM

1. Generative UIλž€?

πŸ’‘
인곡지λŠ₯이 μ‹€μ‹œκ°„μœΌλ‘œ μ‚¬μš©μž μš”κ΅¬μ™€ λ§₯락에 맞게 λ™μ μœΌλ‘œ μƒμ„±ν•˜λŠ” μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€
notion image
Generative UI vs. AI-Assisted Design
νŠΉμ§•
Generative UI
AI-Assisted Design
λͺ©μ 
μ‚¬μš©μž λ§žμΆ€ν˜• μΈν„°νŽ˜μ΄μŠ€ 제곡
λ””μžμΈ 및 κ΅¬ν˜„ κ³Όμ • 가속화
λŒ€μƒ
μ΅œμ’… μ‚¬μš©μž
λ””μžμ΄λ„ˆμ™€ μ œν’ˆ νŒ€
영ν–₯
κ°œμΈν™”λœ μΈν„°νŽ˜μ΄μŠ€ 제곡
λ””μžμΈ ν”„λ‘œμ„ΈμŠ€ 가속화
Β 
Example
Scenario: μ‚¬μš©μžκ°€ ν•­κ³΅νŽΈμ„ μ˜ˆμ•½ν•  λ•Œ κ°œλ³„ μš”κ΅¬μ— 맞좘 λ§žμΆ€ν˜• μΈν„°νŽ˜μ΄μŠ€ 제곡
  • κ°œμΈν™” μš”μ†Œ: 읽기 μ‰¬μš΄ 폰트 및 색상 λŒ€λΉ„, μ„ ν˜Έν•˜λŠ” μ’Œμ„ μœ ν˜•, λΉ„ν–‰ μ˜΅μ…˜ μš°μ„ μˆœμœ„ (λΉ„μš©, μ—¬ν–‰ μ‹œκ°„ λ“±)
Β 
νŒ¨λŸ¬λ‹€μž„ μ „ν™˜: κ²°κ³Ό 지ν–₯적 λ””μžμΈ
  • μ‚¬μš©μž λͺ©ν‘œμ™€ μ΅œμ’… 결과에 μ΄ˆμ μ„ 맞좘 κ²½ν—˜ λ””μžμΈ.
    • μΈν„°νŽ˜μ΄μŠ€ 섀계보닀 μ‚¬μš©μž λͺ©ν‘œ 달성에 쀑점.
    • λ””μžμ΄λ„ˆλŠ” κ°œλ³„ μ‚¬μš©μž μš”κ΅¬μ— 맞좘 μΈν„°νŽ˜μ΄μŠ€ 생성.
  • λ””μžμ΄λ„ˆλŠ” μ‚¬μš©μžμ˜ λͺ©ν‘œμ™€ λΉ„μ¦ˆλ‹ˆμŠ€ λͺ©ν‘œλ₯Ό μ΄ν•΄ν•˜κ³ , 이λ₯Ό AI μ‹œμŠ€ν…œμ— λ°˜μ˜ν•˜μ—¬ μΈν„°νŽ˜μ΄μŠ€λ₯Ό 섀계함
notion image

2. κ΅¬ν˜„ν•΄λ³΄κΈ° with Vercel AI SDK

Vercel AI SDKλž€?
AI μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ„ νƒ€μž…μŠ€ν¬λ¦½νŠΈ 기반 ν”„λ‘œμ νŠΈμ—μ„œ κ°•λ ₯ν•˜κ²Œ λΉŒλ“œν•  수 μžˆλ„λ‘ λ§Œλ“  νˆ΄ν‚·. LLM λͺ¨λΈκ³Ό ν΄λΌμ΄μ–ΈνŠΈ μ‚¬μ΄μ˜ 데이터 흐름을 μ‰½κ²Œ μ œμ–΄ν•  수 μžˆλ‹€. μ—¬λŸ¬ LLM λͺ¨λΈμ˜ API params λ₯Ό ν•˜λ‚˜λ‘œ ν†΅ν•©ν•˜μ—¬ ν‘œμ€€ν™”ν•˜μ—¬ λΉ λ₯΄κ²Œ κ΅¬ν˜„ν•  수 μžˆλ‹€λŠ” μž₯점이 μžˆλ‹€.
  • AI SDK Core, SDK UI, SDK RSC
notion image
import { generateText } from "ai" import { openai } from "@ai-sdk/openai" const { text } = await generateText({ model: openai("gpt-4-turbo"), // or anthropic("claude-3-opus-20240229"), etc. prompt: "What is love?" })
Tool μ΄λž€?
  • LLM λͺ¨λΈμ€ λ†€λΌμš΄ 생성 κΈ°λŠ₯을 κ°€μ§€μ§€λ§Œ, μ—¬λŸ¬ 단계λ₯Ό 거쳐야 ν•˜λŠ” μž‘μ—…μ΄λ‚˜ μ™ΈλΆ€ μ„Έκ³„μ™€μ˜ μƒν˜Έ μž‘μš©μ΄ 어렀움. β†’ μ‚¬μš©μžκ°€ λ§Œλ“  도ꡬ듀을 LLMμ—κ²Œ μ œμ‹œν•˜κ³ , LLM은 μ–΄λ–€ 도ꡬλ₯Ό 선택할지 κ²°μ •ν•œ ν›„, λ„κ΅¬μ˜ input λ§Œμ„ 결정함
import { z } from 'zod'; import { generateText, tool } from 'ai'; const result = await generateText({ model: yourModel, tools: { // 날씨 κ²°μ • 도ꡬ weather: tool({ description: 'Get the weather in a location', parameters: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ( const { data } = axios.get(`/weather?location=${location}`); return { data, location }; ), }), recommendedPlace: (...), }, toolChoice: 'required', // λͺ¨λΈμ΄ λ°˜λ“œμ‹œ 도ꡬλ₯Ό μ„ νƒν•˜λ„λ‘ 지정 prompt: 'What is the weather in San Francisco and what attractions should I visit?', });
notion image
κ΅¬ν˜„ μ‹œμž‘: 폴더 ꡬ쑰 및 데이터 흐름
Scenario: 메뉴λ₯Ό νƒμƒ‰ν•˜μ§€ μ•Šκ³  λΉ λ₯΄κ²Œ λ™μž‘μ„ μˆ˜ν–‰ν•  수 μžˆλŠ” 신사업 봇을 λ§Œλ“€μž! (μ†‘κΈˆ, 결제 λ‚΄μ—­ 확인 λ“±)
|-- playground // λΌμš°νŒ… 경둜 | |-- _action // Server function | | |-- index.tsx | |-- _component | | |-- chat // Generative UI 폴더 | | | |-- UserMessage.tsx | | | |-- BotMessage.tsx | | | |-- TranslateMessage.tsx | | | |-- (...) | | |-- SectionPlaygroundAI.tsx // UI State Render | | |-- (...) | |-- layout.tsx | |-- page.tsx // AI Provider 감싸기
AI State vs. UI State
notion image
UI State: ν΄λΌμ΄μ–ΈνŠΈ μ»΄ν¬λ„ŒνŠΈμ— λ Œλ”λ§λœ λΆ€λΆ„ 전체λ₯Ό μƒνƒœ κ΄€λ¦¬ν•œλ‹€.
{ id: number, display: ReactNode }
AI State: Server functionμ—μ„œ ν˜•μ‹μ— λ§žμΆ”μ–΄ LLM λͺ¨λΈμ΄ 이전 νžˆμŠ€ν† λ¦¬λ₯Ό 이해할 수 μžˆλ„λ‘ μƒνƒœ κ΄€λ¦¬ν•œλ‹€.
{ id: string, role: LLMRole, content: string | Array<ToolCallPart> | (...) }
Action: 두 Stateλ₯Ό λ³€ν™”μ‹œν‚¬ Server function을 λ“±λ‘ν•œλ‹€.
// action.tsx export const AI = createAI({ actions: { submitUserMessage, }, initialUIState, initialAIState, }); // page.tsx <main className="p-10 flex flex-col min-h-screen z-10 relative"> <h1 className="text-3xl z-10">Playground</h1> // provider 둜 감싸기 <AI> <SectionPlaygroundAI /> </AI> </div> </main>
Client: UI State λ Œλ”λ§ν•˜κΈ° / μƒˆλ‘œμš΄ input λ“±λ‘ν•˜κΈ°
'use client'; const SectionPlaygroundAI: React.FC = () => { const [messages, setMessages] = useUIState<typeof AI>(); const { submitUserMessage } = useActions<typeof AI>(); const [form] = Form.useForm(); const handleFinish = async ({ currentInput }: { currentInput: string }) => { // UI μƒνƒœ μ—…λ°μ΄νŠΈ setMessages((currentMessages) => [ ...currentMessages, { id: Date.now(), type: 'user', display: <UserMessage>{currentInput}</UserMessage> }, ]); try { // Action 호좜 const responseMessage = await submitUserMessage(currentInput); // responseλ₯Ό UI μƒνƒœμ— μ—…λ°μ΄νŠΈ setMessages((currentMessages) => [...currentMessages, responseMessage]); form.resetFields(); } catch (e) { message.error('AI μ„œλ²„μ™€μ˜ 톡신이 μ›ν™œν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.'); } }; return ( <section className="rounded-2xl flex flex-col flex-wrap p-6 gap-5 border border-solid border-indigo-500 relative box-content"> <ul className="flex flex-col gap-4 max-h-[calc(100vh-220px)] mb-16 overflow-y-auto whitespace-pre-line leading-snug"> {(...) // UI 메세지 리슀트 λ Œλ”} </ul> <Form layout="inline" onFinish={handleFinish}> {(...) // 인풋 μž…λ ₯ 폼} </Form> </article> </section> ); }; export default SectionPlaygroundAI;
Server: AI State κ΄€λ¦¬ν•˜κΈ° / Action μ •μ˜ν•˜κΈ°
notion image
const submitUserMessage = async (content: string) => { 'use server'; const aiState = getMutableAIState<typeof AI>(); aiState.update(...); // μœ μ € input νžˆμŠ€ν† λ¦¬μ— μΆ”κ°€ // streamable UI μ •μ˜ const reply = createStreamableUI( <BotMessage tool="Loading..." noBg> <Skeleton className="w-2/3 mt-2" active /> </BotMessage>); const defaultParameters = z.object({ isSelect: z.boolean(), reason: z.string().describe('The reason why you choose this tool. Answer In Korean.'), }); const { toolResults, toolClls, text } = await generateText({ model: openai('gpt-4o'), messages: aiState.get().map((info: any) => ({ role: info.role, content: info.content, name: info.name, })), tools: { // streaming tool translateToKorean: { description: 'If user want to translate the input that from another language to Korean.', parameters: defaultParameters, // function name return execute: async (params) => params, }, // generating tool send: { description: 'send money to another company/user', parameters: z.object({ amount: z.number().optional(), receiverName: z.string().describe('name of the receiver').optional(), reason: z.string().describe('The reason why you choose this tool. Answer In Korean.'), }), execute: async ({ amount, receiverName, reason }) => { let label = `${receiverName}λ‹˜κ»˜ \n ${amount?.toLocaleString()}원을 λ³΄λ‚Όκ²Œμš”.`; if (!receiverName?.length) label = `${amount?.toLocaleString()}원을 \n μ–΄λŠ λΆ„κ»˜ μ†‘κΈˆν• κΉŒμš”?`; if (!amount) label = `${receiverName}λ‹˜κ»˜ \n μ–Όλ§ˆλ₯Ό μ†‘κΈˆν• κΉŒμš”?`; reply.update( <BotMessage tool="μ†‘κΈˆλ΄‡" reason={reason}> <SendMoneyMessage label={label} receiverName={receiverName} amount={amount} /> </BotMessage> ); reply.done(); return { reason }; }, }, }, }); const generativeUIActions: Record<any, (args: any) => Promise<any>> = { translateToKorean: async (content: string) => { const { partialObjectStream } = await streamObject({ model: openai('gpt-4-turbo'), system: 'You need to translate the input to Korean. Please also provide important word list.', prompt: content, schema: z.object({ originText: z.string().describe('The original language text.'), sentence: z.string().describe('KOREAN translation of the input.'), wordList: z.array( z.object({ word: z.string(), mean: z.string(), }) ), }), }); // 객체 슀트리밍 둜직 for await (const partialObject of partialObjectStream) { reply.update( <BotMessage tool="λ²ˆμ—­λ΄‡" reason={toolResults?.[0]?.result?.reason}> <TranslateMessage originText={partialObject?.originText} sentence={partialObject?.sentence} wordList={partialObject?.wordList ?? []} /> </BotMessage> ); } reply.done(); }, }; // tool이 μ„ νƒλ˜μ§€ μ•Šμ€ 경우 if (toolResults.length === 0) { // μ–΄μ‹œμŠ€ν„΄νŠΈ 응닡 νžˆμŠ€ν† λ¦¬ μΆ”κ°€ aiState.done([...aiState.get(), { role: 'assistant', content: text }]); reply.update(<BotMessage>{text}</BotMessage>); reply.done(); } else { if (['translateToKorean', (...)].includes(toolResults?.[0]?.toolName)) { generativeUIActions[toolResults?.[0]?.toolName]?.(content); } aiState.done(...); // function calling에 λŒ€ν•œ νžˆμŠ€ν† λ¦¬ μΆ”κ°€ } // ν΄λΌμ΄μ–ΈνŠΈ UI 리턴 return { id: Date.now(), display: reply.value, }; };
Result
Video preview
Β 

3. Reference

Β