Meerkit
Meerkit
HomeDocsComponentsAPI
GitHub

Getting Started

5
IntroductionInstallationProject StructureThemingDeployment

CLI

6
CLI Overviewinit & setupstart, stop & openstatus, check, env & logsdbadd, test & update

Core Concepts

5
AuthenticationRoles & PermissionsData ModelLogin LockoutSecurity

Guides

3
Protect a pageAdd a roleCall your own API

Components

24

사용자 화면12

Login FormSignup FormEmail VerificationTerms AgreementGoogle LoginPassword ResetProfile & PasswordNicknameAuth DialogProfile ImageUser AvatarUser Menu

관리자 화면6

Member TableMember DirectoryMember AI UsageMember AI Usage HistoryRole ManagerRole Select

공통5

Address FieldFile DropzoneLoadingDateRoute Guards

AI Components

4
AI Prompt InputThinking IndicatorAPI Key InputVoice

API

6
OverviewHealthAuthUsers · 내 정보Users · 관리자LLM

Reference

2
Hooks & StateFAQ

API Key Input

사용자가 자기 AI API 키를 넣는 입력 칸과, 웰컴 페이지의 AI 테스트(LlmPlayground)입니다. 로그인 안내와 토큰 사용량 표시를 포함합니다.

사용자가 자기 AI API 키를 넣는 입력입니다. 키를 한 개만 받는 ApiKeyInput, 여러 공급자(GPT, Gemini, Claude)의 키를 받는 MultiApiKeyList(표)와 MultiApiKeyTabs(탭)가 있습니다. 모두 로그인 전에도 화면에 보이지만, 입력하려고 하면 "로그인이 필요해요" 안내가 먼저 뜨고 로그인한 뒤에만 키를 저장할 수 있습니다.

키를 저장하는 서비스라면 서버로 보내세요. 저장 버튼에 onSave를 연결하면 키는 저장할 때 서버로 가고, 화면에는 끝자리(keyHint)만 남습니다. 서버에서는 암호화해 저장하고, 응답에 원본 키를 돌려주지 마세요. 브라우저 쪽에는 localStorage, 쿠키 등 어디에도 키를 저장하지 않습니다.
컴포넌트언제 쓰나저장 방식
ApiKeyInput키를 한 개만 받을 때.onSave를 넘기면 서버로 저장. 넘기지 않으면 브라우저 메모리에만 두고 요청마다 헤더로 보냅니다(웰컴 페이지의 AI 테스트 방식).
MultiApiKeyList · MultiApiKeyTabs여러 공급자 중 서비스가 고른 것만 받을 때.항상 onSave로 서버에 저장합니다.

ApiKeyInput

로그인하지 않은 상태에서 칸을 눌러 보세요. 취소는 닫기, 확인은 로그인 창을 엽니다. 로그인한 뒤에는 키를 입력하고 저장할 수 있습니다.

키는 이 브라우저 메모리에만 두고 서버에 저장하지 않아요. 새로고침하면 지워져요.

tsx
<ApiKeyInput />

서버로 저장하는 방식

onSave를 넘기면 저장 버튼이 값을 서비스에 넘기고, 서비스가 서버로 보냅니다. 성공하면 입력한 키는 지워지고 끝자리만 보입니다.

tsx
<ApiKeyInput provider="claude" showModel onSave={save} />
서비스 코드
import { ApiKeyInput, type ApiKeyInputValue } from "@/meerkit";

async function save(v: ApiKeyInputValue) {
  // v = { provider, model, apiKey? } — apiKey는 이번에 새로 입력한 경우에만 있습니다.
  const res = await fetch("/api/ai-key", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(v) });
  if (!res.ok) throw new Error("저장하지 못했어요."); // 던진 메시지가 화면에 보입니다
}

<ApiKeyInput provider="claude" showModel initial={saved} onSave={save} />

메모리에만 두는 방식

onSave가 없으면 키는 브라우저 메모리에만 있고 새로고침이나 로그아웃하면 지워집니다. 요청할 때 X-LLM-API-Key 헤더로만 보내고 서버는 저장하거나 로그에 남기지 않습니다. 서버에 키를 두지 않는 가벼운 데모나 개인용 도구에 맞는 방식이고, 웰컴 페이지의 AI 테스트(LlmPlayground)가 이렇게 씁니다.

