diff --git a/.vscodeignore b/.vscodeignore index 4639bd4..22e14ba 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -9,3 +9,6 @@ tsconfig.json *.vsix assets/icon.svg preview.html +_*.json +_yt_* +_* diff --git a/PATCHNOTES.md b/PATCHNOTES.md index 156a67c..806a281 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -1,5 +1,82 @@ # Astra Patch Notes +## v2.2.48 (2026-05-20) +### ๐Ÿงน ์ถœ๋ ฅ ํ’ˆ์งˆ โ€” ๋‚ด๋ถ€ ์ฒดํฌ ๋กœ๊ทธ ์ฐจ๋‹จ + ํ•œ์˜ ํ† ํฐ ๊นจ์ง ์ •์ œ +- **`[Self-Reflector Check]` ๋‚ด๋ถ€ ๊ฒ€์ฆ ๋กœ๊ทธ ๋…ธ์ถœ ์ฐจ๋‹จ.** Self-Reflector Phase A๋ฅผ ๊ธฐ๋ณธ ๋น„ํ™œ์„ฑํ™”(`g1nation.selfReflector.enabled` ๊ธฐ๋ณธ๊ฐ’ `true`โ†’`false`). ๋‹ต๋ณ€ ๋์— `Consistency/Completeness/Accuracy` ๋‚ด๋ถ€ ์ฒดํฌ ๋ธ”๋ก์ด ๋” ์ด์ƒ ๋ถ™์ง€ ์•Š๋Š”๋‹ค โ€” ์ผ๋ฐ˜ ์ฑ„ํŒ…ยทํšŒ์‚ฌ ๋ชจ๋“œ ๋ชจ๋‘ ์ ์šฉ. ๊ธฐ๋Šฅ ์ž์ฒด๋Š” ๋‚จ์•„ ์„ค์ •์—์„œ ์ผค ์ˆ˜ ์žˆ์Œ. +- **ํ•œ์˜ ํ† ํฐ ๊นจ์ง ์ •์ œ.** ์Šฌ๋ž˜์‹œ ํ•ฉ์„ฑ(`callLmSynthesis`)ยท์ผ๋ฐ˜ ์ฑ„ํŒ…(`getSystemPrompt`) ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ์ถœ๋ ฅ ์œ„์ƒ ๊ทœ์น™ ์ถ”๊ฐ€ โ€” ํ•œ ๋‹จ์–ด ์•ˆ์— ํ•œ๊ธ€ยท์˜๋ฌธ ์•ŒํŒŒ๋ฒณ ํ˜ผ์šฉ ๊ธˆ์ง€(`๊ฒฐently`ยท`์ธorp` ๊ฐ™์€ ๊นจ์ง„ ํ•ฉ์„ฑ ํ‘œ๊ธฐ ์ฐจ๋‹จ), ์™ธ๋ž˜์–ด๋Š” ์™„์ „ ํ•œ๊ธ€ ๋˜๋Š” ์™„์ „ ์˜๋ฌธ์œผ๋กœ ์ผ๊ด€๋˜๊ฒŒ. +- **์•ˆ์ „๋ง.** ์Šฌ๋ž˜์‹œ ํ•ฉ์„ฑ ๊ฒฐ๊ณผ์— ๋‚ด๋ถ€ ๊ฒ€์ฆ ๋กœ๊ทธ๊ฐ€ ์ƒˆ์–ด ๋‚˜์˜ค๋ฉด ํ›„์ฒ˜๋ฆฌ ์ •๊ทœ์‹์œผ๋กœ ์ž๋™ ์ œ๊ฑฐ. +- **์‹ ๊ทœ ํŒจํ‚ค์ง•:** `astra-2.2.48.vsix`. + +--- + + + +## v2.2.47 (2026-05-20) +### ๐Ÿ”— /wikify ๋‹ค์ค‘ ๋งํฌ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ +- `/wikify`์— **์—ฌ๋Ÿฌ ๋งํฌ๋ฅผ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„ํ•ด ํ•œ ๋ฒˆ์—** ๋„ฃ์œผ๋ฉด 1๊ฐœ์”ฉ ์ˆœ์ฐจ ์œ„ํ‚คํ™”ํ•œ๋‹ค. ์˜ˆ: `/wikify url1 url2 โ€ฆ url10` โ†’ 10๊ฐœ ์œ„ํ‚ค ๋ฌธ์„œ ์ƒ์„ฑ. +- ์ง„ํ–‰ ํ‘œ์‹œ `[i/N]`, ํ•œ ๊ฑด์ด ์‹คํŒจํ•ด๋„ ๋‚˜๋จธ์ง€๋Š” ๊ณ„์† ์ง„ํ–‰, ์™„๋ฃŒ ์‹œ `N/M๊ฐœ ์„ฑ๊ณต` ์š”์•ฝ. +- URL๊ณผ ์ฃผ์ œ๋ช… ์ž๋™ ๋ถ„๋ฅ˜ โ€” URL ํŒจํ„ด ํ† ํฐ์€ ๋ชจ๋‘ ์ฒ˜๋ฆฌ ๋Œ€์ƒ, ๋น„-URL ํ† ํฐ์€ ๊ณตํ†ต ์ฃผ์ œ๋ช…์œผ๋กœ. +- ๋‹จ์ผ ๋งํฌ ์ž…๋ ฅ์€ ๊ธฐ์กด๊ณผ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘. +- **์‹ ๊ทœ ํŒจํ‚ค์ง•:** `astra-2.2.47.vsix`. + +--- + + + +## v2.2.46 (2026-05-20) +### ๐Ÿ”ง /wikify ์ •ํ™•๋„ ๊ฐœ์„  โ€” ๋ช…์„ธ ๋ฌธ์„œ ์™„์ „์„ฑ + ์œ„ํ‚ค๋งํฌ ๊ต์ • +- **๋ช…์„ธ/์Šคํ‚ค๋งˆ ๋ฌธ์„œ ์™„์ „์„ฑ ๊ฐ•ํ™”.** `buildWikifyPrompt`์— ๊ทœ์น™ ์ถ”๊ฐ€ โ€” ์›๋ฌธ์ด JSON SchemaยทAPI ๋ช…์„ธยท๊ธฐ์ˆ  ์ŠคํŽ™์ด๋ฉด `๐Ÿ“– ์„ธ๋ถ€ ๋‚ด์šฉ`์— ๋ชจ๋“  ํ•„๋“œยท์†์„ฑ์„ ๋ˆ„๋ฝ ์—†์ด ๋งˆํฌ๋‹ค์šด ํ‘œ(`[ํ•„๋“œ|ํƒ€์ž…|ํ•„์ˆ˜/์„ ํƒ|์ œ์•ฝ]`)๋กœ ์ •๋ฆฌํ•˜๊ณ , ์›๋ฌธ `required` ๋ฐฐ์—ด์„ ์ž„์˜ ๋ณ€๊ฒฝ ๊ธˆ์ง€, `additionalProperties`ยท`enum`ยท์ค‘์ฒฉ ๊ตฌ์กฐ๋„ ์›๋ฌธ ๊ทธ๋Œ€๋กœ ๋ฐ˜์˜. (์ด์ „์—” LLM์ด `extra`ยท`models` ๋“ฑ ์ตœ์ƒ์œ„ ํ•„๋“œ๋ฅผ ๋ˆ„๋ฝํ•˜๋˜ ๋ฌธ์ œ โ€” ์ถ”์ถœ์€ ์ •ํ™•ํ–ˆ์œผ๋‚˜ ํ•ฉ์„ฑ ๋‹จ๊ณ„ ์†์‹ค) +- **์œ„ํ‚ค๋งํฌ `[[ ]]` ์ž๋™ ๊ต์ •.** LLM์ด ๋‹ซ๋Š” ๋Œ€๊ด„ํ˜ธ๋ฅผ ํ•˜๋‚˜ ๋น ๋œจ๋ฆฌ๋Š” ๊นจ์ง(`[[rfcs repo]`)์„ ํ›„์ฒ˜๋ฆฌ ์ •๊ทœ์‹์œผ๋กœ ์ž๋™ ๋ณด์ •. +- **์‹ ๊ทœ ํŒจํ‚ค์ง•:** `astra-2.2.46.vsix`. + +--- + + + +## v2.2.45 (2026-05-20) +### ๐Ÿ“š ์‹ ๊ทœ /wikify โ€” ์›น์‚ฌ์ดํŠธ ๋ณธ๋ฌธ์„ P-Reinforce v3.0 ์œ„ํ‚ค ๋ฌธ์„œ๋กœ +- **์‹ ๊ทœ ์Šฌ๋ž˜์‹œ ๋ช…๋ น `/wikify [์ฃผ์ œ๋ช…]`.** ์‚ฌ์ดํŠธ ๋ณธ๋ฌธ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•ด Datacollect Research(`/research`)์™€ ๋™์ผํ•œ **P-Reinforce v3.0 ๊ทœ๊ฒฉ ์œ„ํ‚ค ๋ฌธ์„œ**๋กœ LLM ํ•ฉ์„ฑยท์ €์žฅํ•œ๋‹ค โ€” YAML frontmatter + `๐ŸŽฏ ํ•œ ์ค„ ํ†ต์ฐฐ / ๐Ÿง  ํ•ต์‹ฌ ๊ฐœ๋… / ๐Ÿงฉ ์ถ”์ถœ๋œ ํŒจํ„ด / ๐Ÿ“– ์„ธ๋ถ€ ๋‚ด์šฉ / โš–๏ธ ๋ชจ์ˆœ / ๐Ÿ› ๏ธ ์ ์šฉ ์‚ฌ๋ก€ / โœ… ๊ฒ€์ฆ ์ƒํƒœ / ๐Ÿ”— ๊ด€๋ จ ๋ฌธ์„œ ๋งํฌ([[์œ„ํ‚ค๋งํฌ]]) / ๐Ÿ“ ๋ณ€๊ฒฝ ์ด๋ ฅ`. +- **Bridge์— ๋ณธ๋ฌธ ์ถ”์ถœ ์—”๋“œํฌ์ธํŠธ `/api/web-extract` ์‹ ๊ทœ** โ€” Playwright readability ๋ฐฉ์‹์œผ๋กœ `main`/`article` ๋ณธ๋ฌธ ํ…์ŠคํŠธ๋งŒ ์ถ”์ถœ(navยทheaderยทfooter ๋“ฑ ๋…ธ์ด์ฆˆ ์ œ๊ฑฐ), ๋ณธ๋ฌธ 32000์ž ์ƒํ•œ. +- `/benchmark`(๋””์ž์ธ ๋ฒค์น˜๋งˆํ‚น)์™€ ๋‹ฌ๋ฆฌ `/wikify`๋Š” ์‚ฌ์ดํŠธ **์ฝ˜ํ…์ธ ๋ฅผ ์ง€์‹ ๋ฌธ์„œํ™”**ํ•œ๋‹ค. ๊ฒฐ๊ณผ๋ฌผ์€ `datacollectSavePath`(๋˜๋Š” Bridge `WIKI_RAW_PATH`)์— ์ €์žฅ. +- **์‹ ๊ทœ ํŒจํ‚ค์ง•:** `astra-2.2.45.vsix`. + +--- + + + +## v2.2.44 (2026-05-20) +### ๐Ÿ“œ /youtube โ€” ๋ณด๊ณ ์„œ ์•ž์— ์˜์ƒ ์ „์ฒด ์Šคํฌ๋ฆฝํŠธ ์ถœ๋ ฅ +- `/youtube` ๊ฒฐ๊ณผ๋ฌผ ๋งจ ์•ž์— ์˜์ƒ ์ „์ฒด ์ž๋ง‰(Full Script) ์„น์…˜์„ ์ถ”๊ฐ€. 30์ดˆ ๋ฒ„ํ‚ท์œผ๋กœ ๋ฌถ์–ด `[mm:ss] ๋ฌธ์žฅโ€ฆ` ํ˜•ํƒœ๋กœ ์ •๋ฆฌ โ€” ์ž˜๊ฒŒ ๋Š๊ธด ์ž๋™์ž๋ง‰์„ ๊ฐ€๋…์„ฑ ์žˆ๊ฒŒ ํ•ฉ์ณ, ๋ถ„์„ ๋ณด๊ณ ์„œ์™€ ์›๋ฌธ ๋Œ€๋ณธ์„ ํ•œ ๋ฌธ์„œ์—์„œ ํ•จ๊ป˜ ๋ณธ๋‹ค. +- ํ™”๋ฉด ์ถœ๋ ฅยท์ €์žฅ markdown ์–‘์ชฝ ๋ชจ๋‘ `๐Ÿ“œ ์ „์ฒด ์Šคํฌ๋ฆฝํŠธ` โ†’ `---` โ†’ `๋Œ€๋ณธ ์—ญ๊ธฐํš์„œ` ์ˆœ์„œ๋กœ ์ ์šฉ. +- **์‹ ๊ทœ ํŒจํ‚ค์ง•:** `astra-2.2.44.vsix`. + +--- + + + +## v2.2.43 (2026-05-20) +### โœ๏ธ /youtube ์ถœ๋ ฅ ํฌ๋งท ๊ฐœํŽธ โ€” "๋Œ€๋ณธ ์—ญ๊ธฐํš์„œ" +- `/youtube` ๋ถ„์„ ๋ฆฌํฌํŠธ๋ฅผ ์˜์ƒ ์ œ์ž‘ ๊ฐ€์ด๋“œ(4-๋ Œ์ฆˆ)์—์„œ **๋Œ€๋ณธ(์Šคํฌ๋ฆฝํŠธ) ์—ญ๊ธฐํš์„œ**๋กœ ์ „๋ฉด ๊ฐœํŽธ. BGMยท์ž๋ง‰ยท์ปท ์ „ํ™˜ ๋“ฑ ์˜์ƒ ์—ฐ์ถœ ํ•ญ๋ชฉ์„ ๊ฑท์–ด๋‚ด๊ณ  ์Šคํฌ๋ฆฝํŠธ(ํ…์ŠคํŠธ)ยท์–ธ์–ด ๊ตฌ์กฐ์—๋งŒ ์ง‘์ค‘ํ•œ๋‹ค. +- ์ƒˆ ๋ ˆ์ด์•„์›ƒ 5์ข…: ๐ŸŽฌ ํ•œ ์ค„ ์ธ์ƒ / 1. ์Šคํฌ๋ฆฝํŠธ ๋ผˆ๋Œ€ ๊ตฌ์กฐ๋„(ํ‘œ) / 2. ๋ง์˜ ๋ง› & ํ†ค์•ค๋งค๋„ˆ / 3. ๋‚ด ๋Œ€๋ณธ์— ๋ฐ”๋กœ ์“ฐ๋Š” ์•ก์…˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ / โœ‚๏ธ ๋นˆ์นธ ์ฑ„์šฐ๊ธฐ์‹ ๋Œ€๋ณธ ํ…œํ”Œ๋ฆฟ. +- ์–ธ์–ด์  ์žฅ์น˜๋ฅผ ๊ณ ์ • ํƒœ๊ทธ ์–ดํœ˜(`#FOMO #๊ถŒ์œ„๋ถ€์—ฌ #ํ˜ธ๊ธฐ์‹ฌ๊ฐญ #๋ธŒ๋ฆฟ์ง€๋ฉ˜ํŠธ` ๋“ฑ)๋กœ ๋ผ๋ฒจ๋ง. '์ „๋ฌธ์šฉ์–ด โ†’ ์‰ฌ์šด ๋น„์œ ' ๋ถ„์„ ํ•ญ๋ชฉ ์‹ ์„ค โ€” ํ™”์ž์˜ ๊ตฌ์–ด์ฒด '๋ง์˜ ๋ง›'์„ ๋ช…์‹œ์ ์œผ๋กœ ์ถ”์ถœํ•œ๋‹ค. +- **์‹ ๊ทœ ํŒจํ‚ค์ง•:** `astra-2.2.43.vsix`. + +--- + + + +## v2.2.42 (2026-05-20) +### ๐ŸŽฌ /youtube โ€” Datacollect youtube insight 4-๋ Œ์ฆˆ ๋ถ„์„ ์ด์‹ +- **`/youtube`๊ฐ€ ์ด์ œ LLM 4-๋ Œ์ฆˆ ์ฝ˜ํ…์ธ  ์ œ์ž‘ ๊ฐ€์ด๋“œ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.** ๊ทธ๋™์•ˆ transcript/๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋คํ”„๋งŒ ํ–ˆ์œผ๋‚˜, ์ด์ œ Datacollect ์›น์•ฑ(YoutubePanel)์˜ `build4LensPrompt`๋ฅผ ๊ทธ๋Œ€๋กœ ์ด์‹ โ€” 10์ดˆ ํ›… / ์Šคํฌ๋ฆฝํŠธ ๊ตฌ์กฐ(๊ธฐ์Šน์ „๊ฒฐ ํƒ€์ž„๋ผ์ธ) / ์ œ์ž‘ ๋ฆฌ์†Œ์ŠคยทํŽธ์ง‘ ์Šคํƒ€์ผ / ์ธ๋„ค์ผยท์ œ๋ชฉ CTR 4-๋ Œ์ฆˆ ๋ถ„์„ + ์—ญ๊ธฐํš์„œ + ๋Œ€๋ณธ ํ…œํ”Œ๋ฆฟ์„ ์ƒ์„ฑํ•œ๋‹ค. +- **extract ํ•„๋“œ ๋ฒ„๊ทธ ์ˆ˜์ •.** Bridge `/api/youtube/extract`๋Š” `source` ํ•„๋“œ๋ฅผ ์š”๊ตฌํ•˜๋Š”๋ฐ ASTRA๊ฐ€ `url`์„ ๋ณด๋‚ด "source URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค" ์—๋Ÿฌ๊ฐ€ ๋‚˜๋˜ ๋ฌธ์ œ. ์ด์ œ `{ source, withMetadata, limit }` ๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํ˜ธ์ถœํ•œ๋‹ค. +- **๊ฒฐ๊ณผ๋ฌผ ์ž๋™ ์ €์žฅ.** ๋ถ„์„ markdown์„ `/benchmark`์™€ ๋™์ผํ•˜๊ฒŒ raw ํด๋”(`datacollectSavePath` > Bridge `WIKI_RAW_PATH`)์— ์ €์žฅ. +- **๋ช…๋ น์–ด ๋ณด์กฐ ์ปจํ…์ŠคํŠธ.** `/youtube <์šฐ๋ฆฌ ์ฑ„๋„ ์„ค๋ช…>` ํ˜•ํƒœ๋กœ URL ๋’ค ์ž์—ฐ์–ด๋Š” "์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค ์ฝ˜ํ…์ธ " ์ปจํ…์ŠคํŠธ๋กœ ๋ถ„์„์— ๋ฐ˜์˜๋œ๋‹ค. +- **์‹ ๊ทœ ํŒจํ‚ค์ง•:** `astra-2.2.42.vsix`. + +--- + + + ## v2.2.41 (2026-05-20) ### ๐ŸŽ›๏ธ /benchmark ํ•ฉ์„ฑ Temperature ์„ค์ • ์ถ”๊ฐ€ - **`g1nation.datacollectSynthesisTemperature` ์‹ ์„ค** (๊ธฐ๋ณธ 0.1). `/benchmark` LLM 4-๋ Œ์ฆˆ ํ•ฉ์„ฑ์˜ temperature๋ฅผ Astra Settings ํŒจ๋„ 'Datacollect' ์„น์…˜์—์„œ ์กฐ์ ˆ ๊ฐ€๋Šฅ โ€” ๊ทธ๋™์•ˆ ์ฝ”๋“œ์— `0.3`์œผ๋กœ ํ•˜๋“œ์ฝ”๋”ฉ๋ผ ์žˆ์—ˆ๋‹ค. ๋‚ฎ์ถœ์ˆ˜๋ก(0.1) ํ•œ๊ตญ์–ด ์ƒ์„ฑ ์ค‘ ์„ž์ด๋Š” ๊นจ์ง„ ๋ฌธ์žยทํ™˜๊ฐ์ด ์ค„๊ณ  ๊ฒฐ๊ณผ๊ฐ€ ๊ฒฐ์ •์ ์ด๋‹ค. 0~2 ๋ฒ”์œ„๋กœ ํด๋žจํ”„. diff --git a/package.json b/package.json index 9335338..c6de19c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "astra", "displayName": "Astra", "description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.", - "version": "2.2.41", + "version": "2.2.48", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", @@ -503,8 +503,8 @@ }, "g1nation.selfReflector.enabled": { "type": "boolean", - "default": true, - "description": "Self-Reflector Phase A โ€” append a [Self-Reflector Check] block at the end of every substantive LLM answer (Consistency / Completeness / Accuracy, plus References / Paths for code answers). Zero extra LLM calls โ€” the rule lives in the system prompt and the model self-imposes the checklist. Turns response quality up by making the verification step explicit. Disable for purely casual / chat-only usage." + "default": false, + "description": "Self-Reflector Phase A โ€” append a [Self-Reflector Check] block at the end of every substantive LLM answer (Consistency / Completeness / Accuracy, plus References / Paths for code answers). Zero extra LLM calls โ€” the rule lives in the system prompt and the model self-imposes the checklist. OFF by default: the check block is an internal verification log that leaks into the user-facing answer and reads as unpolished. Enable only if you want that transparency block visible." }, "g1nation.selfReflector.externalVerification": { "type": "boolean", diff --git a/src/config.ts b/src/config.ts index f7d82ac..a9a2f3a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -235,7 +235,7 @@ export function getConfig(): IAgentConfig { return v === 'off' || v === 'strict' ? v : 'smart'; })(), companyIntentAlignmentMaxRounds: Math.max(1, Math.min(5, cfg.get('company.intentAlignmentMaxRounds', 3))), - selfReflectorEnabled: cfg.get('selfReflector.enabled', true), + selfReflectorEnabled: cfg.get('selfReflector.enabled', false), selfReflectorExternalEnabled: cfg.get('selfReflector.externalVerification', false), selfReflectorExecutionEnabled: cfg.get('selfReflector.executionVerification', false), companyPixelOfficeEnabled: cfg.get('company.pixelOffice.enabled', true), diff --git a/src/features/datacollect/slashRouter.ts b/src/features/datacollect/slashRouter.ts index a9181ed..3f20e07 100644 --- a/src/features/datacollect/slashRouter.ts +++ b/src/features/datacollect/slashRouter.ts @@ -16,7 +16,7 @@ import { bridgeFetch, getBridgeBaseUrl } from './bridgeClient'; * ๋ช…๋ น์ด ์ฒ˜๋ฆฌ๋˜๋ฉด true ๋ฐ˜ํ™˜ โ†’ chatHandlers๊ฐ€ ์ผ๋ฐ˜ LLM ํ๋ฆ„์œผ๋กœ ์•ˆ ๋‚ด๋ ค๊ฐ€๊ฒŒ. */ -const COMMANDS = ['/research', '/benchmark', '/youtube', '/blog'] as const; +const COMMANDS = ['/research', '/benchmark', '/youtube', '/blog', '/wikify'] as const; type SlashCommand = typeof COMMANDS[number]; export function isSlashCommand(input: string): boolean { @@ -72,6 +72,7 @@ export async function handleSlashCommand( case '/benchmark': return await runBenchmark(arg, view); case '/youtube': return await runYoutube(arg, view); case '/blog': return await runBlog(arg, view); + case '/wikify': return await runWikify(arg, view); } return true; } catch (e: any) { @@ -410,13 +411,20 @@ ${partTemplate}`; * Bridge `/api/lm` ํ”„๋ก์‹œ๋กœ OpenAI ํ˜ธํ™˜ chat completion 1ํšŒ ํ˜ธ์ถœ. * LLM ์„œ๋ฒ„/๋ชจ๋ธ์€ Astra ์„ค์ •(g1nation.ollamaUrl / defaultModel)์„ ์‚ฌ์šฉํ•œ๋‹ค. */ -async function callLmSynthesis(prompt: string): Promise { +async function callLmSynthesis(prompt: string, systemPrompt?: string): Promise { const cfg = vscode.workspace.getConfiguration('g1nation'); const lmUrl = (cfg.get('ollamaUrl', 'http://127.0.0.1:11434') || 'http://127.0.0.1:11434').replace(/\/$/, ''); const model = (cfg.get('defaultModel', '') || 'gemma4:e2b').trim(); // temperature๋Š” ์„ค์ •๊ฐ’(g1nation.datacollectSynthesisTemperature). ๋‚ฎ์„์ˆ˜๋ก ํ™˜๊ฐยท // ๊นจ์ง„ ๋ฌธ์ž๊ฐ€ ์ค„์–ด๋“ ๋‹ค. 0~2 ๋ฒ”์œ„๋กœ ํด๋žจํ”„, ๊ธฐ๋ณธ 0.1. const temperature = Math.max(0, Math.min(2, cfg.get('datacollectSynthesisTemperature', 0.1) ?? 0.1)); + const baseSys = systemPrompt + || '๋‹น์‹ ์€ ์‹œ๋‹ˆ์–ด UX/UI ๋ถ„์„๊ฐ€์ด์ž ํ”„๋ก ํŠธ์—”๋“œ ์•„ํ‚คํ…ํŠธ๋‹ค. ๋ชจ๋“  ๋ณด๊ณ ์„œ๋Š” ํ•œ๊ตญ์–ด๋กœ, ์ œ๊ณต๋œ JSON ์Šค์บ” ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์ฒด์  ์ˆ˜์น˜๋ฅผ ์ธ์šฉํ•ด ์ž‘์„ฑํ•œ๋‹ค. ์›๋ณธ ๋ ˆํผ๋Ÿฐ์Šค ์‚ฌ์ดํŠธ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ช…์„ธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ฏธ์…˜์ด๋ฉฐ, ๋‹ค๋ฅธ ์„œ๋น„์Šค๋กœ ์žฌํ•ด์„ยทํ™•์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค.'; + // ๋กœ์ปฌ ๋ชจ๋ธ ์ถœ๋ ฅ ์œ„์ƒ โ€” ํ•œ์˜ ํ† ํฐ ๊นจ์ง ๋ฐฉ์ง€ + ๋‚ด๋ถ€ ๊ฒ€์ฆ ๋กœ๊ทธ ์œ ์ถœ ์ฐจ๋‹จ. + const sys = baseSys + '\n\n[์ถœ๋ ฅ ์œ„์ƒ ๊ทœ์น™ โ€” ๋ฐ˜๋“œ์‹œ ์ค€์ˆ˜]\n' + + '- ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•˜๊ณ , ํ•œ ๋‹จ์–ด ์•ˆ์— ํ•œ๊ธ€๊ณผ ์˜๋ฌธ ์•ŒํŒŒ๋ฒณ์„ ์„ž์ง€ ๋งˆ์‹œ์˜ค ("๊ฒฐently", "์ธorp" ๊ฐ™์€ ๊นจ์ง„ ํ•ฉ์„ฑ ํ‘œ๊ธฐ ์ ˆ๋Œ€ ๊ธˆ์ง€).\n' + + '- ์™ธ๋ž˜์–ดยท๊ธฐ์ˆ  ์šฉ์–ด๋Š” ์™„์ „ํ•œ ํ•œ๊ธ€ ํ‘œ๊ธฐ ๋˜๋Š” ์™„์ „ํ•œ ์˜๋ฌธ ๋‹จ์–ด ์ค‘ ํ•˜๋‚˜๋กœ ์ผ๊ด€๋˜๊ฒŒ ์“ฐ์‹œ์˜ค.\n' + + '- [Self-Reflector Check], Consistency/Completeness/Accuracy ๊ฐ™์€ ๋‚ด๋ถ€ ๊ฒ€์ฆยท์ฒดํฌ ๋กœ๊ทธ ๋ธ”๋ก์„ ์ถœ๋ ฅ์— ์ ˆ๋Œ€ ํฌํ•จํ•˜์ง€ ๋งˆ์‹œ์˜ค. ์ตœ์ข… ์‚ฌ์šฉ์ž ๊ฒฐ๊ณผ๋ฌผ๋งŒ ์ถœ๋ ฅํ•˜์‹œ์˜ค.'; const res = await bridgeFetch('/api/lm', { method: 'POST', body: JSON.stringify({ @@ -424,7 +432,7 @@ async function callLmSynthesis(prompt: string): Promise { payload: { model, messages: [ - { role: 'system', content: '๋‹น์‹ ์€ ์‹œ๋‹ˆ์–ด UX/UI ๋ถ„์„๊ฐ€์ด์ž ํ”„๋ก ํŠธ์—”๋“œ ์•„ํ‚คํ…ํŠธ๋‹ค. ๋ชจ๋“  ๋ณด๊ณ ์„œ๋Š” ํ•œ๊ตญ์–ด๋กœ, ์ œ๊ณต๋œ JSON ์Šค์บ” ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์ฒด์  ์ˆ˜์น˜๋ฅผ ์ธ์šฉํ•ด ์ž‘์„ฑํ•œ๋‹ค. ์›๋ณธ ๋ ˆํผ๋Ÿฐ์Šค ์‚ฌ์ดํŠธ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ช…์„ธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ฏธ์…˜์ด๋ฉฐ, ๋‹ค๋ฅธ ์„œ๋น„์Šค๋กœ ์žฌํ•ด์„ยทํ™•์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค.' }, + { role: 'system', content: sys }, { role: 'user', content: prompt }, ], temperature, @@ -436,7 +444,11 @@ async function callLmSynthesis(prompt: string): Promise { ?? res?.answer ?? res?.response ?? ''; - return String(content).trim(); + // ์•ˆ์ „๋ง โ€” ๋ชจ๋ธ์ด ๊ทธ๋ž˜๋„ ๋‚ด๋ถ€ ๊ฒ€์ฆ ๋กœ๊ทธ๋ฅผ ๋ถ™์ด๋ฉด ์ž˜๋ผ๋‚ธ๋‹ค. [Self-Reflector Check] + // ๋ธ”๋ก์€ ํ•ญ์ƒ ๋‹ต๋ณ€ ๋งจ ๋์— ์˜ค๋ฏ€๋กœ ๊ทธ ์ง€์ ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์ œ๊ฑฐํ•œ๋‹ค. + return String(content) + .replace(/\n*(?:```[\w]*\s*)?\[Self-Reflector Check\][\s\S]*$/i, '') + .trim(); } async function runBenchmark(arg: string, view: Webview | undefined): Promise { @@ -580,41 +592,276 @@ async function runBenchmark(arg: string, view: Webview | undefined): Promise { +function formatHms(totalSec: number): string { + if (!isFinite(totalSec) || totalSec <= 0) return '00:00'; + const s = Math.floor(totalSec); + const h = Math.floor(s / 3600); + const m = Math.floor((s % 3600) / 60); + const sec = s % 60; + return h > 0 + ? `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}` + : `${m}:${String(sec).padStart(2, '0')}`; +} + +/** + * ์˜์ƒ ์ „์ฒด ์ž๋ง‰์„ 30์ดˆ ๋ฒ„ํ‚ท์œผ๋กœ ๋ฌถ์–ด `[mm:ss] ๋ฌธ์žฅโ€ฆ` ํ˜•ํƒœ์˜ ์ฝ๊ธฐ ์ข‹์€ full script๋กœ ๋ณ€ํ™˜. + * YouTube ์ž๋™์ž๋ง‰์€ segment๊ฐ€ ์ž˜๊ฒŒ ๋Š๊ฒจ ๊ทธ๋Œ€๋กœ ๋‚˜์—ดํ•˜๋ฉด ๊ฐ€๋…์„ฑ์ด ๋‚˜์˜๋ฏ€๋กœ ๋ฌถ๋Š”๋‹ค. + */ +function fullScriptFromSegments(segments: any[] | undefined): string { + if (!segments || segments.length === 0) return '(์ž๋ง‰ ์—†์Œ โ€” ์ž๋™ ์ž๋ง‰์ด ์—†๋Š” ์˜์ƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)'; + const buckets = new Map(); + for (const seg of segments) { + const b = Math.floor((seg.start || 0) / 30); + const arr = buckets.get(b) || []; + arr.push(String(seg.text || '').trim()); + buckets.set(b, arr); + } + return Array.from(buckets.entries()) + .sort((a, b) => a[0] - b[0]) + .map(([b, texts]) => `**[${formatHms(b * 30)}]** ${texts.join(' ').replace(/\s+/g, ' ').trim()}`) + .join('\n\n'); +} + +/** + * timestamped segments โ†’ ๋ถ„ ๋‹จ์œ„ ๋ฒ„ํ‚ท์œผ๋กœ ๋ฌถ์€ "ํƒ€์ž„๋ผ์ธ ๋ผˆ๋Œ€" ํ…์ŠคํŠธ. + * ยง2 ๊ตฌ์กฐ ๋ถ„์„์—์„œ LLM ํ† ํฐ์„ ์•„๋‚€๋‹ค. + */ +function bucketSegments(segments: any[] | undefined, bucketSec = 30): { time: string; text: string }[] { + if (!segments || segments.length === 0) return []; + const buckets = new Map(); + for (const seg of segments) { + const bucket = Math.floor(seg.start / bucketSec); + const arr = buckets.get(bucket) || []; + arr.push(String(seg.text || '').trim()); + buckets.set(bucket, arr); + } + return Array.from(buckets.entries()) + .sort((a, b) => a[0] - b[0]) + .map(([bucket, texts]) => ({ + time: formatHms(bucket * bucketSec), + text: texts.join(' ').replace(/\s+/g, ' ').trim().slice(0, 240), + })); +} + +/** + * extract๋œ ์˜์ƒ โ†’ ์œ ํŠœ๋ธŒ 4-๋ Œ์ฆˆ(ํ›…/๊ตฌ์กฐ/์ œ์ž‘/CTR) ๋ถ„์„ LLM ํ”„๋กฌํ”„ํŠธ. + * Datacollect ์›น์•ฑ(YoutubePanel)์˜ build4LensPrompt๋ฅผ ๊ทธ๋Œ€๋กœ ์ด์‹. + */ +function build4LensPrompt(video: any, userContent: string): string { + const meta = video.metadata || {}; + const segments = video.segments || []; + + // ์ดˆ๋ฐ˜ 30์ดˆ / 60์ดˆ ํ…์ŠคํŠธ โ€” ยง1 ํ›… ๋ถ„์„์šฉ. + const first30s = segments.filter((s: any) => s.start < 30).map((s: any) => String(s.text || '').trim()).join(' ').slice(0, 600); + const first60s = segments.filter((s: any) => s.start < 60).map((s: any) => String(s.text || '').trim()).join(' ').slice(0, 1200); + + // ํƒ€์ž„๋ผ์ธ ๋ฒ„ํ‚ท (30์ดˆ ๋‹จ์œ„) โ€” ยง2 ๊ตฌ์กฐ ๋ถ„์„์šฉ. + const timelineBuckets = bucketSegments(segments, 30); + const timelinePreview = timelineBuckets.slice(0, 24).map(b => `[${b.time}] ${b.text}`).join('\n'); + + // ์ธ๊ฒŒ์ด์ง€๋จผํŠธ ํ‚ค์›Œ๋“œ ๋งค์น˜ โ€” ยง2 ๋ณด์กฐ. + const engagementHits = segments + .filter((s: any) => /๊ตฌ๋…|์ข‹์•„์š”|์•Œ๋ฆผ|๋Œ“๊ธ€|๊ณต์œ |subscribe|like|comment/i.test(String(s.text || ''))) + .slice(0, 5) + .map((s: any) => ({ t: formatHms(s.start), text: String(s.text || '').trim().slice(0, 100) })); + + const slim = { + url: meta.webpage_url || `https://www.youtube.com/watch?v=${video.video_id}`, + title: meta.title || video.title, + channel: meta.channel, + durationSec: meta.duration, + durationHms: meta.duration_string, + viewCount: meta.view_count, + likeCount: meta.like_count, + commentCount: meta.comment_count, + uploadDate: meta.upload_date, + thumbnail: meta.thumbnail, + tags: (meta.tags || []).slice(0, 12), + categories: meta.categories, + chapters: meta.chapters, + descriptionPreview: (meta.description || '').slice(0, 600), + opening30s: first30s, + opening60s: first60s, + engagementMoments: engagementHits, + segmentCount: segments.length, + timelinePreview, + }; + + const today = new Date().toISOString().slice(0, 10); + const userBlock = userContent.trim() + ? userContent.trim() + : '(๋ฏธ์ž…๋ ฅ โ€” ์ผ๋ฐ˜ ์ฝ˜ํ…์ธ  ์ œ์ž‘์ž ์ปจํ…์ŠคํŠธ๋กœ ์ž‘์„ฑ)'; + + return `๋‹น์‹ ์€ ์œ ํŠœ๋ธŒ '๋Œ€๋ณธ(์Šคํฌ๋ฆฝํŠธ)' ๋ถ„์„ ์ „๋ฌธ๊ฐ€์ด์ž ์ฝ˜ํ…์ธ  ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์‚ฌ์žฅ๋‹˜์ด +์ด ์˜์ƒ๊ณผ ๋น„์Šทํ•œ ์ฝ˜ํ…์ธ ์˜ **๋Œ€๋ณธ์„ ์ง์ ‘ ์“ฐ๋ ค** ํ•ฉ๋‹ˆ๋‹ค. ์˜์ƒ ์—ฐ์ถœ์ด ์•„๋‹ˆ๋ผ ์˜ค์ง +์Šคํฌ๋ฆฝํŠธ(ํ…์ŠคํŠธ)์™€ ์–ธ์–ด ๊ตฌ์กฐ๋งŒ ๋ถ„์„ํ•ด, ์ฝ์ž๋งˆ์ž ์ž๊ธฐ ๋Œ€๋ณธ์— ๋ณต๋ถ™ํ•˜๋“ฏ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” +'์œ ์ € ์นœํ™”์  ์—ญ๊ธฐํš์„œ'๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. + +[๋ถ„์„ ์›์น™] +1. BGMยท์ž๋ง‰ยท์ปท ์ „ํ™˜ยท์ธ๋„ค์ผ ๋“ฑ ๋Œ€๋ณธ๋งŒ์œผ๋กœ ์•Œ ์ˆ˜ ์—†๋Š” '์˜์ƒ ์—ฐ์ถœ' ํ•ญ๋ชฉ์€ ๊ณผ๊ฐํžˆ ์ƒ๋žตํ•œ๋‹ค. + ์˜ค์ง ์Šคํฌ๋ฆฝํŠธ(ํ…์ŠคํŠธ)์™€ ์–ธ์–ด ๊ตฌ์กฐ์—๋งŒ ์ง‘์ค‘ํ•œ๋‹ค. +2. ๋Œ€์‚ฌ๋ฅผ ๋‹จ์ˆœ ์ธ์šฉํ•˜์ง€ ๋ง๊ณ , ๊ทธ ๋Œ€์‚ฌ๊ฐ€ ์‹œ์ฒญ์ž ์‹ฌ๋ฆฌ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฑด๋“œ๋ ธ๋Š”์ง€ '์–ธ์–ด์  ์žฅ์น˜'๋ฅผ + ํƒœ๊ทธ๋กœ ๋ผ๋ฒจ๋งํ•œ๋‹ค. ์•„๋ž˜ ํƒœ๊ทธ ์–ดํœ˜์—์„œ๋งŒ ๊ณจ๋ผ ์ผ๊ด€๋˜๊ฒŒ ์‚ฌ์šฉํ•œ๋‹ค: + #FOMO #๊ถŒ์œ„๋ถ€์—ฌ #ํ˜ธ๊ธฐ์‹ฌ๊ฐญ #์‚ฌํšŒ์ ์ฆ๋ช… #ํŽ˜๋ฅด์†Œ๋‚˜ #์•ฝ์†Promise #๊ณต๊ฐํ›„ํ‚น + #๋ฐ˜์ „ #์ˆซ์ž๊ฐ•์กฐ #๋ฌธ์ œ๊ณ ๋ฐœ #๋ธŒ๋ฆฟ์ง€๋ฉ˜ํŠธ #์‰ฌ์šด๋น„์œ  +3. ์ „๋ฌธ ์šฉ์–ด๊ฐ€ ๋‚˜์˜ค๋ฉด, ํ™”์ž๊ฐ€ ๊ทธ๊ฒƒ์„ ์–ด๋–ค '์‰ฌ์šด ๋น„์œ '๋‚˜ ์ผ์ƒ์–ด๋กœ ํ’€์–ด ๋งํ–ˆ๋Š”์ง€ + ๊ทธ ๊ตฌ์–ด์ฒด '๋ง์˜ ๋ง›'์„ ๋ฐ˜๋“œ์‹œ ๋ถ„์„์— ํฌํ•จํ•œ๋‹ค. +4. ํ•œ๊ตญ์–ด. ์ž๋ง‰(text)ยทchaptersยท๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ์žˆ๋Š” ๊ฒƒ๋งŒ ์ธ์šฉ(์ถ”์ธก ๊ธˆ์ง€). ํƒ€์ž„์Šคํƒฌํ”„๋Š” mm:ss. + +[์˜์ƒ ๋ฐ์ดํ„ฐ] +\`\`\`json +${JSON.stringify(slim, null, 2)} +\`\`\` + +[์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค๊ณ  ์‹ถ์€ ์ฝ˜ํ…์ธ  / ์ฑ„๋„ ์ปจํ…์ŠคํŠธ] +${userBlock} + +[ํ•„์ˆ˜ ์ถœ๋ ฅ ํ˜•์‹ โ€” ์ •ํ™•ํžˆ ์ด ๊ตฌ์กฐ. ์•„๋ž˜ 5๊ฐœ ์„น์…˜ ์™ธ ์ถ”๊ฐ€ ๊ธˆ์ง€] + +# ${slim.title || video.title} โ€” ๋Œ€๋ณธ ์—ญ๊ธฐํš์„œ + +> **์˜์ƒ URL**: ${slim.url} ยท **๋ถ„์„ ์ผ์ž**: ${today} ยท **๊ธธ์ด**: ${slim.durationHms || (slim.durationSec ? formatHms(slim.durationSec) : '?')} ยท **์ฑ„๋„**: ${slim.channel || '?'} + +## ๐ŸŽฌ ํ•œ ์ค„ ์ธ์ƒ (One-line Read) +(์ด ์˜์ƒ ์Šคํฌ๋ฆฝํŠธ์˜ ํ•ต์‹ฌ ์„ฑ๊ฒฉ๊ณผ ์„ค๋“ ์ „๋žต์„ ํ•œ ์ค„๋กœ. ์˜ˆ: "์ „๋ฌธ ์ง€์‹์„ ์นœ๊ตฌ์—๊ฒŒ +์„ค๋ช…ํ•˜๋“ฏ ํ’€์–ด๋‚ด๊ณ , ํ˜ธ๊ธฐ์‹ฌ ๊ฐญ์œผ๋กœ ๋๊นŒ์ง€ ๋Œ๊ณ  ๊ฐ€๋Š” ์ •๋ณดํ˜• ๋Œ€๋ณธ") + +## 1. ์Šคํฌ๋ฆฝํŠธ ๋ผˆ๋Œ€ ๊ตฌ์กฐ๋„ (Script Architecture) +๊ตฌ๊ฐ„๋ณ„ ๋งˆํฌ๋‹ค์šด ํ‘œ 1๊ฐœ. '๋ ˆํผ๋Ÿฐ์Šค ์‹ค์ œ ๋Œ€์‚ฌ'๋Š” ์ž๋ง‰์—์„œ 1๋ฌธ์žฅ ์ด๋‚ด๋กœ ์งง๊ฒŒ ๋”ฐ์˜ดํ‘œ ์ธ์šฉ. +'์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋Šฅ'์—๋Š” ์œ„ ํƒœ๊ทธ ์–ดํœ˜๋ฅผ 1~2๊ฐœ ๋ถ™์ธ๋‹ค. ๋น„์ค‘ %๋Š” durationSec ๊ธฐ์ค€, +chapters๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ๊ฒƒ์„, ์—†์œผ๋ฉด timelinePreview๋กœ ๊ตฌ๊ฐ„์„ ์ถ”์ •. + +| ๊ตฌ๊ฐ„ (๋น„์ค‘) | ์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋Šฅ (ํƒœ๊ทธ) | ๋ ˆํผ๋Ÿฐ์Šค ์‹ค์ œ ๋Œ€์‚ฌ | ๋ฒค์น˜๋งˆํ‚น ํ•ต์‹ฌ ๊ธฐ์ˆ  | +| --- | --- | --- | --- | +| ์˜คํ”„๋‹ Hook (0:00~?, ?%) | #ํ˜ธ๊ธฐ์‹ฌ๊ฐญ #์•ฝ์†Promise | "์ฒซ ๋Œ€์‚ฌโ€ฆ" | ๊ฒฐ๊ณผ๋ฅผ ๋ฏธ๋ฆฌ ํ˜๋ ค ์ดํƒˆ ์ฐจ๋‹จ | +| ๋„์ž…๋ถ€ (?~?, ?%) | โ€ฆ | โ€ฆ | โ€ฆ | +| ๋ณธ๋ก  (?~?, ?%) | โ€ฆ | โ€ฆ | โ€ฆ | +| ์•„์›ƒํŠธ๋กœยทCTA (?~?, ?%) | โ€ฆ | โ€ฆ | โ€ฆ | + +## 2. ๋ง์˜ ๋ง› & ํ†ค์•ค๋งค๋„ˆ (Tone & Manner) +- **๋ฌธ์žฅ ๊ธธ์ด ํŠน์ง•**: ๋‹จ๋ฌธ/์žฅ๋ฌธ, ํ˜ธํก, ๋ฆฌ๋“ฌ โ€” ์‹ค์ œ ์ž๋ง‰ ์˜ˆ์‹œ 1๊ฐœ๋ฅผ ๋”ฐ์˜ดํ‘œ๋กœ. +- **์–ด์กฐ ํŽ˜๋ฅด์†Œ๋‚˜**: ์˜ˆ) ์นœ๊ทผํ•œ ์ „๋ฌธ๊ฐ€์ฒด / ๋‹จ์ •์  ์‹ ๋ขฐ์ฒด โ€” ๊ทผ๊ฑฐ ๋Œ€์‚ฌ 1๊ฐœ. +- **ํ•ต์‹ฌ ๋Œ€์‚ฌ ์žฅ์น˜**: ์‹œ์ฒญ์ž ์ค‘๊ฐ„ ์ดํƒˆ์„ ๋ง‰์œผ๋ ค ๋Œ€๋ณธ ์‚ฌ์ด์— ์‹ฌ์€ ๋ฏธ๋ผ ๋ฌธ์žฅยท๋ธŒ๋ฆฟ์ง€ ๋ฉ˜ํŠธ๋ฅผ + ํƒ€์ž„์Šคํƒฌํ”„์™€ ํ•จ๊ป˜ 2~3๊ฐœ ์ถ”์ถœ, ๊ฐ๊ฐ ํƒœ๊ทธ ๋ผ๋ฒจ์„ ๋ถ™์ธ๋‹ค. +- **์ „๋ฌธ์šฉ์–ด โ†’ ์‰ฌ์šด ๋น„์œ **: ์–ด๋ ค์šด ๊ฐœ๋…์„ ํ™”์ž๊ฐ€ ์–ด๋–ค ๋น„์œ ยท์ผ์ƒ์–ด๋กœ ํ’€์—ˆ๋Š”์ง€ + \`์šฉ์–ด โ†’ "ํ™”์ž์˜ ์‹ค์ œ ํ‘œํ˜„"\` ํ˜•ํƒœ๋กœ 2~3๊ฐœ. ์‚ฌ๋ก€๊ฐ€ ์—†์œผ๋ฉด "ํ•ด๋‹น ์‚ฌ๋ก€ ์—†์Œ"์ด๋ผ ๋ช…์‹œ. + +## 3. ๋‚ด ๋Œ€๋ณธ์— ๋ฐ”๋กœ ์“ฐ๋Š” ์•ก์…˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ (Action Items) +๋‹ค์Œ ๋Œ€๋ณธ์„ ์“ธ ๋•Œ ๋ฌด์กฐ๊ฑด ์ ์šฉํ•  ํ–‰๋™ ์ง€์นจ 3~4๊ฐœ. ๋ฐ˜๋“œ์‹œ ์ฒดํฌ๋ฐ•์Šค๋กœ, ๊ตฌ์ฒด์  ์ˆ˜์น˜๋ฅผ ํฌํ•จ. +- [ ] (์˜ˆ: ์˜คํ”„๋‹ 15์ดˆ ์•ˆ์— '๋‚ด๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€' ํŽ˜๋ฅด์†Œ๋‚˜ ํ•œ ๋ฌธ์žฅ ๋ฐ•๊ธฐ) +- [ ] โ€ฆ +- [ ] โ€ฆ + +## โœ‚๏ธ ๋นˆ์นธ ์ฑ„์šฐ๊ธฐ์‹ ๋Œ€๋ณธ ํ…œํ”Œ๋ฆฟ (Fill-in-the-Blank) +๋ ˆํผ๋Ÿฐ์Šค์˜ ๋งํ•˜๊ธฐ ๊ตฌ์กฐยท์ ‘์†์‚ฌยท๋ฆฌ๋“ฌ์€ ๊ทธ๋Œ€๋กœ ์‚ด๋ฆฌ๊ณ , ๋‚ด ์ฝ˜ํ…์ธ  ๋‚ด์šฉ๋งŒ [ ]์— ์ฑ„์šฐ๋ฉด +๋Œ€๋ณธ์ด ์™„์„ฑ๋˜๋Š” ํ˜•ํƒœ. ๊ฐ [ ] ์•ˆ์—๋Š” ๋ฌด์—‡์„ ๋„ฃ์„์ง€ ์งง์€ ํžŒํŠธ๋ฅผ ์ ๋Š”๋‹ค. + +\`\`\` +[์˜คํ”„๋‹ โ€” Hook] +"์—ฌ๋Ÿฌ๋ถ„, ํ˜น์‹œ [์‹œ์ฒญ์ž์˜ ํ”ํ•œ ๊ณ ๋ฏผ]โ€ฆ ํ•ด๋ณด์‹  ์  ์žˆ์œผ์„ธ์š”? +์˜ค๋Š˜์€ [์ด ์˜์ƒ์ด ์ค„ ํ•ต์‹ฌ ๊ฒฐ๊ณผ]๋ฅผ [์ˆซ์ž]๋ถ„ ๋งŒ์— ๋๋‚ด ๋“œ๋ฆด๊ฒŒ์š”." + +[๋„์ž…๋ถ€ โ€” ๊ณต๊ฐ + ๊ถŒ์œ„] +โ€ฆ + +[๋ณธ๋ก  โ€” ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…] +โ€ฆ + +[์•„์›ƒํŠธ๋กœ โ€” CTA] +โ€ฆ +\`\`\` + +> โš ๏ธ ๋ณธ ๋ถ„์„์€ ์Šคํฌ๋ฆฝํŠธ์˜ ์–ธ์–ดยท๊ตฌ์กฐ ํŒจํ„ด ํ•™์Šต์šฉ์ž…๋‹ˆ๋‹ค. ๋Œ€์‚ฌยท์ž๋ฃŒ๋Š” ์ง์ ‘ ์ฐฝ์ž‘/๋ผ์ด์„ ์Šค ํ™•๋ณด.`; +} + +async function runYoutube(arg: string, view: Webview | undefined): Promise { + // URL ํ† ํฐ๋งŒ ์ถ”์ถœ, ๋‚˜๋จธ์ง€๋Š” ๋ณด์กฐ ์ปจํ…์ŠคํŠธ(์šฐ๋ฆฌ ์ฑ„๋„/์ฝ˜ํ…์ธ  ์„ค๋ช…). + const tokens = arg.trim().split(/\s+/).filter(Boolean); + const url = tokens[0] || ''; + const userContent = tokens.slice(1).join(' '); if (!url) { - chunk(view, `์‚ฌ์šฉ๋ฒ•: \`/youtube \`\n์˜ˆ: \`/youtube https://youtu.be/xxxx\`\n`); + chunk(view, `์‚ฌ์šฉ๋ฒ•: \`/youtube [์šฐ๋ฆฌ ์ฑ„๋„/์ฝ˜ํ…์ธ  ์„ค๋ช…]\`\n์˜ˆ: \`/youtube https://youtu.be/xxxx\`\n`); return true; } - chunk(view, `๐ŸŽฌ **YouTube ์ถ”์ถœ**: ${url}\n(transcript + metadata)\n\n`); - const data = await bridgeFetch<{ success: boolean; metadata?: any; segments?: any[]; plainTranscript?: string; outputDir?: string }>( + chunk(view, `๐ŸŽฌ **YouTube ์ถ”์ถœ**: ${url}\n(์ž๋ง‰ + ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ)\n\nโณ Python ์ถ”์ถœ๊ธฐ ๊ธฐ๋™ ยท ์ž๋ง‰/๋ฉ”ํƒ€ ์ถ”์ถœ ์ค‘โ€ฆ`); + // 1) extract โ€” Bridge๋Š” `source` ํ•„๋“œ๋ฅผ ๊ธฐ๋Œ€ํ•œ๋‹ค(`url`์ด ์•„๋‹˜). + const t0 = Date.now(); + const heartbeat = setInterval(() => { + chunk(view, ` ยท${Math.round((Date.now() - t0) / 1000)}s`); + }, 4000); + const data = await bridgeFetch<{ success: boolean; videos?: any[]; totalVideos?: number }>( '/api/youtube/extract', - { method: 'POST', body: JSON.stringify({ url }) }, + { method: 'POST', body: JSON.stringify({ source: url, withMetadata: true, limit: 5 }) }, { timeoutMs: 5 * 60_000 }, - ); + ).finally(() => clearInterval(heartbeat)); - const m = data.metadata || {}; - chunk(view, `### ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ\n`); - chunk(view, `- **title**: ${m.title || '(์—†์Œ)'}\n`); - chunk(view, `- **channel**: ${m.channel || m.uploader || '(์—†์Œ)'}\n`); - chunk(view, `- **duration**: ${m.duration || '(์—†์Œ)'}์ดˆ\n`); - chunk(view, `- **views**: ${m.view_count ?? '(์—†์Œ)'}\n`); - chunk(view, `- **upload_date**: ${m.upload_date || '(์—†์Œ)'}\n`); - if (Array.isArray(m.tags) && m.tags.length) { - chunk(view, `- **tags**: ${m.tags.slice(0, 10).join(', ')}\n`); + const okVideos = (data.videos || []).filter((v: any) => v?.status === 'ok'); + chunk(view, `\nโœ… **์ถ”์ถœ ์™„๋ฃŒ** (${Math.round((Date.now() - t0) / 1000)}s ยท ${okVideos.length}/${data.totalVideos ?? (data.videos || []).length}๊ฐœ ์˜์ƒ)\n\n`); + if (okVideos.length === 0) { + chunk(view, `โš ๏ธ ์ž๋ง‰ ์ถ”์ถœ์— ์„ฑ๊ณตํ•œ ์˜์ƒ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ž๋ง‰์ด ์—†๊ฑฐ๋‚˜ ๋น„๊ณต๊ฐœ ์˜์ƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n`); + return true; } - if (Array.isArray(m.chapters) && m.chapters.length) { - chunk(view, `\n### ์ฑ•ํ„ฐ (${m.chapters.length})\n`); - for (const c of m.chapters.slice(0, 12)) { - chunk(view, `- ${c.start_time}s โ€” ${c.title}\n`); + + const cfg = vscode.workspace.getConfiguration('g1nation'); + const model = (cfg.get('defaultModel', '') || 'gemma4:e2b').trim(); + const ytSystem = '๋‹น์‹ ์€ ์œ ํŠœ๋ธŒ ์ฝ˜ํ…์ธ  ์‹œ๋‹ˆ์–ด PD์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ์— ๊ทผ๊ฑฐํ•œ ์ œ์ž‘ ๊ฐ€์ด๋“œ๋งŒ ์ œ๊ณตํ•˜์„ธ์š”.'; + + // 2) ์˜์ƒ๋งˆ๋‹ค LLM 4-๋ Œ์ฆˆ ๋ถ„์„ (๋ณดํ†ต 1๊ฑด; ์ฑ„๋„/ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ๋ฉด ์ˆœ์ฐจ). + for (const video of okVideos) { + const vTitle = video?.metadata?.title || video?.title || video?.video_id || '(์ œ๋ชฉ ์—†์Œ)'; + + // ๋ณด๊ณ ์„œ ์•ž์— ์˜์ƒ ์ „์ฒด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋จผ์ € ์ถœ๋ ฅ โ€” ๋ถ„์„๊ณผ ์›๋ฌธ ๋Œ€๋ณธ์„ ํ•จ๊ป˜ ๋ณด๋„๋ก. + const script = fullScriptFromSegments(video?.segments); + chunk(view, `## ๐Ÿ“œ ์ „์ฒด ์Šคํฌ๋ฆฝํŠธ (Full Script)\n\n${script}\n\n---\n\n`); + + chunk(view, `๐Ÿงช **LLM 4-๋ Œ์ฆˆ ๋ถ„์„**: ${vTitle} (๋ชจ๋ธ \`${model}\`)\n๋ชจ๋ธยทํ•˜๋“œ์›จ์–ด์— ๋”ฐ๋ผ ์ˆ˜ ๋ถ„ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹คโ€ฆ`); + let report: string; + try { + const partT0 = Date.now(); + report = await callLmSynthesis(build4LensPrompt(video, userContent), ytSystem); + if (!report) throw new Error('LLM ์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.'); + chunk(view, ` โœ“ (${Math.round((Date.now() - partT0) / 1000)}s)\n\n`); + } catch (e: any) { + chunk(view, `\n\nโš ๏ธ LLM ๋ถ„์„ ์‹คํŒจ: ${e?.message || String(e)}\n(LM ์„œ๋ฒ„๊ฐ€ ๋–  ์žˆ๋Š”์ง€, \`g1nation.ollamaUrl\` / \`defaultModel\` ์„ค์ •์„ ํ™•์ธํ•˜์„ธ์š”.)\n\n`); + continue; + } + chunk(view, report + '\n\n'); + + // 3) save โ€” benchmark์™€ ๋™์ผํ•˜๊ฒŒ /api/wiki/save (datacollectSavePath > WIKI_RAW_PATH). + try { + const today = new Date().toISOString().slice(0, 10); + const videoUrl = video?.metadata?.webpage_url || `https://www.youtube.com/watch?v=${video?.video_id}`; + const title = `์œ ํŠœ๋ธŒ๋ถ„์„ ${vTitle} ${today}`; + const fileMarkdown = [ + `# ${title}`, + ``, + `- **์˜์ƒ URL**: ${videoUrl}`, + `- **๋ถ„์„ ์‹œ๊ฐ**: ${new Date().toISOString()}`, + `- **์ƒ์„ฑ**: Astra /youtube ยท Datacollect youtube insight`, + ``, + `## ๐Ÿ“œ ์ „์ฒด ์Šคํฌ๋ฆฝํŠธ (Full Script)`, + ``, + script, + ``, + `---`, + ``, + report, + ``, + ].join('\n'); + const savePath = (cfg.get('datacollectSavePath', '') || '').trim(); + const body: Record = { title, content: fileMarkdown }; + if (savePath) body.saveDir = savePath; + const saved = await bridgeFetch<{ success: boolean; path?: string }>( + '/api/wiki/save', + { method: 'POST', body: JSON.stringify(body) }, + { timeoutMs: 30_000 }, + ); + chunk(view, `๐Ÿ’พ **๊ฒฐ๊ณผ๋ฌผ ์ €์žฅ ์™„๋ฃŒ**: \`${saved?.path || '(๊ฒฝ๋กœ ๋ฏธํ™•์ธ)'}\`\n\n`); + } catch (e: any) { + chunk(view, `โš ๏ธ ๊ฒฐ๊ณผ๋ฌผ ์ €์žฅ ์‹คํŒจ: ${e?.message || String(e)}\n\n`); } } - - const transcript = data.plainTranscript || ''; - chunk(view, `\n### Transcript (${transcript.length.toLocaleString()}์ž, ์ฒ˜์Œ 4000์ž๋งŒ ๋ฏธ๋ฆฌ๋ณด๊ธฐ)\n\n`); - chunk(view, `\`\`\`\n${transcript.slice(0, 4000)}${transcript.length > 4000 ? '\n... (์ƒ๋žต)' : ''}\n\`\`\`\n`); - - chunk(view, `\n> ๐Ÿ’ก Hook/Structure/Production/CTR 4-๋ Œ์ฆˆ ๋ถ„์„์„ ์›ํ•˜๋ฉด ์œ„ transcript๋ฅผ ์ธ์šฉํ•ด Astra์— ์ถ”๊ฐ€ ์งˆ๋ฌธํ•˜์„ธ์š”.\n`); return true; } @@ -643,3 +890,196 @@ async function runBlog(keyword: string, view: Webview | undefined): Promise { + const cfg = vscode.workspace.getConfiguration('g1nation'); + + // 1) extract โ€” Bridge๊ฐ€ Playwright๋กœ main/article ๋ณธ๋ฌธ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœ. + chunk(view, `โณ ๋ณธ๋ฌธ ์ถ”์ถœ ์ค‘โ€ฆ`); + const t0 = Date.now(); + const heartbeat = setInterval(() => { + chunk(view, ` ยท${Math.round((Date.now() - t0) / 1000)}s`); + }, 4000); + const data = await bridgeFetch<{ success: boolean; url: string; title?: string; description?: string; lang?: string; headings?: string[]; text?: string; textLength?: number; truncated?: boolean }>( + '/api/web-extract', + { method: 'POST', body: JSON.stringify({ url }) }, + { timeoutMs: 3 * 60_000 }, + ).finally(() => clearInterval(heartbeat)); + chunk(view, `\nโœ… ๋ณธ๋ฌธ ์ถ”์ถœ (${Math.round((Date.now() - t0) / 1000)}s ยท ${(data.textLength ?? 0).toLocaleString()}์ž${data.truncated ? ', ์ผ๋ถ€ ์ž˜๋ฆผ' : ''})\n\n`); + + if (!data.text || data.text.trim().length < 50) { + chunk(view, `โš ๏ธ ์ถ”์ถœ๋œ ๋ณธ๋ฌธ์ด ๊ฑฐ์˜ ์—†์–ด ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. (JS ์ „์šฉ ๋ Œ๋”๋ง์ด๊ฑฐ๋‚˜ ์ฝ˜ํ…์ธ ๊ฐ€ ๋นˆ์•ฝํ•œ ํŽ˜์ด์ง€)\n`); + return false; + } + + // 2) synthesize โ€” P-Reinforce v3.0 ์œ„ํ‚ค ๋ฌธ์„œ๋กœ LLM ํ•ฉ์„ฑ. + const model = (cfg.get('defaultModel', '') || 'gemma4:e2b').trim(); + const wikiSystem = '๋‹น์‹ ์€ ์ง€์‹ ํ๋ ˆ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ์ œ๊ณต๋œ ์›น์‚ฌ์ดํŠธ ๋ณธ๋ฌธ์„ P-Reinforce v3.0 ๊ทœ๊ฒฉ์˜ ๊ณ ๋ฐ€๋„ ์œ„ํ‚ค ๋ฌธ์„œ๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ณธ๋ฌธ์— ์—†๋Š” ๋‚ด์šฉ์€ ์ ˆ๋Œ€ ์ง€์–ด๋‚ด์ง€ ์•Š์œผ๋ฉฐ, ๋ชจ๋“  ๋ฌธ์„œ๋Š” ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.'; + chunk(view, `๐Ÿงช P-Reinforce ์œ„ํ‚ค ํ•ฉ์„ฑ (๋ชจ๋ธ \`${model}\`)โ€ฆ`); + let report: string; + try { + const synthT0 = Date.now(); + report = await callLmSynthesis(buildWikifyPrompt(data, userContent), wikiSystem); + if (!report) throw new Error('LLM ์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.'); + // LLM์ด ์œ„ํ‚ค๋งํฌ [[ ]]์˜ ๋‹ซ๋Š” ๋Œ€๊ด„ํ˜ธ๋ฅผ ํ•˜๋‚˜ ๋น ๋œจ๋ฆฌ๋Š” ๊นจ์ง์„ ์ž๋™ ๊ต์ •. + report = report.replace(/\[\[([^\[\]]+?)\](?!\])/g, '[[$1]]'); + chunk(view, ` โœ“ (${Math.round((Date.now() - synthT0) / 1000)}s)\n\n`); + } catch (e: any) { + chunk(view, `\nโš ๏ธ ์œ„ํ‚ค ํ•ฉ์„ฑ ์‹คํŒจ: ${e?.message || String(e)}\n(LM ์„œ๋ฒ„๊ฐ€ ๋–  ์žˆ๋Š”์ง€, \`g1nation.ollamaUrl\` / \`defaultModel\` ์„ค์ •์„ ํ™•์ธํ•˜์„ธ์š”.)\n\n`); + return false; + } + chunk(view, report + '\n\n'); + + // 3) save โ€” /api/wiki/save (datacollectSavePath > WIKI_RAW_PATH). + // report ์ž์ฒด๊ฐ€ frontmatter ํฌํ•จ ์™„๊ฒฐ ์œ„ํ‚ค ๋ฌธ์„œ์ด๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ์ €์žฅํ•œ๋‹ค. + try { + const today = new Date().toISOString().slice(0, 10); + let host = url; + try { host = new URL(/^https?:\/\//i.test(url) ? url : `https://${url}`).host; } catch { /* keep raw url */ } + const title = `์œ„ํ‚ค ${(userContent.trim() || data.title || host)} ${today}`; + const savePath = (cfg.get('datacollectSavePath', '') || '').trim(); + const body: Record = { title, content: report }; + if (savePath) body.saveDir = savePath; + const saved = await bridgeFetch<{ success: boolean; path?: string }>( + '/api/wiki/save', + { method: 'POST', body: JSON.stringify(body) }, + { timeoutMs: 30_000 }, + ); + chunk(view, `๐Ÿ’พ ์œ„ํ‚ค ๋ฌธ์„œ ์ €์žฅ ์™„๋ฃŒ: \`${saved?.path || '(๊ฒฝ๋กœ ๋ฏธํ™•์ธ)'}\`\n`); + } catch (e: any) { + chunk(view, `โš ๏ธ ์œ„ํ‚ค ๋ฌธ์„œ ์ €์žฅ ์‹คํŒจ: ${e?.message || String(e)}\n`); + } + return true; +} + +async function runWikify(arg: string, view: Webview | undefined): Promise { + // ํ† ํฐ์„ URL๊ณผ ๋น„-URL๋กœ ๋ถ„๋ฅ˜. URL์ฒ˜๋Ÿผ ์ƒ๊ธด ํ† ํฐ์€ ๋ชจ๋‘ ์ฒ˜๋ฆฌ ๋Œ€์ƒ, ๋‚˜๋จธ์ง€๋Š” ๊ณตํ†ต ์ฃผ์ œ๋ช…. + // ์—ฌ๋Ÿฌ ๋งํฌ๋ฅผ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„ํ•ด ๋„ฃ์œผ๋ฉด 1๊ฐœ์”ฉ ์ˆœ์ฐจ์ ์œผ๋กœ ์œ„ํ‚คํ™”ํ•œ๋‹ค. + const isUrl = (t: string) => /^https?:\/\//i.test(t) || /^[a-z0-9-]+(\.[a-z0-9-]+)+/i.test(t); + const tokens = arg.trim().split(/\s+/).filter(Boolean); + const urls = tokens.filter(isUrl); + const userContent = tokens.filter((t) => !isUrl(t)).join(' '); + if (urls.length === 0) { + chunk(view, `์‚ฌ์šฉ๋ฒ•: \`/wikify [url2 url3 โ€ฆ] [์ฃผ์ œ๋ช…]\`\n์˜ˆ: \`/wikify https://example.com\`\n์—ฌ๋Ÿฌ ๋งํฌ๋ฅผ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„ํ•ด ํ•œ ๋ฒˆ์— ๋„ฃ์œผ๋ฉด 1๊ฐœ์”ฉ ์ˆœ์ฐจ ์œ„ํ‚คํ™”ํ•ฉ๋‹ˆ๋‹ค.\n`); + return true; + } + + // ๋‹จ์ผ ๋งํฌ โ€” ๊ทธ๋Œ€๋กœ ์ฒ˜๋ฆฌ. + if (urls.length === 1) { + chunk(view, `๐Ÿ“š **์œ„ํ‚คํ™”**: ${urls[0]}\n(๋ณธ๋ฌธ ์ถ”์ถœ โ†’ P-Reinforce v3.0 ์œ„ํ‚ค ๋ฌธ์„œ ํ•ฉ์„ฑ)\n\n`); + await wikifyOne(urls[0], userContent, view); + return true; + } + + // ๋‹ค์ค‘ ๋งํฌ โ€” 1๊ฐœ์”ฉ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ. ํ•œ ๊ฑด์ด ์‹คํŒจํ•ด๋„ ๋‚˜๋จธ์ง€๋Š” ๊ณ„์† ์ง„ํ–‰. + chunk(view, `๐Ÿ“š **์œ„ํ‚คํ™” ๋ฐฐ์น˜**: ์ด ${urls.length}๊ฐœ ๋งํฌ๋ฅผ ์ˆœ์ฐจ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.\n`); + const batchT0 = Date.now(); + let okCount = 0; + for (let i = 0; i < urls.length; i++) { + chunk(view, `\n---\n\n### [${i + 1}/${urls.length}] ${urls[i]}\n\n`); + try { + if (await wikifyOne(urls[i], userContent, view)) okCount++; + } catch (e: any) { + chunk(view, `โŒ [${i + 1}/${urls.length}] ์ฒ˜๋ฆฌ ์‹คํŒจ: ${e?.message || String(e)}\n`); + } + } + chunk(view, `\n---\n\n๐Ÿ **๋ฐฐ์น˜ ์™„๋ฃŒ**: ${okCount}/${urls.length}๊ฐœ ์„ฑ๊ณต (${Math.round((Date.now() - batchT0) / 1000)}s ์†Œ์š”)\n`); + return true; +} diff --git a/src/utils.ts b/src/utils.ts index b09234e..a420517 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -340,7 +340,7 @@ export function getSystemPrompt(): string { const now = new Date(); const dateTimeStr = now.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'long', hour: '2-digit', minute: '2-digit' }); const isoDate = now.toISOString().split('T')[0]; - const base = `${BASE_SYSTEM_PROMPT}\n\n[CURRENT DATE/TIME]\nToday: ${isoDate} (${dateTimeStr})\nUse this date as the absolute reference for any date-related calculations (e.g., "this week", "today", "yesterday").`; + const base = `${BASE_SYSTEM_PROMPT}\n\n[CURRENT DATE/TIME]\nToday: ${isoDate} (${dateTimeStr})\nUse this date as the absolute reference for any date-related calculations (e.g., "this week", "today", "yesterday").\n\n[์ถœ๋ ฅ ์œ„์ƒ ๊ทœ์น™ โ€” ๋ฐ˜๋“œ์‹œ ์ค€์ˆ˜]\n- ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•˜๊ณ , ํ•œ ๋‹จ์–ด ์•ˆ์— ํ•œ๊ธ€๊ณผ ์˜๋ฌธ ์•ŒํŒŒ๋ฒณ์„ ์„ž์ง€ ๋งˆ์‹œ์˜ค ("๊ฒฐently", "์ธorp" ๊ฐ™์€ ๊นจ์ง„ ํ•ฉ์„ฑ ํ‘œ๊ธฐ ์ ˆ๋Œ€ ๊ธˆ์ง€).\n- ์™ธ๋ž˜์–ดยท๊ธฐ์ˆ  ์šฉ์–ด๋Š” ์™„์ „ํ•œ ํ•œ๊ธ€ ํ‘œ๊ธฐ ๋˜๋Š” ์™„์ „ํ•œ ์˜๋ฌธ ๋‹จ์–ด ์ค‘ ํ•˜๋‚˜๋กœ ์ผ๊ด€๋˜๊ฒŒ ์“ฐ์‹œ์˜ค.\n- ๋‚ด๋ถ€ ๊ฒ€์ฆยท์ฒดํฌ ๋กœ๊ทธ(Consistency/Completeness/Accuracy ๋“ฑ) ๋ธ”๋ก์„ ์‚ฌ์šฉ์ž ์ถœ๋ ฅ์— ํฌํ•จํ•˜์ง€ ๋งˆ์‹œ์˜ค.`; // Self-Reflector Phase A โ€” ์‚ฌ์šฉ์ž ์„ค์ •์ด ์ผœ์ ธ ์žˆ์œผ๋ฉด ๋‹ต๋ณ€ ๋์— ์ž๊ธฐ๊ฒ€์ฆ // ๋ธ”๋ก์„ ๊ฐ•์ œํ•˜๋Š” ๋ฃฐ์„ prepend. require๋กœ ๋™์  ๋กœ๋“œํ•ด ์ˆœํ™˜ import ํšŒํ”ผ. try {