feat: 보드 선택에 언어 옵션 추가 + 아두이노 힌트 범용화

- 보드 드롭다운 상단에 Python / C++(일반) / 기타 언어 옵션 추가
- _boardSpecificHints 리팩터: 모든 하드웨어 보드에 Arduino C++ 기본 규칙 적용
- arch별 추가 규칙 (esp32/esp8266/stm32/avr) 유지
- STM32 힌트 "Python 절대 사용 금지" → "PC 앱 필요 시 병행 가능"으로 완화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kim
2026-06-30 17:50:12 +09:00
co-authored by Claude Sonnet 4.6
parent 1eb4674303
commit 267441d633
+69 -43
View File
@@ -1584,49 +1584,53 @@ function _boardSpecificHints(code = '') {
const fqbn = _csbSelBoard.fqbn || '';
const arch = fqbn.split(':')[1] || '';
const usesWifi = /WiFi|ESP8266WiFi|HTTPClient|WebServer/i.test(code);
if (arch === 'esp32') {
const lines = [
'ESP32 코딩 규칙 (반드시 준수):',
'- analogWrite() 없음 → ledcAttach(pin, freq, res) + ledcWrite(pin, duty) 사용',
'- loop()에서 오래 blocking하면 watchdog(TG1WDT) 리셋 발생 → delay() 또는 yield() 필수',
'- SPIFFS 대신 LittleFS 권장 (esp32 3.x 코어)',
];
if (usesWifi) lines.splice(1, 0,
'- WiFi는 SoC 내장 — #include <SPI.h> 절대 추가하지 말 것',
'- #include <WiFi.h> 하나로 충분 (esp32 코어 제공)',
'- WiFi.firmwareVersion() 없음 (WiFiNINA 전용)');
return '\n' + lines.join('\n');
}
if (arch === 'esp8266') {
const lines = [
'ESP8266 코딩 규칙 (반드시 준수):',
'- analogWrite() 있음 (PWM, 0-1023)',
'- loop()에서 yield() 또는 delay() 호출 필수 (watchdog 방지)',
'- SPI.h / ESP32Servo.h / ledcAttach 등 ESP32 전용 API 사용 금지',
];
if (usesWifi) lines.splice(1, 0,
'- WiFi: #include <ESP8266WiFi.h> — #include <WiFi.h> 절대 사용 금지',
'- WiFi 암호화 상수: ENC_TYPE_NONE / ENC_TYPE_CCMP / ENC_TYPE_TKIP 사용 (WIFI_AUTH_OPEN 등 ESP32 상수 금지)',
'- WiFi 스캔: WiFi.scanNetworks() / WiFi.getNetworkInfo() / WiFi.getScanInfoByIndex() / bss_info 사용');
return '\n' + lines.join('\n');
}
if (arch === 'avr') {
const lines = ['AVR(Arduino) 코딩 규칙:'];
if (usesWifi) lines.push('- WiFi 실드: #include <SPI.h> + #include <WiFiNINA.h> 또는 #include <WiFi.h>');
return '\n' + lines.join('\n');
}
if (arch === 'stm32') {
// 언어 직접 선택 (하드웨어 보드 아님)
if (fqbn === 'lang:python') {
return '\n' + [
'STM32 (Arduino) 코딩 규칙 (반드시 준수):',
'- 언어: C++ (Arduino 스케치), Python 절대 사용 금지',
'- 핀 이름: PB_3·PA_0 등 STM32 PinName 상수 사용. Arduino D-number와 혼용 주의',
'- Serial = USART1 (PA9/PA10), Serial.begin(115200) 필수',
'- analogWrite() 없음 → HardwareTimer + PWM 채널 사용',
'- Wire(I2C1): SDA=PB7, SCL=PB6 (블루필 기본)',
'- SPI1: MOSI=PA7, MISO=PA6, SCK=PA5',
'언어: Python',
'- 순수 Python 코드로 작성하세요. Arduino/C++ 문법 사용 금지',
'- 스크립트는 표준 Python 환경에서 실행됩니다',
].join('\n');
}
return '';
if (fqbn === 'lang:cpp') {
return '\n' + [
'언어: C++ (일반)',
'- 표준 C++17 코드로 작성하세요. Arduino 전용 API 사용 금지',
].join('\n');
}
if (fqbn === 'lang:other') return '';
// 하드웨어 보드: 공통 Arduino 규칙 + arch별 추가 규칙
if (!arch) return '';
const base = ['Arduino (C++) 코딩 규칙:', '- 언어: C++ (Arduino 스케치). PC 전용 앱이 필요하면 해당 언어 병행 가능'];
const extra = [];
if (arch === 'esp32') {
extra.push('- analogWrite() 없음 → ledcAttach(pin, freq, res) + ledcWrite(pin, duty)');
extra.push('- loop() 장시간 blocking → watchdog 리셋. delay() 또는 yield() 필수');
extra.push('- SPIFFS 대신 LittleFS 권장 (esp32 3.x 코어)');
if (usesWifi) extra.push(
'- WiFi 내장 — #include <WiFi.h> 하나로 충분. SPI.h 추가 금지',
'- WiFi.firmwareVersion() 없음 (WiFiNINA 전용)');
} else if (arch === 'esp8266') {
extra.push('- analogWrite() 있음 (PWM, 0-1023)');
extra.push('- loop()에서 yield() 또는 delay() 필수 (watchdog 방지)');
extra.push('- ledcAttach 등 ESP32 전용 API 사용 금지');
if (usesWifi) extra.push(
'- WiFi: #include <ESP8266WiFi.h>. #include <WiFi.h> 사용 금지',
'- 암호화 상수: ENC_TYPE_NONE / ENC_TYPE_CCMP / ENC_TYPE_TKIP');
} else if (arch === 'stm32') {
extra.push('- 핀 이름: PB_3·PA_0 등 STM32 PinName 상수 사용');
extra.push('- Serial = USART1 (PA9/PA10), Serial.begin(115200) 필수');
extra.push('- analogWrite() 없음 → HardwareTimer + PWM 채널 사용');
extra.push('- Wire(I2C1): SDA=PB7, SCL=PB6 / SPI1: MOSI=PA7, MISO=PA6, SCK=PA5');
} else if (arch === 'avr') {
if (usesWifi) extra.push('- WiFi 실드: #include <SPI.h> + #include <WiFiNINA.h>');
}
// sam, samd, megaavr 등은 base만 적용
return '\n' + [...base, ...extra].join('\n');
}
async function csbCompileOnly() {
@@ -2875,8 +2879,9 @@ function _buildCodeSystemPrompt() {
const summaryBlock = _historySummary ? `\n[이전 대화 요약]\n${_historySummary}\n` : '';
const _activeCode = (() => { try { const f = codeFiles.find(f => f.id === activeCodeFileId); return f?.model?.getValue() || f?.content || ''; } catch { return ''; } })();
const boardHints = _boardSpecificHints(_activeCode);
const isLangOnly = _csbSelBoard?.fqbn?.startsWith('lang:');
const boardBlock = _csbSelBoard?.fqbn
? `\n## 대상 보드: ${_csbSelBoard.name} (${_csbSelBoard.fqbn})${boardHints}\n`
? `\n## ${isLangOnly ? '코딩 언어' : '대상 보드'}: ${_csbSelBoard.name}${isLangOnly ? '' : ` (${_csbSelBoard.fqbn})`}${boardHints}\n`
: '';
// 에디터 파일 컨텍스트 — 로컬 폴더와 분리하여 강조
@@ -4840,16 +4845,37 @@ function csbBoardSelClose() {
if (dd) dd.style.display = 'none';
}
const _LANG_BOARDS = [
{ fqbn: 'lang:python', name: '🐍 Python', desc: 'Python 스크립트' },
{ fqbn: 'lang:cpp', name: '⚡ C++ (일반)', desc: '표준 C++, Arduino 아님' },
{ fqbn: 'lang:other', name: '📝 기타 언어', desc: 'AI가 언어를 판단' },
];
async function csbBoardSelSearch() {
const q = (document.getElementById('csb-board-sel-q')?.value || '').trim();
const list = document.getElementById('csb-board-sel-list');
if (!list) return;
list.innerHTML = '<div style="padding:10px;color:var(--muted);font-size:11px;text-align:center">불러오는 중...</div>';
try {
// 언어 옵션 고정 섹션
const langFiltered = q ? _LANG_BOARDS.filter(b => b.name.includes(q) || b.desc.includes(q)) : _LANG_BOARDS;
const langHtml = langFiltered.length ? [
`<div style="padding:4px 10px 2px;font-size:9px;color:var(--muted);font-weight:600;letter-spacing:.05em;text-transform:uppercase">언어</div>`,
...langFiltered.map(b => {
const sel = b.fqbn === _csbSelBoard.fqbn;
return `<div data-fqbn="${_csbEsc(b.fqbn)}" data-name="${_csbEsc(b.name)}" data-qemu="" data-chip=""
style="padding:6px 10px;cursor:pointer;font-size:11px;border-bottom:1px solid var(--line);display:flex;flex-direction:column;gap:1px;${sel?'background:var(--brand);color:#fff':''}">
<span style="font-weight:600;${sel?'color:#fff':'color:var(--text)'}">${_csbEsc(b.name)}</span>
<span style="font-size:10px;${sel?'color:rgba(255,255,255,.7)':'color:var(--muted)'}">${b.desc}</span>
</div>`;
}),
`<div style="padding:4px 10px 2px;font-size:9px;color:var(--muted);font-weight:600;letter-spacing:.05em;text-transform:uppercase;border-top:1px solid var(--line)">보드</div>`,
].join('') : '';
const data = await api('/api/arduino/boards/listall?q=' + encodeURIComponent(q));
const boards = data.boards || [];
if (!boards.length) { list.innerHTML = '<div style="padding:10px;color:var(--muted);font-size:11px;text-align:center">검색 결과 없음</div>'; return; }
list.innerHTML = boards.map(b => {
if (!boards.length && !langFiltered.length) { list.innerHTML = '<div style="padding:10px;color:var(--muted);font-size:11px;text-align:center">검색 결과 없음</div>'; return; }
list.innerHTML = langHtml + boards.map(b => {
const sel = b.fqbn === _csbSelBoard.fqbn;
const qemuBadge = b.qemu === 'esp8266-native'
? `<span style="font-size:9px;padding:1px 5px;border-radius:3px;margin-left:4px;background:#0c3a4a;color:#67e8f9;border:1px solid #0e7490">🖥 Native</span>`