동작방법
로그인 전에 입력 시도칸을 누르면 로그인 안내 창이 뜹니다. 취소하면 닫히고, 확인하면 로그인 창이 열립니다.
키 저장로그인한 뒤 키를 입력하고 저장을 누릅니다. 공급자별 키 모양(OpenAI sk-, Gemini AIza, Claude sk-ant-)이 맞지 않으면 안내가 나옵니다.
키 보기·가리기칸 오른쪽의 눈 아이콘으로 입력한 글자를 확인합니다.
키 지우기메모리 방식: 저장하면 앞 3자리와 뒤 4자리만 보이고, 지우기를 누르면 사라집니다.
모델 고르기showModel을 켜면 모델 선택이 생깁니다.
메모리 방식에서 값 읽기
import { ApiKeyInput, useLlmKeyStore } from "@/meerkit";

<ApiKeyInput provider="claude" showModel />

const apiKey = useLlmKeyStore((s) => s.apiKey);
const model = useLlmKeyStore((s) => s.model);     // 비어 있으면 서비스의 기본 모델
PropType설명
classNamestring바깥 요소에 붙일 클래스.
provider"openai" | "gemini" | "claude"어느 공급자의 키를 받을지. 기본 openai. 안내 문구와 키 모양 검사가 공급자에 맞게 바뀝니다.
showModelboolean모델 고르는 칸을 보입니다. 기본 false.
models{ id, label }[]모델 목록을 바꿉니다. 기본은 공급자의 기본 목록(ai/providers.ts).
onSave(v: ApiKeyInputValue) => void | Promise<void>넘기면 서버로 저장하는 방식이 됩니다. 실패하면 throw 하세요.
initial{ keyHint?, model? }서버에 저장된 값(onSave와 함께). 원본 키는 받지 않고 끝자리만 받습니다.

MultiApiKeyList · MultiApiKeyTabs

서비스가 여러 공급자의 키를 받을 때 씁니다. 받을 공급자는 providers로 서비스가 고릅니다(예: GPT와 Claude만). 표 형태(MultiApiKeyList)와 탭 형태(MultiApiKeyTabs)는 값과 동작이 같고 모양만 다릅니다. 탭은 한 줄로 늘어서고 넘치면 옆으로 밀어 봅니다.

두 가지 선택은 뜻이 다릅니다

선택뜻몇 개까지
이 키로 사용지금 AI를 부를 때 쓸 키입니다. 사용자가 고릅니다.라디오라서 하나만 고를 수 있습니다. 키가 입력된 공급자만 고를 수 있습니다.
기본 키사용 중인 키가 한도 초과·만료·오류로 막혔을 때 대신 쓸 키입니다. "기본 키" 옆 (i)에 마우스를 올리면 설명이 나옵니다.하나만 지정합니다. 키가 입력된 공급자만 지정할 수 있습니다. 사용 중인 키를 기본 키로 지정하면 다른 키로 바뀌지 않습니다.
자동 전환은 호출이 실제로 실패했을 때 서버(또는 서비스 코드)가 합니다. 화면 컴포넌트는 실패를 알 수 없으니, 아래 failoverApiKeys로 다음 상태를 계산해 저장하세요. 자동 전환이 필요 없는 서비스는 autoFailover={false}로 "기본 키"를 화면에서 뺄 수 있습니다. 공급자를 하나만 넘겨도 대체할 키가 없으니 나타나지 않습니다.
이 키로 사용공급자API 키모델기본 키
GPT (OpenAI)
Claude (Anthropic)
tsx
<MultiApiKeyList providers={["openai", "claude"]} initial={saved} onSave={save} />
API 키
모델
이 키로 사용지금 AI를 부를 때 쓸 키예요. 하나만 고를 수 있어요.
기본 키
tsx
<MultiApiKeyTabs initial={saved} onSave={save} />
API 키
모델
이 키로 사용지금 AI를 부를 때 쓸 키예요. 하나만 고를 수 있어요.
tsx
<MultiApiKeyTabs providers={["openai", "claude"]} autoFailover={false} onSave={save} />
저장 연결 (서비스 코드)
import { MultiApiKeyTabs, type ApiKeySavePayload } from "@/meerkit";

