프로그램명: Life Plan Scheduler AI
한 줄 소개: 설정 패널 없이 바로 쓰는 Azure OpenAI 기반 일정/메모/조사 AI 웹앱
- 메인 엔트리:
index.html - 스타일:
main.css - 프런트 로직:
main.js - 백엔드 프록시:
llm-proxy-server.js - 사용자별 데이터/설정 저장소:
db.js(SQLite, 파일:data.sqlite3) - 스모크 테스트:
scripts/smoke-test.mjs - 환경 변수 템플릿:
.env.example
참고:
app.js,style.css,simple.html,simple-enhanced.html은 과거 실험 파일이며 현재 실행 경로에서 사용하지 않습니다.- 현재 기본 실행 경로는
index.html + main.js + llm-proxy-server.js입니다. data.sqlite3는.gitignore에 포함되어 커밋되지 않습니다.
- Azure OpenAI 단일 경로만 사용, 설정 패널 없이 바로 사용
- 답변 형식(설명/계획/비교/요약)은 AI가 질문 의도를 보고 스스로 판단해서 결정 (하드코딩된 분기 없음)
- 인터넷 조사 결과는 참고 컨텍스트로만 제공하고, 최종 문장 생성은 항상 AI가 담당
- 새로고침 후에도 대화/추출 항목 유지 (
localStorage+ 서버 SQLite 동기화) - 사용자 이름으로 데이터 분리, 사용자별 모델/temperature/max_tokens 설정 가능
- rate limit(429) 등 장애 상황에서도 화면이 멈추지 않고 명확한 안내 제공
.env.example을 .env로 복사 후 값 입력:
AZURE_OPENAI_ENDPOINTAZURE_OPENAI_KEY- (선택)
AZURE_OPENAI_DEPLOYMENT(기본:gpt-4o-mini) - (선택)
AZURE_OPENAI_API_VERSION(기본:2024-02-15-preview) - (선택)
PROXY_MAX_RETRIES(기본:2, 5xx 네트워크 오류에만 적용) - (선택)
ALLOWED_ORIGIN(기본:http://localhost:8000, CORS 허용 출처) PORT(기본8787)
npm run start:proxynpm run start:webhttp://localhost:8000/index.html
npm run test:smoke- 프록시(8787)가 떠 있어야 합니다.
- 확인 항목: health, 날씨 조사 라우팅, 장소 조사 라우팅, 일반 조사, (Azure 설정 시) 챗 엔드포인트
- 사용자가 입력 후 전송
main.js가 프록시(8787)의/api/research로 인터넷 조사 자료를 먼저 수집 (날씨/장소/일반 검색 자동 분기)- 조사 자료를 컨텍스트로 붙여 Azure OpenAI에 채팅 요청
- AI가 질문 의도에 맞는 형식(설명/계획/비교/요약)을 스스로 선택해 Markdown으로 응답
- 응답을 채팅에 렌더링 (표/목록/강조 등 Markdown 서식 지원)
- 일정/메모 키워드나 날짜·시간 표현이 있으면 최근 항목으로 자동 추출 (최대 8개,
예상 일정메타 표시)
오류 시:
- Azure 미설정이면 폴백 안내 표시, 화면은 계속 사용 가능
- 전체 응답 대기시간에 상한(워치독)이 있어 무한정 "생각 중..."에 머무르지 않음
Rate limit(429) 대응:
- 프록시는 429를 즉시 반환합니다 (내부 블로킹 재시도 없음, 5xx만 짧게 재시도)
- 프런트는 서버가 알려준 실제 대기시간(
retryAfterMs)만큼 1회만 기다렸다가 재시도합니다 - 계속 반복되면 Azure 배포 쿼터(TPM/RPM) 상향이 필요하다는 안내를 표시합니다
-
/health에서providers.azure.configured가true - 전송 버튼 클릭 시 로딩 메시지(
생각 중...) 표시 - 응답 수신 후 로딩 메시지가 실제 답변으로 교체
- 새로고침 후 대화 기록과 최근 추출 항목 유지
- 최근 추출 항목 최대 8개 유지, 수정/삭제 동작 확인
-
초기화클릭 시 대화만 초기화되고 메모(최근 추출 항목)는 유지 -
npm run test:smoke전체 통과
npm run llm-proxy/npm run start:proxy: 프록시 서버 실행npm run start:web: 정적 웹 서버 실행 (포트 8000)npm run test:smoke: 조사 라우팅(날씨/장소/일반)과 챗 엔드포인트 자동 점검
증상: 응답이 계속 지연되거나 "Azure 요청 한도(rate limit) 초과" 메시지가 반복됨
원인 확인:
az cognitiveservices account deployment list --name <resource-name> --resource-group <rg-name> -o jsonrateLimits의request/token값이 낮으면 배포 용량(TPM)이 부족한 상태입니다.
해결(용량 상향 예시, 여유 쿼터가 있을 때):
az cognitiveservices account deployment create \
--name <resource-name> \
--resource-group <rg-name> \
--deployment-name <deployment-name> \
--model-name gpt-4.1-mini \
--model-version "2025-04-14" \
--model-format OpenAI \
--sku-name GlobalStandard \
--sku-capacity 10- 리전별 여유 쿼터는
az cognitiveservices usage list --location <region>으로 확인합니다.
- Azure OpenAI 단일 경로 구조로 정리, 설정 패널 제거
- 최근 추출 항목: 수정/삭제, 날짜·시간 파싱(
오늘/내일/모레+오전/오후 N시,HH:MM), 초기화 시 메모 유지 - 대화/항목 JSON 내보내기(
내보내기 JSON버튼) - 인터넷 조사 자동 라우팅 추가 (
/api/research): 날씨(Open-Meteo), 장소(OpenStreetMap), 일반 검색(Wikipedia/DuckDuckGo/HackerNews) - AI 응답을 하드코딩된 템플릿 분기 대신 모델이 직접 판단해 생성하도록 변경, Markdown 렌더링 적용
- Rate limit(429) 처리 개선: 프록시 즉시 반환 + 프런트 1회 실대기 재시도로 응답 지연 대폭 단축
- Azure 배포 용량 상향(요청 1/분 → 10/분, 토큰 1,000/분 → 10,000/분)
scripts/smoke-test.mjs추가로 핵심 경로 자동 검증- SQLite 기반 사용자별 데이터/설정 저장 추가 (
db.js,/api/users,/api/state,/api/settings)
- 상단 사용자 이름 입력 +
전환버튼: 이름으로 사용자를 구분하며, 전환 시 서버(SQLite)에 저장된 대화/항목을 불러옵니다. - 로그인/비밀번호는 없습니다. 같은 이름을 입력하면 같은 데이터를 이어서 사용합니다.
설정버튼: 사용자별model,temperature,max_tokens를 편집하고 저장합니다. 저장된 값은 다음 요청부터 바로 적용됩니다.- 대화/항목은
localStorage에 즉시 저장되고, 1.2초 디바운스 후 서버(SQLite)에도 동기화됩니다. 상단DB 동기화상태 표시로 확인 가능합니다. - 관련 API:
GET /api/users,POST /api/users(사용자 생성/조회)GET /api/state?user=NAME,POST /api/state(대화/항목/사용량 저장·조회)GET /api/settings?user=NAME,POST /api/settings(모델/온도/토큰 설정 저장·조회)
- 상단
🌙 다크모드/☀️ 화이트모드버튼으로 즉시 전환됩니다. - 브라우저에 저장(
localStorage: lps-theme)되어 새로고침·재방문 시에도 유지됩니다. - 저장된 값이 없으면 OS의 다크모드 설정(
prefers-color-scheme)을 따릅니다. - 서버 동기화 대상이 아닌 브라우저 로컬 개인 설정입니다(사용자별 DB 설정과는 별개).
- 글자 크기(작게/보통/크게), 채팅 밀도(여유롭게/촘촘하게):
설정패널에서 변경,localStorage에 저장되어 즉시 적용됩니다. - 사용자별 모델/temperature/max_tokens: 기존과 동일하게 서버(SQLite)에 저장됩니다.
- 로그인 / 회원가입 (권장): 상단 아이디/비밀번호 입력 후
회원가입또는로그인버튼으로 정식 계정을 사용할 수 있습니다.- 비밀번호는
scrypt+ salt로 해시 저장됩니다(평문 저장 없음). - 로그인 성공 시 서버가 세션 토큰을 발급하며, 브라우저에는
sessionStorage에만 저장됩니다(탭 닫으면 만료, 서버 기준 7일 유효). - 이후 해당 계정 이름으로
/api/state,/api/settings접근 시Authorization: Bearer <token>세션이 필요하며, 다른 사람의 유효한 세션 토큰으로도 남의 데이터에는 접근할 수 없습니다 (토큰의 소유자와 요청 대상 사용자가 반드시 일치해야 함). 로그아웃시 서버에서 세션 토큰을 즉시 폐기합니다.- 관련 API:
POST /api/auth/register,POST /api/auth/login,POST /api/auth/logout
- 비밀번호는
- PIN 기반 데이터 보호(구버전 호환): 비밀번호 계정을 만들지 않은 사용자 이름은 기존처럼
설정패널의 PIN으로 보호할 수 있습니다.- PIN도
scrypt로 해시 저장되며, 브라우저에는sessionStorage에만 보관됩니다. - PIN도 비밀번호도 설정하지 않은 이름은 기존처럼 개방형(닉네임 게시판 방식)으로 동작합니다.
- PIN도
- XSS 방어: AI 응답(Markdown, 인터넷 조사 결과 포함)은
DOMPurify로 살균 후 렌더링하며,http/https/mailto외 링크 스킴(javascript:등)은 제거됩니다. - 요청 제한(Rate Limit):
/api/llm/chat(20회/분),/api/research(30회/분),/api/state·/api/settings(60회/분),/api/auth/*(10회/분, 무차별 대입 방지)로 IP당 요청 수를 제한합니다. - CORS 제한: 기본적으로
http://localhost:8000출처만 허용합니다. 다른 호스트에서 서비스할 경우ALLOWED_ORIGIN환경 변수로 변경하세요. - 입력 크기 제한: 조사 질의는 300자, 채팅 메시지 배열은 최근 40개로 제한합니다.
- 참고: 로컬 개발 환경 기준이며, 실제 배포 시에는 반드시 HTTPS를 적용하세요(세션 토큰과 비밀번호가 평문 HTTP로 전송되지 않도록).