아키텍처 개요
AISCouncil는 브라우저에서 완전히 실행되는 제로 호스팅 봇 관리 플랫폼입니다. 프로덕션 출력은 80개의 모듈식 소스 파일로 조립된 단일 index.html 파일(~980 KB)입니다. 외부 런타임 의존성이 없습니다. JavaScript와 WASM만 브라우저로 전송됩니다.
단일 파일 아키텍처
앱은 단일 자체 포함 HTML 파일입니다. 개발 중에는 더 작은 컨텍스트 윈도우와 집중된 편집을 위해 src/ 파트로 분할됩니다. build.sh 스크립트가 엄격한 순서로 80개의 모든 파트를 다시 index.html로 연결합니다.
src/shell-head.html \
src/shell-style.html |
src/shell-body.html |
src/core-boot.js |
src/core-auth-main.js | build.sh
... | ---------> index.html (~980 KB)
src/settings-main.js |
src/miniprograms.js |
src/pwa.js |
src/shell-bottom.js /
index.html을 직접 편집하지 마세요. 항상 src/의 해당 파일을 편집한 다음 ./build.sh를 실행하여 재조립하세요.
전역 네임스페이스
모든 모듈은 window.AIS 전역 네임스페이스에 등록됩니다. 네임스페이스는 src/core-boot.js에서 초기화됩니다:
window.AIS = { version: "2.0.0", type: "aiscouncil" };
AIS.PLATFORM_VERSION = "1.0.0";
AIS.ABI_VERSION = 1;
교차 모듈 통신은 경량 이벤트 버스를 사용합니다:
AIS.on("event", handler); // 구독
AIS.off("event", handler); // 구독 취소
AIS.emit("event", data); // 발행
모듈 시스템: AIS.lazy()
모듈은 Qwik 스타일의 지연 하이드레이션 패턴을 사용합니다. 팩토리 함수는 AIS의 속성 게터를 통해 모듈에 처음 접근할 때까지 지연됩니다:
if (!AIS.Council)
AIS.lazy("Council", function () {
"use strict";
// 모듈 코드
return { run, renderCouncilMessage, estimateCost };
});
첫 번째 접근 시(예: AIS.Council.run()) 게터가 발생하고, 팩토리를 실행하고, 자신을 반환된 모듈 객체로 교체하고, 반환합니다. 후속 접근은 오버헤드 없이 일반 값을 사용합니다.
속성이 configurable: true로 정의되어 있기 때문에 WASM 커널 모듈은 첫 번째 접근 전에 AIS.Council = wasmModule을 설정하여 JS 모듈을 재정의할 수 있습니다.
모듈 카테고리
코어 모듈 (항상 로드됨)
코어 모듈은 단일 <script> 블록(core-boot.js부터 core-end.js까지) 내부에 정의됩니다. 페이지 로드 시 즉시 실행되며 앱의 기반을 형성합니다.
| 모듈 | 파일 | 용도 |
|---|---|---|
AIS.Auth | core-auth-main.js, core-auth-local.js, core-auth-init.js | WebAuthn/Passkey + OAuth 로그인, 세션 관리, 인증 쿠키 |
AIS.Billing | core-billing.js | 구독 등급, 평가판, 관리형 플랜 |
AIS.Codec | core-codec.js | Base80 인코딩, VLQ 버전 관리, deflate 압축 |
AIS.Storage | core-storage.js | IndexedDB + 선택적 SQLite WASM, 오프라인 우선 |
AIS.Providers | core-providers.js, core-providers-builtin.js | LLM 제공자 레지스트리, SSE 스트리밍 팩토리 |
AIS.UI | core-ui.js | DOM 유틸리티, 마크다운 렌더러, 토스트 알림 |
AIS.Session | core-session.js | 봇 세션 CRUD (IndexedDB 백업) |
AIS.Chat | core-chat.js | 채팅 기록, 스트리밍 메시지 송수신 |
AIS.Config | core-config.js | 봇 설정 패널 바인딩, URL 동기화 |
AIS.App | core-app-botlist.js, core-app-switch.js, core-app-events.js, core-app-search.js, core-app-init.js | 애플리케이션 컨트롤러, 초기화, 라우팅, 봇 관리 |
WASM 교체 가능 모듈 (지연 로드)
각 WASM 교체 가능 모듈은 자체 <script> 블록에 있으며 AIS.lazy() 패턴을 사용합니다. 처음 접근할 때만 실행됩니다.
| 모듈 | 파일 | 용도 |
|---|---|---|
AIS.Registry | registry.js | 커뮤니티 모델 레지스트리, 24시간 캐시, GitHub 폴백 |
AIS.Grid | grid.js | 모델 테이블/카드 렌더러 (TypeScript에서 컴파일) |
AIS.Council | council.js | 다중 모델 심의 엔진 (7가지 스타일) |
AIS.Wizard | wizard.js | 듀얼 모드 첫 실행 설정 마법사 |
AIS.Vision | vision.js | 비전 모델용 이미지 입력 (붙여넣기/업로드) |
AIS.Memory | memory.js | 봇별 영구 키-값 메모리 |
AIS.ImageGen | imagegen.js | 이미지 생성 (DALL-E, Grok Imagine, OpenRouter) |
AIS.Tools | tools.js | 도구/함수 호출 형식 정규화 |
AIS.Reminders | reminders.js | /remind 명령을 통한 예약 메시지 |
AIS.Themes | themes.js | 시각적 테마 시스템 |
AIS.Templates | templates-registry.js | 시스템 프롬프트 템플릿, 환영 화면 |
AIS.ModelPicker | model-picker.js | 정렬 가능한 모델 브라우저 |
인프라 모듈 (지연 로드)
| 모듈 | 파일 | 용도 |
|---|---|---|
AIS.ModuleLoader | moduleloader.js | 핫스왑 모듈 라이프사이클, OPFS 캐시 |
AIS.Plugins | plugins.js | 플러그인 시스템: 매니페스트 검증, 훅 |
AIS.MCP | mcp.js | Model Context Protocol (도구 + 리소스) |
AIS.Channels | channels-core.js, channels-whatsapp.js, channels-adapters.js, channels-stubs.js | 채널 어댑터 (Telegram, Discord, Matrix, Slack, WhatsApp) |
AIS.Sandbox | sandbox.js | WASM 도구 샌드박스 (Pyodide, QuickJS, SQLite) |
AIS.Publish | publish.js | SEO + 정적 HTML 게시 |
AIS.Perf | perf.js | 성능 모니터링 |
AIS.P2P | p2p.js | WebRTC + CRDT를 통한 P2P 협업 |
플랫폼 모듈
| 모듈 | 파일 | 용도 |
|---|---|---|
AIS.Settings | settings-main.js | 전역 설정 대화상자 (모든 섹션) |
AIS.I18n | i18n.js | 국제화 |
AIS.MiniPrograms | miniprograms.js | 미니 프로그램 런타임 (샌드박스 iframe) |
AIS.Docs | docs.js | 인라인 문서 뷰어 |
AIS.Profiles | profiles.js | 다중 모델 프로필/카운실 템플릿 |
AIS.Cron | cron.js | 브라우저 기반 스케줄러 |
CSS 아키텍처
앱은 클래스 없는 CSS 접근 방식을 사용합니다. 스타일은 클래스 대신 시맨틱 HTML 요소와 ID를 대상으로 합니다.
| 선택자 타입 | 예시 | 사용 사례 |
|---|---|---|
| ID | #header, #sidebar-left, #config-body | 고유 레이아웃 컨테이너 |
| 시맨틱 요소 | #messages > article, article menu button | 메시지, 액션, 네비게이션 |
| 데이터 속성 | [data-from="user"], [data-variant="primary"] | 변형 (메시지 역할, 버튼 타입) |
| 상태 클래스 | .active, .collapsed, .mobile-open | JS 토글 상태 |
| 컴포넌트 클래스 | .council-member-row, .status-dot | 동적 JS 생성 컴포넌트 |
| 유틸리티 클래스 | .f1, .sc, .dim | 일회용 인라인 스타일 대체 |
사용되는 시맨틱 HTML 요소: <header>, <main>, <aside>, <section>, <footer>, <nav>, <article>, <menu>, <details>, <dialog>, <output>.
클래스는 세 가지 경우에만 사용됩니다: 상태 토글(.active, .collapsed), 동적 JS 컴포넌트(.council-member-row), 유틸리티 약어(.f1, .sc). 시맨틱 선택자나 데이터 속성으로 요소를 대상으로 할 수 있다면 클래스를 추가하는 대신 그것을 사용하세요.
저장소 아키텍처
저장소는 동기식과 비동기식 저장소로 분할됩니다:
| 저장소 | 키 | 이유 |
|---|---|---|
| localStorage (동기) | ais-theme, ais-apikey-*, ais-user, ais-ollama-endpoint | 부팅 시 동기 읽기 필요 |
| IndexedDB (비동기, 무제한) | ais-bots, ais-chat-*, ais-addon-manifests | 대용량 데이터, 5 MB 제한 없음 |
| SQLite WASM (선택, OPFS) | 바이너리 블롭 | 동시 접근을 위해 지연 로드 |
첫 번째 부팅 시 AIS.Storage.init()가 localStorage에서 IndexedDB로 데이터를 자동 마이그레이션합니다.
제공자 아키텍처
제공자는 AIS.Providers.register()를 통해 등록됩니다. 대부분의 제공자는 다음을 처리하는 공유 openaiCompatible() SSE 스트리밍 팩토리를 사용합니다:
- Server-Sent Events 파싱
- 토큰별 스트리밍 콜백
- AbortController 시그널 지원
- 응답 헤더에서 토큰 카운팅
6개의 내장 제공자가 core-providers-builtin.js에 등록되어 있습니다:
| 제공자 | API 스타일 | 인증 | 참고 |
|---|---|---|---|
| Anthropic | 네이티브 (Messages API) | x-api-key 헤더 | 커스텀 스트리밍 형식 |
| OpenAI | OpenAI 호환 | Bearer 토큰 | 표준 SSE |
| xAI | OpenAI 호환 | Bearer 토큰 | Grok 모델 |
| Google Gemini | 네이티브 (Gemini API) | ?key= 쿼리 파라미터 | CORS 프리플라이트 회피 |
| OpenRouter | OpenAI 호환 | Bearer 토큰 | 300+ 모델, 무료 등급 |
| Ollama | OpenAI 호환 | 없음 | 로컬 LLM, 모델 자동 감지 |
모든 API 키는 로컬에 저장됩니다(localStorage['ais-apikey-{provider}']). 키는 브라우저에서 제공자로 직접 전송됩니다. 어떤 서버를 통해서도 프록시되지 않습니다.
설계 원칙
코드베이스는 모든 변경이 충족해야 하는 엄격한 설계 원칙을 따릅니다:
- HTML5 네이티브 우선 -- JS 등가물 이전에
<dialog>,<output>,hidden속성, CSS 애니메이션을 사용. - 제로 폴링 -- 애니메이션을 위한
requestAnimationFrame루프 없음,setInterval없음,setTimeout없음. 이벤트 기반만. - 수동 리스너 -- 모든 취소 불가능한 이벤트는
{ passive: true }사용. - 이벤트 위임 -- 컨테이너에 단일 리스너, 동적 목록의 항목별 리스너가 아님.
- 14px 최소 폰트 -- 모든 텍스트는 비전 LLM이 읽을 수 있어야 함. 14px 이하 텍스트 없음.
- VLM 친화적 레이아웃 -- 48px+ 클릭 타겟, 큰 토글, 고대비. 설정과 메뉴는 1920x1080에서 스크롤 없이 모든 기본 항목을 표시.
- 동작 감소 --
@media (prefers-reduced-motion: reduce)가 모든 애니메이션을 비활성화. - CSS 컨테인먼트 -- 사이드바에
contain: strict, 스크롤 가능 목록에content-visibility: auto.
기능을 추가하기 전에 네 가지 결정 게이트 질문을 하세요:
- 브라우저가 이것을 네이티브로 할 수 있는가?
- 이것에 서버가 필요한가? (그렇다면 선택적으로 만드세요.)
- 이것이 기기당 100만 개의 봇으로 확장되는가?
- 메모리와 CPU 비용은 무엇인가?
WASM 커널
선택적 WASM 커널(kernel/)은 Zig로 작성되었으며 ~5.5 KB로 컴파일됩니다. 모듈 시스템을 위한 저수준 프리미티브를 제공합니다: 슬롯 관리, 훅 디스패치, 링 버퍼 I/O, 쓰기 앞 로깅.
커널은 모듈 세그먼트, 링 버퍼, WAL, 스크래치 아레나를 위한 정의된 메모리 영역이 있는 64 MB SharedArrayBuffer에서 작동합니다. JS 모듈은 지연 로딩 계약을 깨지 않고 런타임에 WASM 등가물로 교체될 수 있습니다.
API 아키텍처
플랫폼은 다른 관심사에 대해 분할된 Worker를 사용합니다:
| Worker | 도메인 | 용도 |
|---|---|---|
aiscouncil-api | api.aiscouncil.net | 결제, 사용량, 키 발급, 지역 |
aiscouncil-auth | auth.aiscouncil.net | OAuth 콜백, 토큰 검증 |
API 기본 URL은 도메인 감지에서 부팅 시 설정되며 로컬 개발을 위해 localStorage['ais-api-base']를 통해 재정의할 수 있습니다.