diff --git a/.smallclaw/skills/investor/SKILL.md b/.smallclaw/skills/investor/SKILL.md index 2cb123c..5bedcbd 100644 --- a/.smallclaw/skills/investor/SKILL.md +++ b/.smallclaw/skills/investor/SKILL.md @@ -15,6 +15,47 @@ version: 1.0.0 --- +## ⚠️ 시스템 내장 주가 차트 렌더링 — 반드시 읽을 것 + +이 시스템에는 **TradingView 실시간 주가 차트** 기능이 내장되어 있습니다. + +주가 차트 요청 시 아래 **단순 형식**의 코드블록만 출력하면 채팅창에 "📈 차트 불러오기" 버튼이 자동 생성됩니다: + +```tradingview +KRX:005930|D +``` + +**⚠️ 절대 JSON 형식 사용 금지** — `{...}` 객체 대신 반드시 `거래소:종목코드|봉주기` 한 줄만 출력 + +**형식**: `거래소:종목코드|봉주기` + +| 봉주기 | 코드 | +|---|---| +| 1분봉 | `1` | +| 5분봉 | `5` | +| 15분봉 | `15` | +| 1시간봉 | `60` | +| 일봉 | `D` | +| 주봉 | `W` | +| 월봉 | `M` | + +**거래소 코드 예시**: +- 한국 코스피: `KRX:005930` (삼성전자), `KRX:000660` (SK하이닉스) +- 한국 코스닥: `KOSDAQ:035720` (카카오) +- 미국 나스닥: `NASDAQ:AAPL`, `NASDAQ:NVDA`, `NASDAQ:TSLA` +- 미국 NYSE: `NYSE:BRK.B`, `NYSE:JPM` +- 환율: `FX:USDKRW`, `FX:EURUSD` +- 금/원자재: `TVC:GOLD`, `TVC:SILVER`, `NYMEX:CL1!` (원유) +- 암호화폐: `BINANCE:BTCUSDT`, `BINANCE:ETHUSDT` +- 지수: `KRX:KOSPI`, `FOREXCOM:SPXUSD` (S&P500), `NASDAQ:QQQ` + +**주가 차트가 자동으로 렌더링되므로 절대 금지 문구**: +- "직접 차트를 보여드릴 수 없습니다" +- "TradingView 사이트에서 확인하세요" +- 외부 링크 안내 + +--- + ### 사용 가능한 도구 | 도구 | 활용 상황 | diff --git a/.smallclaw/skills/musician/SKILL.md b/.smallclaw/skills/musician/SKILL.md index b397067..e031191 100644 --- a/.smallclaw/skills/musician/SKILL.md +++ b/.smallclaw/skills/musician/SKILL.md @@ -5,6 +5,29 @@ emoji: "🎵" version: 1.0.0 --- +## ⚠️ 시스템 내장 악보 렌더링 — 반드시 읽을 것 + +이 시스템에는 **ABC notation → SVG 악보 + MIDI 자동 변환** 기능이 서버에 내장되어 있습니다. + +악보 요청 시 아래 형식의 코드블록만 출력하면 채팅창에 "악보 렌더링" 버튼이 자동 생성됩니다: + +```abc +X:1 +T:제목 +M:3/4 +L:1/8 +Q:1/4=120 +K:G +...음표... +``` + +**절대 금지 문구** (시스템이 처리하므로 언급 불필요): +- "직접 생성 불가" / "파일을 만들어 드릴 수 없습니다" +- "LilyBin", "온라인 편집기", "붙여넣으세요" +- "LilyPond 코드를 복사하세요" + +--- + ## 음악 어시스턴트 모드 활성화 당신은 클래식 작곡·재즈 화성·대중음악 프로듀싱·악기 코칭에 두루 능통한 음악 어시스턴트입니다. 열정적이되 정확하게, 이론은 쉬운 말로, 실습은 구체적으로 안내하세요. @@ -36,8 +59,14 @@ version: 1.0.0 | 국악 자료 | `site:gugak.go.kr` | 국립국악원 (국악 악보·음원·이론) | | 저작권 정보 | `site:komca.or.kr` | 한국음악저작권협회 | | 재즈 리드시트 | `site:jazzstandards.com` | 재즈 스탠다드 코드·멜로디 분석 | +| MIDI 다운로드 (팝·록·클래식) | `site:freemidi.org` | 팝·록·클래식·게임 MIDI 무료 대형 라이브러리 | +| MIDI 다운로드 (대량) | `site:bitmidi.com` | 10만+ MIDI 파일 무료 검색·스트리밍·다운로드 | +| MIDI 다운로드 (고품질 클래식 피아노) | `site:piano-midi.de` | 베토벤·쇼팽·리스트 등 고품질 클래식 피아노 MIDI | +| MIDI 다운로드 (종합 아카이브) | `site:midiworld.com` | 클래식·팝·게임·영화 음악 MIDI 아카이브 | 예시: 베토벤 소나타 악보 → `web_search("site:imslp.org Beethoven piano sonata op.13")` +예시: 쇼팽 야상곡 MIDI → `web_search("site:piano-midi.de Chopin nocturne")` +예시: 팝 MIDI 검색 → `web_search("site:freemidi.org 곡 제목")` --- @@ -50,10 +79,26 @@ version: 1.0.0 3. **화성 진행** — 기능화성 또는 재즈/모달 화성 기반 코드 제안 (로마 숫자 표기 병기) 4. **멜로디 아이디어** — 음역·리듬 패턴·모티프 발전 방향 제시 5. **편성 조언** — 음역대·음색·밸런스 고려한 악기 배치 -6. **악보/리드시트** — 요청 시 ABC notation 또는 텍스트 악보로 표현 +6. **악보/리드시트** — 요청 시 반드시 ABC notation 코드블록으로 출력 (렌더링 버튼이 자동 생성됨) **코드 표기 규칙**: 로마 숫자(I, IV, V7, ii, iii°) + 팝/재즈 표기(Cmaj7, Dm7, G7) 병기. +**악보 출력 규칙**: 멜로디·코드·리드시트를 출력할 때는 반드시 아래 형식으로 감싸세요. 이 블록은 자동으로 SVG 악보로 렌더링되고 MIDI 재생 버튼이 생성됩니다. + +~~~ +```abc +X:1 +T:제목 +M:4/4 +L:1/8 +Q:1/4=120 +K:C +...음표... +``` +~~~ + +> ABC notation 기본 규칙: `C` = 중간 C, `c` = 한 옥타브 위, `C,` = 한 옥타브 아래. 음표 길이: `C2`=2박, `C/2`=반박. 쉼표: `z`. 마디 구분: `|`. 코드: `"Am"A4`. + --- ### 악보 PDF 분석 방법 diff --git a/web-ui/index.html b/web-ui/index.html index 0e7be59..a4c502e 100644 --- a/web-ui/index.html +++ b/web-ui/index.html @@ -9253,6 +9253,37 @@ function renderMarkdown(text, opts) { '' + ''; } + if (lang === 'tradingview') { + var tvSymbol = '', tvInterval = 'D'; + // code content is already HTML-escaped at this point — unescape before parsing + var tvRaw = trimmed.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/gi,'"').replace(/'/g,"'").trim(); + if (tvRaw.startsWith('{')) { + try { + var tvJson = JSON.parse(tvRaw); + tvSymbol = (tvJson.symbol || tvJson.SYMBOL || '').toString().toUpperCase(); + tvInterval = (tvJson.interval || tvJson.INTERVAL || 'D').toString().toUpperCase(); + } catch(e) { tvSymbol = tvRaw.split('|')[0].trim().toUpperCase(); } + } else { + var parts = tvRaw.split('|'); + tvSymbol = (parts[0] || '').trim().toUpperCase(); + tvInterval = (parts[1] || 'D').trim().toUpperCase(); + } + if (!tvSymbol) return '
' + trimmed + '
'; + var tvId = 'tv-' + Math.random().toString(36).slice(2, 9); + return '
' + + '
' + + '📈 ' + escHtml(tvSymbol) + ' · ' + escHtml(tvInterval) + '' + + '
' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'; + } return '
' + trimmed + '
'; }); @@ -9286,6 +9317,45 @@ function renderMarkdown(text, opts) { return html; } +function renderTVChart(btn, containerId, symbol, interval) { + btn.disabled = true; + btn.textContent = '로딩 중...'; + var isDark = document.documentElement.getAttribute('data-theme') !== 'light'; + function doInit() { + try { + new TradingView.widget({ + container_id: containerId, + autosize: true, + symbol: symbol, + interval: interval || 'D', + timezone: 'Asia/Seoul', + theme: isDark ? 'dark' : 'light', + style: '1', + locale: 'kr', + enable_publishing: false, + allow_symbol_change: true, + save_image: false, + withdateranges: true, + hide_side_toolbar: false, + studies: ['RSI@tv-basicstudies', 'MACD@tv-basicstudies'], + }); + btn.style.display = 'none'; + } catch(e) { + btn.disabled = false; + btn.textContent = '📈 차트 불러오기'; + } + } + if (window.TradingView && window.TradingView.widget) { + doInit(); + } else { + var s = document.createElement('script'); + s.src = 'https://s3.tradingview.com/tv.js'; + s.onload = doInit; + s.onerror = function() { btn.disabled = false; btn.textContent = '📈 차트 불러오기'; }; + document.head.appendChild(s); + } +} + function toggleMusicWidget(widgetId) { var widget = document.getElementById(widgetId); if (!widget) return; @@ -9495,7 +9565,7 @@ function buildMusicActions(data, downloadName) { + '' + ''; html += ''; - html += ''; + html += ''; html += '⬇ MIDI'; } if (pdfUrl) html += '⬇ PDF'; @@ -9616,7 +9686,7 @@ function buildMidiPickerWidget(data, filename) { savedLine + '
' + '' + - '' + + '' + '' + '
' + playerPanel + @@ -11061,6 +11131,10 @@ function canvasRun() { }); })(); +// Stop MIDI playback when tab is closed or navigated away +window.addEventListener('beforeunload', () => { try { midiRouter.stop(); } catch {} }); +window.addEventListener('pagehide', () => { try { midiRouter.stop(); } catch {} }); + // Resize xterm when window resizes window.addEventListener('resize', () => { const shell = getActiveShell();