@tienne/gestalt 0.102.0 → 0.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.ko.md +2 -1
  2. package/README.md +2 -1
  3. package/dist/package.json +1 -1
  4. package/dist/plugin/role-agents/video-summarizer/AGENT.md +177 -113
  5. package/dist/plugin/skills/_shared/proactive-routing.md +2 -1
  6. package/dist/plugin/skills/research/references/design.md +1 -1
  7. package/dist/plugin/skills/video-digest/SKILL.md +408 -0
  8. package/dist/plugin/skills/video-digest/templates/background.md +15 -0
  9. package/dist/plugin/skills/video-digest/templates/blog.md +16 -0
  10. package/dist/plugin/skills/video-digest/templates/comments.md +19 -0
  11. package/dist/plugin/skills/video-digest/templates/counterpoints.md +15 -0
  12. package/dist/plugin/skills/video-digest/templates/flashcards.md +15 -0
  13. package/dist/plugin/skills/video-digest/templates/interview.md +15 -0
  14. package/dist/plugin/skills/video-digest/templates/meeting.md +15 -0
  15. package/dist/plugin/skills/video-digest/templates/mindmap.md +16 -0
  16. package/dist/plugin/skills/video-digest/templates/quiz.md +16 -0
  17. package/dist/plugin/skills/video-digest/templates/quotes.md +16 -0
  18. package/dist/plugin/skills/video-digest/templates/resources.md +14 -0
  19. package/dist/plugin/skills/video-digest/templates/timestamps.md +14 -0
  20. package/dist/src/cli/commands/digest.d.ts +57 -0
  21. package/dist/src/cli/commands/digest.d.ts.map +1 -0
  22. package/dist/src/cli/commands/digest.js +368 -0
  23. package/dist/src/cli/commands/digest.js.map +1 -0
  24. package/dist/src/cli/index.d.ts.map +1 -1
  25. package/dist/src/cli/index.js +77 -0
  26. package/dist/src/cli/index.js.map +1 -1
  27. package/dist/src/digest/blocks.d.ts +1853 -0
  28. package/dist/src/digest/blocks.d.ts.map +1 -0
  29. package/dist/src/digest/blocks.js +148 -0
  30. package/dist/src/digest/blocks.js.map +1 -0
  31. package/dist/src/digest/captions.d.ts +37 -0
  32. package/dist/src/digest/captions.d.ts.map +1 -0
  33. package/dist/src/digest/captions.js +138 -0
  34. package/dist/src/digest/captions.js.map +1 -0
  35. package/dist/src/digest/comments.d.ts +113 -0
  36. package/dist/src/digest/comments.d.ts.map +1 -0
  37. package/dist/src/digest/comments.js +153 -0
  38. package/dist/src/digest/comments.js.map +1 -0
  39. package/dist/src/digest/format.d.ts +9 -0
  40. package/dist/src/digest/format.d.ts.map +1 -0
  41. package/dist/src/digest/format.js +26 -0
  42. package/dist/src/digest/format.js.map +1 -0
  43. package/dist/src/digest/frames.d.ts +26 -0
  44. package/dist/src/digest/frames.d.ts.map +1 -0
  45. package/dist/src/digest/frames.js +109 -0
  46. package/dist/src/digest/frames.js.map +1 -0
  47. package/dist/src/digest/index.d.ts +25 -0
  48. package/dist/src/digest/index.d.ts.map +1 -0
  49. package/dist/src/digest/index.js +25 -0
  50. package/dist/src/digest/index.js.map +1 -0
  51. package/dist/src/digest/ingest.d.ts +65 -0
  52. package/dist/src/digest/ingest.d.ts.map +1 -0
  53. package/dist/src/digest/ingest.js +303 -0
  54. package/dist/src/digest/ingest.js.map +1 -0
  55. package/dist/src/digest/lint-extension.d.ts +57 -0
  56. package/dist/src/digest/lint-extension.d.ts.map +1 -0
  57. package/dist/src/digest/lint-extension.js +395 -0
  58. package/dist/src/digest/lint-extension.js.map +1 -0
  59. package/dist/src/digest/lint.d.ts +40 -0
  60. package/dist/src/digest/lint.d.ts.map +1 -0
  61. package/dist/src/digest/lint.js +517 -0
  62. package/dist/src/digest/lint.js.map +1 -0
  63. package/dist/src/digest/merge.d.ts +40 -0
  64. package/dist/src/digest/merge.d.ts.map +1 -0
  65. package/dist/src/digest/merge.js +104 -0
  66. package/dist/src/digest/merge.js.map +1 -0
  67. package/dist/src/digest/ocr.d.ts +96 -0
  68. package/dist/src/digest/ocr.d.ts.map +1 -0
  69. package/dist/src/digest/ocr.js +168 -0
  70. package/dist/src/digest/ocr.js.map +1 -0
  71. package/dist/src/digest/prose.d.ts +43 -0
  72. package/dist/src/digest/prose.d.ts.map +1 -0
  73. package/dist/src/digest/prose.js +112 -0
  74. package/dist/src/digest/prose.js.map +1 -0
  75. package/dist/src/digest/render-blocks.d.ts +32 -0
  76. package/dist/src/digest/render-blocks.d.ts.map +1 -0
  77. package/dist/src/digest/render-blocks.js +292 -0
  78. package/dist/src/digest/render-blocks.js.map +1 -0
  79. package/dist/src/digest/render-html.d.ts +22 -0
  80. package/dist/src/digest/render-html.d.ts.map +1 -0
  81. package/dist/src/digest/render-html.js +466 -0
  82. package/dist/src/digest/render-html.js.map +1 -0
  83. package/dist/src/digest/render-md.d.ts +18 -0
  84. package/dist/src/digest/render-md.d.ts.map +1 -0
  85. package/dist/src/digest/render-md.js +113 -0
  86. package/dist/src/digest/render-md.js.map +1 -0
  87. package/dist/src/digest/render.d.ts +47 -0
  88. package/dist/src/digest/render.d.ts.map +1 -0
  89. package/dist/src/digest/render.js +123 -0
  90. package/dist/src/digest/render.js.map +1 -0
  91. package/dist/src/digest/reports.d.ts +26 -0
  92. package/dist/src/digest/reports.d.ts.map +1 -0
  93. package/dist/src/digest/reports.js +70 -0
  94. package/dist/src/digest/reports.js.map +1 -0
  95. package/dist/src/digest/schema-base.d.ts +31 -0
  96. package/dist/src/digest/schema-base.d.ts.map +1 -0
  97. package/dist/src/digest/schema-base.js +19 -0
  98. package/dist/src/digest/schema-base.js.map +1 -0
  99. package/dist/src/digest/schema.d.ts +5713 -0
  100. package/dist/src/digest/schema.d.ts.map +1 -0
  101. package/dist/src/digest/schema.js +229 -0
  102. package/dist/src/digest/schema.js.map +1 -0
  103. package/dist/src/digest/server.d.ts +36 -0
  104. package/dist/src/digest/server.d.ts.map +1 -0
  105. package/dist/src/digest/server.js +278 -0
  106. package/dist/src/digest/server.js.map +1 -0
  107. package/dist/src/digest/slice.d.ts +20 -0
  108. package/dist/src/digest/slice.d.ts.map +1 -0
  109. package/dist/src/digest/slice.js +48 -0
  110. package/dist/src/digest/slice.js.map +1 -0
  111. package/dist/src/digest/store.d.ts +67 -0
  112. package/dist/src/digest/store.d.ts.map +1 -0
  113. package/dist/src/digest/store.js +152 -0
  114. package/dist/src/digest/store.js.map +1 -0
  115. package/dist/src/digest/svg.d.ts +44 -0
  116. package/dist/src/digest/svg.d.ts.map +1 -0
  117. package/dist/src/digest/svg.js +264 -0
  118. package/dist/src/digest/svg.js.map +1 -0
  119. package/dist/src/digest/templates.d.ts +38 -0
  120. package/dist/src/digest/templates.d.ts.map +1 -0
  121. package/dist/src/digest/templates.js +125 -0
  122. package/dist/src/digest/templates.js.map +1 -0
  123. package/dist/src/digest/translate.d.ts +46 -0
  124. package/dist/src/digest/translate.d.ts.map +1 -0
  125. package/dist/src/digest/translate.js +50 -0
  126. package/dist/src/digest/translate.js.map +1 -0
  127. package/dist/src/digest/viewer-js.d.ts +4 -0
  128. package/dist/src/digest/viewer-js.d.ts.map +1 -0
  129. package/dist/src/digest/viewer-js.js +710 -0
  130. package/dist/src/digest/viewer-js.js.map +1 -0
  131. package/dist/src/digest/viewer-logic.d.ts +46 -0
  132. package/dist/src/digest/viewer-logic.d.ts.map +1 -0
  133. package/dist/src/digest/viewer-logic.js +89 -0
  134. package/dist/src/digest/viewer-logic.js.map +1 -0
  135. package/dist/src/digest/ytdlp.d.ts +34 -0
  136. package/dist/src/digest/ytdlp.d.ts.map +1 -0
  137. package/dist/src/digest/ytdlp.js +64 -0
  138. package/dist/src/digest/ytdlp.js.map +1 -0
  139. package/dist/src/mcp/schemas.d.ts +48 -0
  140. package/dist/src/mcp/schemas.d.ts.map +1 -1
  141. package/dist/src/mcp/schemas.js +25 -0
  142. package/dist/src/mcp/schemas.js.map +1 -1
  143. package/dist/src/mcp/server.d.ts.map +1 -1
  144. package/dist/src/mcp/server.js +7 -1
  145. package/dist/src/mcp/server.js.map +1 -1
  146. package/dist/src/mcp/tools/digest-passthrough.d.ts +3 -0
  147. package/dist/src/mcp/tools/digest-passthrough.d.ts.map +1 -0
  148. package/dist/src/mcp/tools/digest-passthrough.js +148 -0
  149. package/dist/src/mcp/tools/digest-passthrough.js.map +1 -0
  150. package/package.json +1 -1
  151. package/plugin/.codex-plugin/plugin.json +1 -1
  152. package/plugin/.mcp.json +1 -1
  153. package/plugin/mcp.json +1 -1
  154. package/plugin/role-agents/video-summarizer/AGENT.md +177 -113
  155. package/plugin/skills/_shared/proactive-routing.md +2 -1
  156. package/plugin/skills/research/references/design.md +1 -1
  157. package/plugin/skills/video-digest/SKILL.md +408 -0
  158. package/plugin/skills/video-digest/templates/background.md +15 -0
  159. package/plugin/skills/video-digest/templates/blog.md +16 -0
  160. package/plugin/skills/video-digest/templates/comments.md +19 -0
  161. package/plugin/skills/video-digest/templates/counterpoints.md +15 -0
  162. package/plugin/skills/video-digest/templates/flashcards.md +15 -0
  163. package/plugin/skills/video-digest/templates/interview.md +15 -0
  164. package/plugin/skills/video-digest/templates/meeting.md +15 -0
  165. package/plugin/skills/video-digest/templates/mindmap.md +16 -0
  166. package/plugin/skills/video-digest/templates/quiz.md +16 -0
  167. package/plugin/skills/video-digest/templates/quotes.md +16 -0
  168. package/plugin/skills/video-digest/templates/resources.md +14 -0
  169. package/plugin/skills/video-digest/templates/timestamps.md +14 -0