async function save(payload: ApiKeySavePayload) {
  // payload = { activeProvider, defaultProvider, keys: [{ provider, model, apiKey? }] }
  // activeProvider = 이 키로 사용, defaultProvider = 기본 키(자동 전환을 안 쓰면 null)
  // apiKey는 이번에 새로 입력한 키만 있습니다. 없으면 서버에 저장된 키를 그대로 씁니다.
  const res = await fetch("/api/ai-keys", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
  if (!res.ok) throw new Error("저장하지 못했어요."); // 던진 메시지가 화면에 보입니다
}

<MultiApiKeyTabs providers={["openai", "gemini"]} initial={saved} onSave={save} />
키가 막혔을 때 (서버 또는 서비스 코드)
import { failoverApiKeys } from "@/meerkit";

// 사용 중이던 openai 키가 한도 초과(LLM_QUOTA_EXCEEDED)로 실패했다면
const { state, switchedTo } = failoverApiKeys(
  { activeProvider: "openai", defaultProvider: "claude", keys: [{ provider: "openai" }, { provider: "claude" }] },
  "openai",
);
// state.activeProvider === "claude" (기본 키가 사용 중인 키가 됩니다)
// switchedTo: "claude" (바꿀 키가 없으면 null)
// 사용 중인 키 자체가 기본 키로 지정돼 있으면 전환하지 않고 switchedTo는 null입니다.
PropType설명
onSave필수(payload) => void | Promise<void>저장 버튼을 누르면 호출됩니다. 실패하면 throw 하세요.
providersProviderId[]키를 받을 공급자. 기본은 openai, gemini, claude 모두.
modelsPartial<Record<ProviderId, { id, label }[]>>공급자별 모델 목록을 바꿉니다.
initial{ entries: { provider, model?, keyHint? }[]; activeProvider?; defaultProvider? }서버에 저장된 값. 수정 화면에서 넘기면 "저장됨 sk-…a1b2"로 보입니다.
autoFailoverboolean기본 키(자동 전환)를 쓸지. 기본 true. false면 기본 키 지정이 화면에서 빠집니다.
classNamestring바깥 요소에 붙일 클래스.

LlmPlayground

ApiKeyInput, AiPromptInput, ThinkingIndicator을 묶어 서버의 /api/llm/chat까지 이어 둔 AI 테스트입니다. 웰컴 페이지에 기본으로 들어 있습니다. 로그인 전에는 키 칸만 보이고, 로그인하면 질문 입력창이 열리며, 응답 카드 아래에 이번 응답의 토큰 수와 예상 비용, 이 탭에서의 누적 합계가 나옵니다.

meerkit.config.ts
ai: {
  playground: false, // 웰컴 페이지의 AI 테스트를 숨깁니다. 기본 true
},

사용량과 예상 비용

  • 토큰 수는 AI 응답이 돌려준 값(입력·출력·합계)을 그대로 보여 줍니다. 서버에 기록하지 않고, 이 탭의 메모리에서만 합산합니다.
  • 예상 비용은 ai/pricing.ts의 모델별 단가(USD, 토큰 100만 개당)로 계산하는 참고용 값입니다. 공급자 요금은 바뀔 수 있으니 모델을 늘리거나 단가가 달라지면 이 파일을 고치세요.
  • 사용 기록 저장, 이용 이력, 한도는 이 기능에 들어 있지 않습니다.

서버와 주고받는 것

구분내용
요청POST /api/llm/chat · 로그인 필요 · 헤더 X-LLM-API-Key · 본문 { message, model }
응답{ reply, model, usage: { input_tokens, output_tokens, total_tokens } }
허용 모델gpt-4o-mini, gpt-4o (목록에 없으면 422). 공급자는 현재 OpenAI만 열려 있습니다.

오류와 안내 문구

code상태뜻
LLM_KEY_REQUIRED400키 헤더가 없습니다.
LLM_KEY_INVALID401키가 올바르지 않거나 사용할 수 없습니다.
LLM_QUOTA_EXCEEDED429입력한 키의 크레딧·한도를 모두 썼습니다 (공급자 사이트에서 충전·확인).
LLM_RATE_LIMITED429짧은 시간에 요청이 많습니다. 잠시 뒤 다시 시도합니다.
LLM_INPUT_TOO_LONG413입력이 너무 깁니다.
LLM_TIMEOUT504응답이 늦어지고 있습니다.

백엔드에서 쓰기

backend
from app.llm import chat_once

reply = chat_once("안녕", api_key=사용자가_넣은_키, model_name="gpt-4o-mini", provider="openai")
reply.text           # 답
reply.usage.total_tokens  # 토큰 수

다른 공급자(Anthropic 등)를 열려면 langchain-anthropic을 추가하고 app/llm/client.py의 BYOK_PROVIDERS에 이름을 더합니다. 키와 질문·응답 내용은 로그에 남기지 않습니다.

shadcn/ui · Inputshadcn/ui · Dialog
Thinking IndicatorVoice
© Meerkit. All rights reserved.GitHub