메카넘휠 이동로봇 프로젝트(파이5+RPLidar, 추후 팔 추가)의 부품리스트/
단계별 작업/예산을 관리하는 웹앱. doctor-app의 GET/PUT 단일문서 패턴을
재사용하되 케이스 목록 없이 단일 프로젝트 문서로 단순화했고, 첫 로드시
project_robot_dog.md 메모 기준 실제 진행상황을 기본 시드로 반환한다.
- src/gateway/routes/routes-robot.ts: GET/PUT /api/robot/project,
사용자별 workspace/.smallclaw/robot-project.json에 원자적 저장
- web-ui/html/robot-app.html: 부품/작업/메모 3탭, 실시간 예산 요약,
디바운스 자동저장
- index.html 🏠 홈 드롭다운에 🤖 로봇 링크 추가
장비연동/전용챗봇/외부데이터연동/CAD플러그인은 다음 단계로 보류.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
지서버에 muse-glimmer와 gemma4:26b를 둘 다 두고 골라 쓰기 위해 되살린다.
86063d3에 있었으나 그 커밋이 통째로 되돌려졌던 UI 부분이다. 되돌린 사유였던
에러는 서버 로그상 8시간 앞선 별건(config.json 손상 + 지서버 엔드포인트 .99)으로
확인됐다.
설정에서 지서버 모델은 자유입력 텍스트 박스라 모델명을 손으로 정확히 적어야 했다.
정작 refreshProviderModels는 ollama_local일 때도 목록을 가져오고 있었는데, 그
결과를 항상 ollama 패널의 select에 써넣어서 — 지서버를 고르면 그 패널이 숨겨지므로 —
가져온 목록이 화면에 한 번도 안 보이고 버려지고 있었다.
- 지서버 모델 필드를 select로 바꾸고 "모델 새로고침" 버튼 추가
- refreshProviderModels가 provider별로 맞는 select와 상태줄을 쓰도록 수정
- lm_studio/llama_cpp의 모델 필드는 아직 input이라 SELECT일 때만 option을 채운다
(input에 <option>을 넣으면 사용자가 입력한 값이 날아간다)
onProviderChange는 ollama_local만 자동 새로고침에서 빼둔다 — 드롭다운을 고른 것만으로
WOL 패킷을 쏘고 45초를 기다리면 안 되기 때문이다. 그래서 새로고침 없이 저장하면
select가 placeholder(값 "")뿐이라 설정된 모델명이 빈 값으로 덮어써진다.
seedModelSelect로 저장된 모델을 항상 실제 option으로 심어 막는다(빈 select에
.value를 넣으면 조용히 무시된다는 점도 이유).
app.js 쪽 호출은 typeof 가드로 감쌌다. seedModelSelect는 settings-provider.js에
있으므로, 브라우저가 그 파일의 캐시본을 든 채 새 app.js만 받으면 ReferenceError로
설정 로딩이 통째로 죽는다 — 지난번 "에러가 너무 나네"의 가장 유력한 경로다.
검증(실제 index.html 패널 마크업 + 실제 함수를 그대로 불러오고 네트워크 경계만 스텁):
새로고침 전 저장값이 실제 option으로 심어지고 저장 payload가 그 값을 읽는지,
새로고침 후 저장 모델이 목록 맨 앞에 오는지, 다른 패널 select를 안 건드리는지,
사용자가 고른 값이 저장 payload에 실리는지 — 전부 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
지서버(ollama_local)에 muse-glimmer를 추가로 올려두고 gemma4:26b와 골라 쓰려 했으나
설정 화면에서 지서버 모델은 자유입력 텍스트 박스라 모델명을 손으로 정확히 적어야 했다.
정작 refreshProviderModels는 ollama_local일 때도 목록을 가져오고 있었는데, 그 결과를
항상 ollama 패널의 select에 써넣어서 — 지서버를 고르면 그 패널이 숨겨지므로 —
가져온 목록이 화면에 한 번도 안 보이고 버려지고 있었다.
- 지서버 모델 필드를 select로 바꾸고 "모델 새로고침" 버튼 추가
- refreshProviderModels가 provider별로 맞는 select와 상태줄을 쓰도록 수정
- lm_studio/llama_cpp의 모델 필드는 아직 input이라, SELECT일 때만 option을 채운다
(input에 <option>을 넣으면 사용자가 입력한 값이 날아간다)
여기서 새로 생기는 함정 하나를 같이 막았다. onProviderChange는 ollama_local만
자동 새로고침에서 빼두는데, 드롭다운을 고른 것만으로 WOL 패킷을 쏘고 45초를 기다리면
안 되기 때문이다. 그러면 새로고침 없이 저장할 때 select가 placeholder(값 "")뿐이라
설정된 모델명이 빈 값으로 덮어써진다. seedModelSelect로 저장된 모델을 항상 실제
option으로 심어둬서 막았다(빈 select에 .value를 넣으면 조용히 무시된다는 점도 이유).
buildThinkCandidates: 명시적 think:false는 재시도 사다리를 오르지 않는다.
사다리는 glm-5.1:cloud처럼 think를 생략하면 생각만 하고 content를 안 내는 모델을
건지려고 만든 것인데, false는 성격이 다르다. "이 모델은 생각에 예산을 낭비한다"는
운영자의 결정이라, 빈 응답 한 번에 thinking을 도로 켜면 그 결정이 조용히 뒤집힌다.
08-12 muse-glimmer 실측: 생각비중 85%, 체감 3.1 tok/s → think:false로 20.9 tok/s(7배)에
답변은 오히려 길어지고 잘림도 사라짐(20문항 빈 답변 0건). 사다리가 이걸 되돌리면
의도한 7배가 말없이 느린 경로로 돌아간다. 이제 false는 모델 기본값까지만 물러선다
— 빈 응답이 막다른 길이 되지는 않게.
테스트용으로 모듈 레벨 export로 추출(346개 통과).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vault에 저장된 키는 이름만 opaque 문자열로 남아 있고 어디서 발급받았는지가 UI
어디에도 없어, 갱신·재발급 때마다 해당 콘솔을 찾아 헤매야 했다. data.go.kr은
특히 하나의 키라도 서비스별로 활용신청이 따로 필요한데(에어코리아 측정소정보가
같은 키로 403 난 이유), 그 사실이 기록된 곳이 없었다.
- 서비스 19곳의 발급 페이지 링크. /api/credentials/status로 실제 저장 여부를 읽어
✓(저장됨)/·(미저장)로 표시하므로 하드코딩된 목록이 아니다.
- 공공데이터포털 항목에 "서비스별로 활용신청 필요"를 명시했다.
- 전 링크 https + target=_blank + rel=noopener noreferrer, 값은 escHtml 처리.
- Vault 조회에 실패해도 링크는 그대로 보이고 경고만 덧붙인다. 저장 여부를 잘못
표시하는 것보다 모른다고 하는 편이 낫다.
브라우저에서 렌더 확인: 19개 중 17개 ✓, 미저장으로 표시된 Brave·OpenAI는
실제 Vault 내용과 일치. 콘솔 에러 없음.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
기존 "ollama" provider(클로서버 localhost, 주로 *:cloud 모델용)는 그대로
두고, LAN의 다른 로컬 GPU 머신(지서버 등)을 가리키는 두 번째 독립 Ollama
provider "ollama_local"을 신설. OllamaAdapter가 id를 생성자 인자로 받게
바꿔서(기존 'ollama' 하드코딩 → 'ollama'|'ollama_local' 선택 가능) 두
인스턴스를 provider 캐시/설정에서 구분함. 설정화면에 "Ollama
(클로서버/클라우드)" / "Ollama (지서버/로컬)"로 라벨 분리, 엔드포인트·모델
입력 필드 추가.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
프론트엔드가 예전부터 호출하던 POST /api/openai/models가 백엔드에 구현이
안 되어 있어 항상 404였던 버그 수정 - OpenAICompatAdapter로 실제 OpenAI
API에 모델 목록을 조회하도록 구현.
지서버(LM Studio)의 gemma-4-26b-a4b가 응답당 완성 토큰을 2.5배 더 많이
쓰는 것을 벤치마크로 확인(322 vs 132 토큰/응답, thinking 오버헤드로 추정) -
chat_template_kwargs.enable_thinking=false를 기본 적용하되, 복잡한 질문에서
품질이 떨어질 수 있어 설정 화면(모델 → LM Studio)에 체크박스로 켜고 끌 수
있게 구현. 기본값은 비활성화(현재 상태 유지).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
이미지/동영상/사진편집/이모티콘세트 탭과 ComfyUI 탭 양쪽에 AI 프롬프트
도우미를 붙여, 한국어로 설명하면 SDXL/FLUX/LTX용 영어 프롬프트를 만들어줌.
생성 중에는 예상 소요시간 기준 진행률 바를 표시.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- google-usage.ts: Gemini 무료티어 RPD(하루 500회 추정치) 대비 요청 카운트를
날짜별로 추적(.smallclaw/google-usage.json, 자정 자동 리셋)
- openai-compat-adapter.ts: google provider일 때 post() 호출마다 카운트
- factory.ts: getProvider()/getModelForRole() 둘 다 getEffectiveProviderConfig()
경유하도록 통일 — 카운트가 480(여유분 20 적용) 넘으면 자동으로
ollama+deepseek-v4-flash:cloud로 전환(429 맞고 반응하는 게 아니라 사전 차단).
provider/model 선택이 별도 함수라 따로 고치면 불일치 날 수 있어서 공유
헬퍼로 묶음
- 메인 채팅 사이드바 모델 pill에 "N/500" 뱃지 추가(Google일 때만 표시,
70%↑ 노랑/90%↑ 빨강), 기존 10초 폴링(checkStatus)에 얹어서 실시간 반영
- 실사용 확인: 배포 직후 실제 채팅으로 카운트 0→1 증가 확인, 480 시뮬레이션
으로 폴백 전환/복귀 양쪽 다 확인
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- LLMProvider.ts/factory.ts: 'google' ProviderID 추가, 기존 openai_compat
어댑터를 Gemini의 OpenAI 호환 엔드포인트로 재사용(새 어댑터 클래스 없음).
api_key는 resolveEnvKey가 아닌 getConfig().resolveSecret()로 해석 —
vault: 참조를 실제로 못 푸는 기존 openai/anthropic 케이스의 버그를
google 케이스에서는 피해감
- config.ts: llm.providers.google 기본값 추가(기본모델 gemini-3.6-flash),
SECRET_FIELD_MAP에 api_key 자동 vault 이관 등록
- 설정 화면(index.html/settings-provider.js): LLM 제공자 드롭다운에
Google 옵션 + API키/모델 선택 UI, anthropic 패턴 그대로 재사용
- openai-compat-adapter.ts: 기본 max_tokens 512→4000 — 사고형 모델
(gemini-3.6-flash)이 내부 사고 토큰에 예산을 다 써서 빈 응답 반환하는
버그 실측 후 수정(llama_cpp/lm_studio/openai/google 전부 공유하는 값)
- 5문항 벤치마크로 gemini-3.6-flash/3.5-flash-lite/3.1-flash-lite 비교,
secondary 후보로 3.5-flash-lite 확정(정직함 동률 최상위+5배 빠름+저가)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- handle-chat.ts: excel/coder+shell/email/kakao/sqlite_query/image_edit/
image_read/run_command/news_search/pdf_*/schedule_job를 전용 앱 세션·
키워드·첨부 신호로 스코핑 (기존 weather/legal/pptx/dental/mind 패턴과
동일). 일반 대화 기준 도구 스키마 오버헤드 10,204→3,951 토큰(-61%)
- desktop_*(10개, Windows 전용) 완전 제외 — 이 리눅스 서버에서 전량
실패 확인(tool_audit.log), process.platform 체크라 윈도우 이전시 자동 복구
- 채팅 기본 num_predict 4096→6000 (사고형 모델 빈 응답 여유분)
- ollama-adapter.ts: 턴마다 prompt/completion 토큰을 token-usage.jsonl에
기록 (Ollama Cloud vs 종량제 API 비용 비교용)
- web-ui/js/ 25개 파일을 app/·code-editor/·voice/·music/·claude-app/
서브디렉토리로 재배치, 전체 참조 경로 갱신
- package.json build 스크립트에 dist 클린 단계 추가
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- coder_write_file이 xlsx/pptx/docx/pdf/zip 같은 바이너리 확장자로 파일을
만들려 하면 차단 (텍스트 escape로 바이너리를 흉내내다 파일이 깨지던 문제)
- 레거시 HTML 리다이렉트(/xlsx-viewer.html 등)가 쿼리스트링을 버리던 버그 수정
- xlsx-viewer: "새 시트"가 존재하지 않는 __new__.xlsx를 fetch하다 404나던
것을 빈 워크북 생성 후 저장 시 파일명 입력받는 흐름으로 교체
- xlsx-viewer: LuckyExcel 변환이 내놓는 손상된 숫자 서식("???.???")을
Luckysheet SSF 파서가 못 읽고 죽던 문제에 sanitizer 적용
- 회계사 앱 업로드가 존재하지 않는 /api/upload를 호출하던 것을
/api/upload/file로 수정
- web-ui/js/helpers.js(2240줄)를 helpers-core/markdown/voice/media/files.js
5개로 분리 (기능 변경 없음, 함수 단위 1:1 이관 확인됨)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
app.js 4728-6961(Helpers 섹션 전체)을 web-ui/js/helpers.js로 추출.
escHtml/_normalizeFileUrl/_renderLinkButton, renderMarkdown+embed 변환기,
TTS/voice(mic 포함), music/MIDI, smiles/tradingview, 파일 미리보기/업로드,
동적 로더(SheetJS/Luckysheet/PDF.js) 등 75개 함수 + midiRouter 객체.
모든 함수를 전역 function 선언으로 유지(모듈 래핑 안 함)해 inline onclick
문자열(downloadFile, openImageZoom, togglePdfEmbed, playTTSMsg, ...)이
그대로 동작. state vars(_xxxLoaded, stagedAttachments, _tts*, _appSendFn
등)도 함께 이동.
escHtml 중복(원래 3625, dead code — 5412 버전이 이김) 제거로 단일 정의.
escapeHtml(이메일 렌더링 전용, 다른 매핑)은 app.js에 유지.
index.html: voice-call → helpers → chat-sse → app 순서로 로드.
node --check 통과, 재시작 후 /js/helpers.js 200 확인.
다음(B2)에서 helpers.js를 5개 파일로 세분화 예정.
Co-Authored-By: Claude <noreply@anthropic.com>
app.js 1411-2092의 async function sendChat을 web-ui/js/chat-sse.js로
추출. 범위 내 top-level 선언은 sendChat 하나뿝이라 외부 노출 대상은
sendChat 단일. 끝에 window.sendChat 할당 추가. app.js에서 제거 후
index.html에서 app.js 직전에 로드.
공유 상태(currentAbortController/isThinking)와 외부 헬퍼
(authHeaders/renderChatMessages/addProcessEntry 등)는 전역 그대로
참조. node --check 통과, 재시작 후 /js/chat-sse.js 200 확인.
Co-Authored-By: Claude <noreply@anthropic.com>