@@ -0,0 +1,104 @@
1
+ import { extensionKey, extensionSchema } from './blocks.js';
2
+ import { commentsMap } from './comments.js';
3
+ import { lintDigest, lintErrors } from './lint.js';
4
+ import { reportKey, upsertReport } from './reports.js';
5
+ import { DIGEST_SCHEMA_VERSION, parseDigest, reportSchema } from './schema.js';
6
+ import { DigestStore } from './store.js';
7
+ /**
8
+ * 새로 만든 리포트 한 벌을 digest.json에 합친다. 같은 키가 있으면 바꿔 끼운다.
9
+ * 합친 결과가 lint 오류를 내면 저장하지 않는다. 원래 digest.json을 깨뜨리지 않으려는 것이다
10
+ */
11
+ export function mergeReport(repoRoot, slug, input) {
12
+ const store = new DigestStore(repoRoot);
13
+ const t = store.readTranscript(slug);
14
+ if (!t.ok)
15
+ throw new Error(t.message);
16
+ const parsedReport = reportSchema.safeParse(input);
17
+ if (!parsedReport.success) {
18
+ const issues = parsedReport.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`);
19
+ throw new Error(`리포트 형식 오류: ${issues.join('; ')}`);
20
+ }
21
+ const report = parsedReport.data;
22
+ const raw = store.readDigestFile(slug);
23
+ let base = null;
24
+ if (raw.kind === 'ok') {
25
+ const parsed = parseDigest(raw.value);
26
+ if (!parsed.ok)
27
+ throw parsed.error;
28
+ base = parsed.value;
29
+ }
30
+ else if (raw.kind !== 'missing') {
31
+ throw new Error(raw.message);
32
+ }
33
+ const { digest, replaced } = base
34
+ ? upsertReport(base, report)
35
+ : {
36
+ digest: { schemaVersion: DIGEST_SCHEMA_VERSION, slug, reports: [report], extensions: [] },
37
+ replaced: false,
38
+ };
39
+ const merged = {
40
+ ...digest,
41
+ schemaVersion: DIGEST_SCHEMA_VERSION,
42
+ generatedAt: new Date().toISOString(),
43
+ };
44
+ const issues = lintDigest(merged, t.transcript);
45
+ const saved = lintErrors(issues).length === 0;
46
+ const digestPath = saved ? store.saveDigest(merged) : store.digestPath(slug);
47
+ const key = reportKey(report.options);
48
+ const index = merged.reports.findIndex((r) => reportKey(r.options) === key);
49
+ return { slug, key, index, replaced, saved, digestPath, issues };
50
+ }
51
+ /** 같은 템플릿과 언어의 확장이 있으면 그 자리에 바꿔 끼우고 없으면 뒤에 붙인다 */
52
+ export function upsertExtension(digest, ext) {
53
+ const key = extensionKey(ext);
54
+ const at = digest.extensions.findIndex((x) => extensionKey(x) === key);
55
+ const extensions = [...digest.extensions];
56
+ if (at >= 0)
57
+ extensions[at] = ext;
58
+ else
59
+ extensions.push(ext);
60
+ return { digest: { ...digest, extensions }, replaced: at >= 0 };
61
+ }
62
+ /**
63
+ * 템플릿으로 만든 확장 리포트 한 벌을 digest.json에 합친다.
64
+ * 확장은 요약 리포트에 기대지 않지만 뷰어가 요약 탭을 먼저 열어서 digest.json이 있어야 한다
65
+ */
66
+ export function mergeExtension(repoRoot, slug, input, opts = {}) {
67
+ const store = new DigestStore(repoRoot);
68
+ const t = store.readTranscript(slug);
69
+ if (!t.ok)
70
+ throw new Error(t.message);
71
+ const parsedExt = extensionSchema.safeParse(input);
72
+ if (!parsedExt.success) {
73
+ const issues = parsedExt.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`);
74
+ throw new Error(`확장 리포트 형식 오류: ${issues.join('; ')}`);
75
+ }
76
+ const ext = parsedExt.data;
77
+ const raw = store.readDigestFile(slug);
78
+ if (raw.kind === 'missing') {
79
+ throw new Error(`digest.json이 없어요. 요약 리포트를 먼저 합쳐 주세요: ${slug}`);
80
+ }
81
+ if (raw.kind !== 'ok')
82
+ throw new Error(raw.message);
83
+ const parsed = parseDigest(raw.value);
84
+ if (!parsed.ok)
85
+ throw parsed.error;
86
+ const { digest, replaced } = upsertExtension(parsed.value, ext);
87
+ const merged = {
88
+ ...digest,
89
+ schemaVersion: DIGEST_SCHEMA_VERSION,
90
+ generatedAt: new Date().toISOString(),
91
+ };
92
+ // 댓글 예시는 받아 둔 댓글과 맞아야 합친다. 파일이 없으면 null이라 댓글 블록이 오류로 걸린다
93
+ const issues = lintDigest(merged, t.transcript, {
94
+ ...opts,
95
+ comments: opts.comments !== undefined ? opts.comments : commentsMap(store, slug),
96
+ strictVerbatim: true,
97
+ });
98
+ const saved = lintErrors(issues).length === 0;
99
+ const digestPath = saved ? store.saveDigest(merged) : store.digestPath(slug);
100
+ const key = extensionKey(ext);
101
+ const index = merged.extensions.findIndex((x) => extensionKey(x) === key);
102
+ return { slug, key, index, replaced, saved, digestPath, issues };
103
+ }
104
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../src/digest/merge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { z } from 'zod';
2
+ import { type RunCommand } from './ytdlp.js';
3
+ export declare const OCR_FILE = "ocr.json";
4
+ export declare const DEFAULT_OCR_INTERVAL_SEC = 10;
5
+ /** 긴 영상에 짧은 간격을 주면 PNG가 수만 장 쌓여서 이보다 많으면 간격을 넓힌다 */
6
+ export declare const MAX_OCR_FRAMES = 600;
7
+ /** tesseract 언어 이름. kor+eng처럼 +로 잇는다 */
8
+ export declare const TESSERACT_LANG_PATTERN: RegExp;
9
+ /**
10
+ * 화면 글자 한 벌. 영상 화면에서 읽은 글자라 자막만큼 믿지 않는다.
11
+ * 요약 에이전트가 보조 자료로만 읽고 렌더러는 이 파일을 쓰지 않는다
12
+ */
13
+ export declare const ocrFileSchema: z.ZodObject<{
14
+ engine: z.ZodLiteral<"tesseract">;
15
+ lang: z.ZodString;
16
+ intervalSec: z.ZodNumber;
17
+ /** 프레임 상한 때문에 간격을 넓혔으면 처음 요청한 간격 */
18
+ requestedIntervalSec: z.ZodOptional<z.ZodNumber>;
19
+ items: z.ZodArray<z.ZodObject<{
20
+ sec: z.ZodNumber;
21
+ i: z.ZodNumber;
22
+ text: z.ZodString;
23
+ truncated: z.ZodOptional<z.ZodBoolean>;
24
+ }, "strip", z.ZodTypeAny, {
25
+ text: string;
26
+ i: number;
27
+ sec: number;
28
+ truncated?: boolean | undefined;
29
+ }, {
30
+ text: string;
31
+ i: number;
32
+ sec: number;
33
+ truncated?: boolean | undefined;
34
+ }>, "many">;
35
+ }, "strip", z.ZodTypeAny, {
36
+ items: {
37
+ text: string;
38
+ i: number;
39
+ sec: number;
40
+ truncated?: boolean | undefined;
41
+ }[];
42
+ engine: "tesseract";
43
+ lang: string;
44
+ intervalSec: number;
45
+ requestedIntervalSec?: number | undefined;
46
+ }, {
47
+ items: {
48
+ text: string;
49
+ i: number;
50
+ sec: number;
51
+ truncated?: boolean | undefined;
52
+ }[];
53
+ engine: "tesseract";
54
+ lang: string;
55
+ intervalSec: number;
56
+ requestedIntervalSec?: number | undefined;
57
+ }>;
58
+ export type OcrFile = z.infer<typeof ocrFileSchema>;
59
+ export interface OcrOptions {
60
+ repoRoot: string;
61
+ intervalSec?: number;
62
+ /** tesseract 언어. 비우면 스크립트 언어에서 고른다 */
63
+ lang?: string;
64
+ run?: RunCommand;
65
+ }
66
+ export interface OcrResult {
67
+ path: string;
68
+ lang: string;
69
+ intervalSec: number;
70
+ /** 프레임 상한 때문에 간격을 넓혔으면 처음 요청한 간격 */
71
+ requestedIntervalSec?: number;
72
+ frames: number;
73
+ items: number;
74
+ /** 글자 상한에서 잘린 항목 수 */
75
+ truncated: number;
76
+ }
77
+ /** 화면 글자에는 코드와 제품 이름 같은 영문이 섞여서 영어가 아닌 언어에도 eng를 붙인다 */
78
+ export declare function tesseractLang(lang: string): string;
79
+ /** 이보다 낮은 신뢰도의 단어는 아이콘이나 지도 무늬를 글자로 읽은 경우가 대부분이다 */
80
+ export declare const MIN_WORD_CONFIDENCE = 70;
81
+ /**
82
+ * tesseract tsv 출력을 줄 단위 글자로 바꾼다. 신뢰도가 낮은 단어와 글자 하나짜리 단어는 버리고
83
+ * 글자나 숫자가 두 개 안 남은 줄도 버린다
84
+ */
85
+ export declare function textFromTsv(tsv: string, minConfidence?: number): string;
86
+ /** 상한을 넘는 글자는 줄 단위로 자르고 끝에 …를 붙인다. 줄 하나가 상한보다 길면 그 줄을 자른다 */
87
+ export declare function clipOcrText(text: string, max?: number): {
88
+ text: string;
89
+ truncated: boolean;
90
+ };
91
+ /**
92
+ * intervalSec마다 프레임을 뽑아 화면 글자를 읽고 ocr.json에 둔다.
93
+ * 슬라이드처럼 한동안 그대로인 화면은 앞 항목과 글자가 같아서 한 번만 남긴다
94
+ */
95
+ export declare function runOcr(slug: string, opts: OcrOptions): Promise<OcrResult>;
96
+ //# sourceMappingURL=ocr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocr.d.ts","sourceRoot":"","sources":["../../../src/digest/ocr.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEnF,eAAO,MAAM,QAAQ,aAAa,CAAC;AACnC,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,oDAAoD;AACpD,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,wCAAwC;AACxC,eAAO,MAAM,sBAAsB,QAA8C,CAAC;AAElF;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;IAIxB,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWpC,CAAC;AAEH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AASD,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,oDAAoD;AACpD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,SAAsB,GAAG,MAAM,CAcpF;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAU9F;AAID;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAwF/E"}
@@ -0,0 +1,168 @@
1
+ import { mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { ensureVideo } from './frames.js';
6
+ import { DigestStore } from './store.js';
7
+ import { findSegmentIndex } from './viewer-logic.js';
8
+ import { CommandError, runCommand, timeoutFor } from './ytdlp.js';
9
+ export const OCR_FILE = 'ocr.json';
10
+ export const DEFAULT_OCR_INTERVAL_SEC = 10;
11
+ const MAX_TEXT = 2000;
12
+ /** 긴 영상에 짧은 간격을 주면 PNG가 수만 장 쌓여서 이보다 많으면 간격을 넓힌다 */
13
+ export const MAX_OCR_FRAMES = 600;
14
+ /** tesseract 언어 이름. kor+eng처럼 +로 잇는다 */
15
+ export const TESSERACT_LANG_PATTERN = /^[a-z][a-z_]{1,15}(\+[a-z][a-z_]{1,15})*$/;
16
+ /**
17
+ * 화면 글자 한 벌. 영상 화면에서 읽은 글자라 자막만큼 믿지 않는다.
18
+ * 요약 에이전트가 보조 자료로만 읽고 렌더러는 이 파일을 쓰지 않는다
19
+ */
20
+ export const ocrFileSchema = z.object({
21
+ engine: z.literal('tesseract'),
22
+ lang: z.string().regex(TESSERACT_LANG_PATTERN),
23
+ intervalSec: z.number().positive(),
24
+ /** 프레임 상한 때문에 간격을 넓혔으면 처음 요청한 간격 */
25
+ requestedIntervalSec: z.number().positive().optional(),
26
+ items: z.array(z.object({
27
+ sec: z.number().nonnegative(),
28
+ i: z.number().int().nonnegative(),
29
+ // 글자 상한에서 잘렸으면 마지막 줄 끝에 …가 붙고 truncated가 true다
30
+ text: z.string().min(1).max(MAX_TEXT),
31
+ truncated: z.boolean().optional(),
32
+ })),
33
+ });
34
+ const TESSERACT_BY_LANG = {
35
+ ko: 'kor+eng',
36
+ ja: 'jpn+eng',
37
+ zh: 'chi_sim+eng',
38
+ en: 'eng',
39
+ };
40
+ /** 화면 글자에는 코드와 제품 이름 같은 영문이 섞여서 영어가 아닌 언어에도 eng를 붙인다 */
41
+ export function tesseractLang(lang) {
42
+ return TESSERACT_BY_LANG[lang.toLowerCase().split(/[-_]/)[0]] ?? 'eng';
43
+ }
44
+ /** 이보다 낮은 신뢰도의 단어는 아이콘이나 지도 무늬를 글자로 읽은 경우가 대부분이다 */
45
+ export const MIN_WORD_CONFIDENCE = 70;
46
+ /**
47
+ * tesseract tsv 출력을 줄 단위 글자로 바꾼다. 신뢰도가 낮은 단어와 글자 하나짜리 단어는 버리고
48
+ * 글자나 숫자가 두 개 안 남은 줄도 버린다
49
+ */
50
+ export function textFromTsv(tsv, minConfidence = MIN_WORD_CONFIDENCE) {
51
+ const lines = new Map();
52
+ for (const row of tsv.split(/\r?\n/).slice(1)) {
53
+ const cols = row.split('\t');
54
+ if (cols.length < 12)
55
+ continue;
56
+ const word = cols.slice(11).join(' ').trim();
57
+ if (Number(cols[10]) < minConfidence)
58
+ continue;
59
+ if ((word.match(/[\p{L}\p{N}]/gu) ?? []).length < 2)
60
+ continue;
61
+ const key = `${cols[2]}-${cols[3]}-${cols[4]}`;
62
+ const words = lines.get(key) ?? [];
63
+ words.push(word);
64
+ lines.set(key, words);
65
+ }
66
+ return [...lines.values()].map((w) => w.join(' ')).join('\n');
67
+ }
68
+ /** 상한을 넘는 글자는 줄 단위로 자르고 끝에 …를 붙인다. 줄 하나가 상한보다 길면 그 줄을 자른다 */
69
+ export function clipOcrText(text, max = MAX_TEXT) {
70
+ if (text.length <= max)
71
+ return { text, truncated: false };
72
+ let out = '';
73
+ for (const line of text.split('\n')) {
74
+ const next = out ? `${out}\n${line}` : line;
75
+ if (next.length > max - 1)
76
+ break;
77
+ out = next;
78
+ }
79
+ if (!out)
80
+ out = text.slice(0, max - 1);
81
+ return { text: `${out}…`, truncated: true };
82
+ }
83
+ const FRAME_FILE = /^f(\d{5})\.png$/;
84
+ /**
85
+ * intervalSec마다 프레임을 뽑아 화면 글자를 읽고 ocr.json에 둔다.
86
+ * 슬라이드처럼 한동안 그대로인 화면은 앞 항목과 글자가 같아서 한 번만 남긴다
87
+ */
88
+ export async function runOcr(slug, opts) {
89
+ const run = opts.run ?? runCommand;
90
+ let intervalSec = opts.intervalSec ?? DEFAULT_OCR_INTERVAL_SEC;
91
+ if (!Number.isFinite(intervalSec) || intervalSec < 1 || intervalSec > 600) {
92
+ throw new Error(`OCR 간격은 1~600초여야 해요: ${intervalSec}`);
93
+ }
94
+ const requestedIntervalSec = intervalSec;
95
+ const store = new DigestStore(opts.repoRoot);
96
+ const t = store.readTranscript(slug);
97
+ if (!t.ok)
98
+ throw new Error(t.message);
99
+ const transcript = t.transcript;
100
+ intervalSec = Math.max(intervalSec, Math.ceil(transcript.meta.durationSec / MAX_OCR_FRAMES));
101
+ const widened = intervalSec !== requestedIntervalSec;
102
+ const lang = opts.lang ?? tesseractLang(transcript.meta.lang);
103
+ if (!TESSERACT_LANG_PATTERN.test(lang)) {
104
+ throw new Error(`tesseract 언어는 kor+eng 꼴이어야 해요: ${lang}`);
105
+ }
106
+ const cwd = mkdtempSync(join(tmpdir(), 'gestalt-digest-ocr-'));
107
+ try {
108
+ const video = await ensureVideo(run, transcript, store.videoCacheDir(slug), cwd);
109
+ await run('ffmpeg', [
110
+ ...['-v', 'error', '-y', '-i', video],
111
+ // 작은 글자는 tesseract가 못 읽어서 1280보다 좁은 화면은 두 배로 키운다
112
+ ...['-vf', `fps=1/${intervalSec},scale='if(lt(iw,1280),iw*2,iw)':-2`],
113
+ join(cwd, 'f%05d.png'),
114
+ ], { cwd, timeoutMs: timeoutFor(transcript.meta.durationSec, 0.2) });
115
+ const frames = readdirSync(cwd)
116
+ .filter((f) => FRAME_FILE.test(f))
117
+ .sort();
118
+ const starts = transcript.segments.map((s) => s.startSec);
119
+ const items = [];
120
+ let prev = '';
121
+ let truncated = 0;
122
+ for (const f of frames) {
123
+ const n = Number(FRAME_FILE.exec(f)[1]);
124
+ const sec = (n - 1) * intervalSec;
125
+ let raw;
126
+ try {
127
+ // 화면 녹화는 글자가 흩어져 있어서 문단 분할(기본 psm 3)로는 거의 못 읽는다
128
+ ({ stdout: raw } = await run('tesseract', [join(cwd, f), 'stdout', '-l', lang, '--psm', '11', 'tsv'], { cwd, timeoutMs: 60_000 }));
129
+ }
130
+ catch (e) {
131
+ if (e instanceof CommandError && e.message.includes('찾지 못했어요')) {
132
+ throw new Error('tesseract를 찾지 못했어요. brew install tesseract tesseract-lang으로 설치해 주세요.', { cause: e });
133
+ }
134
+ throw e;
135
+ }
136
+ const full = textFromTsv(raw);
137
+ if (!full || full === prev)
138
+ continue;
139
+ prev = full;
140
+ const clip = clipOcrText(full);
141
+ if (clip.truncated)
142
+ truncated += 1;
143
+ items.push({
144
+ sec,
145
+ i: Math.max(0, findSegmentIndex(starts, sec)),
146
+ text: clip.text,
147
+ ...(clip.truncated ? { truncated: true } : {}),
148
+ });
149
+ }
150
+ const widenedFrom = widened ? { requestedIntervalSec } : {};
151
+ const out = { engine: 'tesseract', lang, intervalSec, ...widenedFrom, items };
152
+ const path = join(store.dir(slug), OCR_FILE);
153
+ writeFileSync(path, JSON.stringify(out, null, 2) + '\n');
154
+ return {
155
+ path,
156
+ lang,
157
+ intervalSec,
158
+ ...widenedFrom,
159
+ frames: frames.length,
160
+ items: items.length,
161
+ truncated,
162
+ };
163
+ }
164
+ finally {
165
+ rmSync(cwd, { recursive: true, force: true });
166
+ }
167
+ }
168
+ //# sourceMappingURL=ocr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocr.js","sourceRoot":"","sources":["../../../src/digest/ocr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { type Digest } from './schema.js';
2
+ export interface ProseItem {
3
+ path: string;
4
+ text: string;
5
+ /** 원문 sha256 앞 12자. apply가 extract 이후 원문이 바뀌었는지 이걸로 본다 */
6
+ hash: string;
7
+ }
8
+ export type ProseWarningCode = 'UNKNOWN_PATH' | 'STALE' | 'MARKER_MISMATCH' | 'EMPTY' | 'DUPLICATE_PATH';
9
+ export interface ProseWarning {
10
+ path: string;
11
+ code: ProseWarningCode;
12
+ missing?: string[];
13
+ added?: string[];
14
+ }
15
+ export interface ProseEdit {
16
+ path: string;
17
+ text: string;
18
+ hash: string;
19
+ }
20
+ export interface ProseApplyResult {
21
+ digest: Digest;
22
+ applied: number;
23
+ unchanged: number;
24
+ warnings: ProseWarning[];
25
+ }
26
+ export declare function proseHash(text: string): string;
27
+ /** 윤문할 문장. path는 lint가 쓰는 것과 같아서 경고와 짝을 맞춰 볼 수 있다 */
28
+ export declare function extractProse(digest: Digest): ProseItem[];
29
+ /**
30
+ * 윤문 전후로 그대로 남아야 하는 표식. 키워드는 id만 본다. 표시 글자는 문장에 맞게 바뀌어도 된다.
31
+ * 키워드 마커를 먼저 지워야 id 속 숫자가 수치로 한 번 더 세어지지 않는다.
32
+ */
33
+ export declare function extractMarkers(text: string): string[];
34
+ export declare function diffMarkers(before: string, after: string): {
35
+ missing: string[];
36
+ added: string[];
37
+ } | null;
38
+ /**
39
+ * 윤문된 문장을 path로 되돌려 넣는다. 검사에 걸린 항목은 원문을 두고 경고만 남긴다.
40
+ * 넘겨받은 digest는 건드리지 않고 복제본을 고쳐 돌려준다.
41
+ */
42
+ export declare function applyProse(input: Digest, edits: ProseEdit[]): ProseApplyResult;
43
+ //# sourceMappingURL=prose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prose.d.ts","sourceRoot":"","sources":["../../../src/digest/prose.ts"],"names":[],"mappings":"AAEA,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,OAAO,GACP,iBAAiB,GACjB,OAAO,GACP,gBAAgB,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAKD,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CAIxD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBrD;AAQD,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAQ/C;AAUD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,gBAAgB,CA4C9E"}
@@ -0,0 +1,112 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { collectTextFields } from './lint.js';
3
+ import { boldPattern, keywordPattern } from './schema.js';
4
+ export function proseHash(text) {
5
+ return createHash('sha256').update(text).digest('hex').slice(0, 12);
6
+ }
7
+ /** 제목은 목차와 미니맵에 그대로 쓰이는 이름이라 윤문 대상에서 뺀다 */
8
+ const isTitle = (path) => path.endsWith('.title');
9
+ /** 윤문할 문장. path는 lint가 쓰는 것과 같아서 경고와 짝을 맞춰 볼 수 있다 */
10
+ export function extractProse(digest) {
11
+ return collectTextFields(digest)
12
+ .filter((f) => !isTitle(f.path) && f.text.trim())
13
+ .map((f) => ({ path: f.path, text: f.text, hash: proseHash(f.text) }));
14
+ }
15
+ /**
16
+ * 윤문 전후로 그대로 남아야 하는 표식. 키워드는 id만 본다. 표시 글자는 문장에 맞게 바뀌어도 된다.
17
+ * 키워드 마커를 먼저 지워야 id 속 숫자가 수치로 한 번 더 세어지지 않는다.
18
+ */
19
+ export function extractMarkers(text) {
20
+ const markers = [];
21
+ let rest = text.replace(keywordPattern(), (_m, id, label) => {
22
+ markers.push(`키워드:${id}`);
23
+ return ` ${label} `;
24
+ });
25
+ rest = rest.replace(boldPattern(), (_m, inner) => {
26
+ markers.push('굵게');
27
+ return ` ${inner} `;
28
+ });
29
+ rest = rest.replace(/https?:\/\/[^\s)]+/g, (m) => {
30
+ markers.push(`링크:${m}`);
31
+ return ' ';
32
+ });
33
+ for (const m of rest.match(/\d(?:[\d,]*\d)?(?:\.\d+)?%?/g) ?? [])
34
+ markers.push(`수치:${m}`);
35
+ return markers;
36
+ }
37
+ function countOf(list) {
38
+ const counts = new Map();
39
+ for (const m of list)
40
+ counts.set(m, (counts.get(m) ?? 0) + 1);
41
+ return counts;
42
+ }
43
+ export function diffMarkers(before, after) {
44
+ const a = countOf(extractMarkers(before));
45
+ const b = countOf(extractMarkers(after));
46
+ const missing = [];
47
+ const added = [];
48
+ for (const [m, n] of a)
49
+ for (let k = b.get(m) ?? 0; k < n; k++)
50
+ missing.push(m);
51
+ for (const [m, n] of b)
52
+ for (let k = a.get(m) ?? 0; k < n; k++)
53
+ added.push(m);
54
+ return missing.length || added.length ? { missing, added } : null;
55
+ }
56
+ /** `reports.0.sections.0.conclusion` 같은 path의 마지막 칸에 값을 넣는다 */
57
+ function setAt(root, path, value) {
58
+ const keys = path.split('.');
59
+ let node = root;
60
+ for (const k of keys.slice(0, -1))
61
+ node = node[k];
62
+ node[keys[keys.length - 1]] = value;
63
+ }
64
+ /**
65
+ * 윤문된 문장을 path로 되돌려 넣는다. 검사에 걸린 항목은 원문을 두고 경고만 남긴다.
66
+ * 넘겨받은 digest는 건드리지 않고 복제본을 고쳐 돌려준다.
67
+ */
68
+ export function applyProse(input, edits) {
69
+ const digest = structuredClone(input);
70
+ const current = new Map(extractProse(digest).map((i) => [i.path, i]));
71
+ const pathCounts = countOf(edits.map((e) => e.path));
72
+ const warnings = [];
73
+ const reported = new Set();
74
+ let applied = 0;
75
+ let unchanged = 0;
76
+ for (const edit of edits) {
77
+ const { path } = edit;
78
+ // 같은 path가 둘 이상이면 어느 쪽이 맞는지 모르니 전부 원문을 둔다
79
+ if ((pathCounts.get(path) ?? 0) > 1) {
80
+ if (!reported.has(path))
81
+ warnings.push({ path, code: 'DUPLICATE_PATH' });
82
+ reported.add(path);
83
+ continue;
84
+ }
85
+ const item = current.get(path);
86
+ if (!item) {
87
+ warnings.push({ path, code: 'UNKNOWN_PATH' });
88
+ continue;
89
+ }
90
+ if (item.hash !== edit.hash) {
91
+ warnings.push({ path, code: 'STALE' });
92
+ continue;
93
+ }
94
+ if (!edit.text.trim()) {
95
+ warnings.push({ path, code: 'EMPTY' });
96
+ continue;
97
+ }
98
+ const diff = diffMarkers(item.text, edit.text);
99
+ if (diff) {
100
+ warnings.push({ path, code: 'MARKER_MISMATCH', ...diff });
101
+ continue;
102
+ }
103
+ if (edit.text === item.text) {
104
+ unchanged++;
105
+ continue;
106
+ }
107
+ setAt(digest, path, edit.text);
108
+ applied++;
109
+ }
110
+ return { digest, applied, unchanged, warnings };
111
+ }
112
+ //# sourceMappingURL=prose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prose.js","sourceRoot":"","sources":["../../../src/digest/prose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { type Block, type Extension } from './blocks.js';
2
+ import { type Cite, type Diagram } from './schema.js';
3
+ /**
4
+ * 확장 리포트 블록을 HTML과 마크다운으로 그린다. 블록 타입을 더하면 여기와 lint-extension.ts의
5
+ * blockFields 두 곳에 case를 넣는다. 둘 다 never로 빠진 타입을 컴파일 때 잡는다
6
+ */
7
+ export interface BlockHtmlContext {
8
+ escape: (s: string) => string;
9
+ cite: (c: Cite) => string;
10
+ /** cite.key 문장의 시작 시각 링크. 누르면 영상이 그 자리로 간다 */
11
+ time: (c: Cite) => string;
12
+ /** cite.key 문장의 시작 초. SVG 노드에 data-sec으로 단다 */
13
+ sec: (c: Cite) => number;
14
+ }
15
+ export interface BlockMdContext {
16
+ cite: (c: Cite) => string;
17
+ time: (c: Cite) => string;
18
+ }
19
+ /** 그림 하나와 확대 버튼, 스크립트 없이도 읽히는 목록. 대섹션 도식도 이 틀을 쓴다 */
20
+ export declare function diagramFigure(svg: string, outline: string, caption?: string): string;
21
+ /** 대섹션 도식. title은 그림의 aria-label에 붙는 대섹션 제목이다 */
22
+ export declare function sectionDiagramHtml(d: Diagram, title: string, ctx: BlockHtmlContext): string;
23
+ /** 마크다운에는 그림 대신 목록으로 옮긴다. 순서가 있는 흐름과 순환은 번호를 단다 */
24
+ export declare function sectionDiagramMd(d: Diagram, ctx: BlockMdContext): string[];
25
+ export declare const mdInline: (text: string) => string;
26
+ /** id는 블록마다 겹치지 않는 접두어다. 퀴즈 라디오 그룹 이름에 쓴다 */
27
+ export declare function blockHtml(block: Block, ctx: BlockHtmlContext, id?: string): string;
28
+ export declare function blockMarkdown(block: Block, ctx: BlockMdContext): string[];
29
+ /** 탭 id와 패널 id. 키에 들어가는 글자(영숫자, -, .)는 id에 그대로 써도 된다 */
30
+ export declare const extensionPanelId: (ext: Pick<Extension, "template" | "lang">) => string;
31
+ export declare function extensionHtml(ext: Extension, ctx: BlockHtmlContext): string;
32
+ //# sourceMappingURL=render-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-blocks.d.ts","sourceRoot":"","sources":["../../../src/digest/render-blocks.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,KAAK,EAEV,KAAK,SAAS,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAA+B,KAAK,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAGnF;;;GAGG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC;IAC1B,+CAA+C;IAC/C,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC;IAC1B,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC;CAC3B;AA4CD,qDAAqD;AACrD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpF;AASD,iDAAiD;AACjD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,GAAG,MAAM,CA6B3F;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,MAAM,EAAE,CAqB1E;AAmBD,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,WAAwC,CAAC;AA0B9E,6CAA6C;AAC7C,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,SAAM,GAAG,MAAM,CA0F/E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,GAAG,MAAM,EAAE,CA8FzE;AAED,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,GAAI,KAAK,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAAC,WAC9C,CAAC;AAE7B,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,GAAG,MAAM,CAK3E"}