@iyulab/chat-components 0.11.9 → 0.12.1

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 (42) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +16 -0
  3. package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorate.js +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorateMetadata.js +1 -1
  5. package/dist/components/blocks/UCodeBlock.js +2 -2
  6. package/dist/components/blocks/UElementBlock.js +2 -2
  7. package/dist/components/blocks/UFileBlock.d.ts +2 -0
  8. package/dist/components/blocks/UFileBlock.js +21 -18
  9. package/dist/components/blocks/UFileBlock.styles.js +24 -4
  10. package/dist/components/blocks/UMarkedBlock.js +3 -3
  11. package/dist/components/blocks/URefBlock.js +2 -2
  12. package/dist/components/blocks/UTableBlock.js +13 -9
  13. package/dist/components/blocks/UTableBlock.styles.js +30 -3
  14. package/dist/components/blocks/UTextBlock.js +2 -2
  15. package/dist/components/message/UMessage.js +2 -2
  16. package/dist/components/prompt/UPrompt.js +2 -2
  17. package/dist/components/references/URefCard.js +2 -2
  18. package/dist/components/references/URefCardGroup.js +2 -2
  19. package/dist/components/references/URefCardGroup.styles.js +5 -0
  20. package/dist/components/references/URefTag.js +2 -2
  21. package/dist/components-extra/UChartBlock.js +2 -2
  22. package/dist/components-extra/UChartBlock.styles.js +15 -1
  23. package/dist/components-extra/UImagesBlock.js +9 -6
  24. package/dist/components-extra/UImagesBlock.styles.js +13 -0
  25. package/dist/components-extra/UMapBlock.js +2 -2
  26. package/dist/components-extra/UMapBlock.styles.js +11 -1
  27. package/dist/components-extra/UVideoBlock.js +2 -2
  28. package/dist/node_modules/katex/dist/katex.js +26367 -0
  29. package/dist/utilities/markedKatex.d.ts +10 -0
  30. package/dist/utilities/markedKatex.js +106 -0
  31. package/dist/utilities/messages.d.ts +1 -1
  32. package/dist/utilities/messages.js +6 -2
  33. package/package.json +10 -11
  34. package/skills/iyulab-chat-components/references/components/chart-block.md +17 -0
  35. package/skills/iyulab-chat-components/references/components/code-block.md +12 -0
  36. package/skills/iyulab-chat-components/references/components/images-block.md +10 -0
  37. package/skills/iyulab-chat-components/references/components/map-block.md +15 -0
  38. package/skills/iyulab-chat-components/references/components/marked-block.md +8 -0
  39. package/skills/iyulab-chat-components/references/components/message.md +8 -0
  40. package/skills/iyulab-chat-components/references/components/prompt.md +11 -0
  41. package/skills/iyulab-chat-components/references/components/table-block.md +15 -0
  42. package/skills/iyulab-chat-components/references/components/video-block.md +10 -0
@@ -0,0 +1,10 @@
1
+ import { KatexOptions } from 'katex';
2
+ import { MarkedExtension } from 'marked';
3
+ /** `marked` 확장이 받는 옵션 — `displayMode` 는 구분자가 정하므로 소비자가 줄 수 없다. */
4
+ export type MarkedKatexOptions = Omit<KatexOptions, "displayMode">;
5
+ /**
6
+ * `marked.use()` 에 넘길 수식 확장을 만든다.
7
+ *
8
+ * @param options KaTeX 렌더 옵션(`displayMode` 제외 — 구분자가 정한다).
9
+ */
10
+ export declare function markedKatex(options?: MarkedKatexOptions): MarkedExtension;
@@ -0,0 +1,106 @@
1
+ import katex from "../node_modules/katex/dist/katex.js";
2
+ //#region src/utilities/markedKatex.ts
3
+ /**
4
+ * `marked` 에 TeX 수식 렌더를 붙이는 확장.
5
+ *
6
+ * 알고리즘(구분자 규칙과 토크나이저 구성)은 `marked-katex-extension`(MIT, © 2021 @markedjs)에서
7
+ * 왔고, 타입·옵션 표면은 이 패키지의 필요에 맞춰 다시 썼다.
8
+ *
9
+ * ## 두 가지 «모드»가 있고, 서로 다른 축이다
10
+ *
11
+ * - **`displayMode`** — KaTeX 에게 *"큰 조판으로 그려라"* 라고 말하는 것(`$$…$$`).
12
+ * - **블록 배치** — 수식이 문단 «밖»에 서는 것. `$$` 가 **자기 줄에 혼자** 있을 때만 일어난다.
13
+ *
14
+ * ⚠**둘은 함께 가지 않는다**: 한 줄 안의 `$$x$$` 는 `displayMode` 이면서 문단 «안»에 남는다.
15
+ * 두 축을 하나로 합치면 기존 문서의 조판이 바뀐다.
16
+ *
17
+ * ## 구분자 규칙
18
+ *
19
+ * - 인라인 `$…$` / `$$…$$` — 여는 구분자 뒤에 `$` 가 바로 오지 않아야 하고(빈 수식 방지),
20
+ * 내용은 **줄을 넘지 못하며**, 닫는 구분자 뒤에는 공백·구두점·문장 끝이 와야 한다.
21
+ * - 블록 `$$\n…\n$$` — 여는·닫는 `$$` 가 각각 자기 줄에 있을 때.
22
+ *
23
+ * 🔴**«수식이 아닌 것을 수식으로 만들지 않는 것»이 이 규칙의 목적이다.** 가장 흔한 오탐
24
+ * 경로는 통화 표기 둘(`from $5 to $10`)이고, 그것을 막는 것이 ⑴내용에서 개행을 배제하고
25
+ * ⑵닫는 구분자 뒤 문맥을 요구하는 두 조건이다. 규칙을 느슨하게 하면 본문이 통째로 한
26
+ * 수식으로 삼켜진다.
27
+ */
28
+ /** 여는 구분자 뒤에 `$` 가 오지 않고, 내용이 줄을 넘지 않으며, 닫는 뒤가 공백·구두점·끝. */
29
+ var INLINE_RULE = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n$]))\1(?=[\s?!.,:?!。,:]|$)/;
30
+ /** `$$` 가 각각 자기 줄에 있는 형태. */
31
+ var BLOCK_RULE = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/;
32
+ function render(options, token, newlineAfter) {
33
+ const html = katex.renderToString(token.text, {
34
+ ...options,
35
+ displayMode: token.displayMode
36
+ });
37
+ return newlineAfter ? `${html}\n` : html;
38
+ }
39
+ function inlineKatex(options) {
40
+ return {
41
+ name: "inlineKatex",
42
+ level: "inline",
43
+ /**
44
+ * `marked` 가 «인라인 텍스트를 어디서 끊을지» 고르는 힌트.
45
+ *
46
+ * ⚠**이것은 규칙이 아니라 힌트다** — 여기서 걸러도 토크나이저는 그 뒤 위치에서 다시
47
+ * 호출되므로, 여는 `$` 앞에 공백이 없는 `a$x$` 도 결국 파싱된다. 문서의 조판을 바꾸지
48
+ * 않으려면 이 성질을 그대로 두어야 한다.
49
+ */
50
+ start(src) {
51
+ let indexSrc = src;
52
+ let consumed = 0;
53
+ while (indexSrc) {
54
+ const index = indexSrc.indexOf("$");
55
+ if (index === -1) return;
56
+ if ((index === 0 || indexSrc.charAt(index - 1) === " ") && INLINE_RULE.test(indexSrc.substring(index))) return consumed + index;
57
+ const skipped = indexSrc.substring(index + 1);
58
+ const trimmed = skipped.replace(/^\$+/, "");
59
+ consumed += index + 1 + (skipped.length - trimmed.length);
60
+ indexSrc = trimmed;
61
+ }
62
+ },
63
+ tokenizer(src) {
64
+ const match = src.match(INLINE_RULE);
65
+ if (!match) return void 0;
66
+ return {
67
+ type: "inlineKatex",
68
+ raw: match[0],
69
+ text: match[2].trim(),
70
+ displayMode: match[1].length === 2
71
+ };
72
+ },
73
+ renderer(token) {
74
+ return render(options, token, false);
75
+ }
76
+ };
77
+ }
78
+ function blockKatex(options) {
79
+ return {
80
+ name: "blockKatex",
81
+ level: "block",
82
+ tokenizer(src) {
83
+ const match = src.match(BLOCK_RULE);
84
+ if (!match) return void 0;
85
+ return {
86
+ type: "blockKatex",
87
+ raw: match[0],
88
+ text: match[2].trim(),
89
+ displayMode: match[1].length === 2
90
+ };
91
+ },
92
+ renderer(token) {
93
+ return render(options, token, true);
94
+ }
95
+ };
96
+ }
97
+ /**
98
+ * `marked.use()` 에 넘길 수식 확장을 만든다.
99
+ *
100
+ * @param options KaTeX 렌더 옵션(`displayMode` 제외 — 구분자가 정한다).
101
+ */
102
+ function markedKatex(options = {}) {
103
+ return { extensions: [inlineKatex(options), blockKatex(options)] };
104
+ }
105
+ //#endregion
106
+ export { markedKatex };
@@ -5,5 +5,5 @@
5
5
  * 이 리포가 채택한 표준이 *"영어 기본 **+ 레지스트리**"* 이기 때문이다 — 영어 리터럴만으로는
6
6
  * 한국어 앱이 그 문구를 되돌릴 방법이 없다. 다음 문자열은 여기로 온다.
7
7
  */
8
- export type ChatMessageKey = 'canvasUnavailable' | 'previousReference' | 'nextReference' | 'closePreview' | 'closeLightbox' | 'previousImage' | 'nextImage' | 'sendMessage' | 'stopGenerating' | 'fullScreen' | 'remove' | 'pngDownload' | 'jsonDownload' | 'search' | 'rowCount' | 'excelDownload' | 'csvDownload';
8
+ export type ChatMessageKey = 'canvasUnavailable' | 'previousReference' | 'nextReference' | 'closePreview' | 'closeLightbox' | 'previousImage' | 'nextImage' | 'sendMessage' | 'stopGenerating' | 'fullScreen' | 'remove' | 'pngDownload' | 'jsonDownload' | 'search' | 'rowCount' | 'excelDownload' | 'csvDownload' | 'openImage' | 'previewFile';
9
9
  export declare const messages: import('@iyulab/components/dist/utilities/Locale.js').LocaleNamespace<ChatMessageKey>;
@@ -18,7 +18,9 @@ messages.register("en", {
18
18
  search: "Search...",
19
19
  rowCount: "{shown} / {total} Rows",
20
20
  excelDownload: "Excel Download",
21
- csvDownload: "CSV Download"
21
+ csvDownload: "CSV Download",
22
+ openImage: "Open image {index}",
23
+ previewFile: "Preview {name}"
22
24
  });
23
25
  messages.register("ko", {
24
26
  canvasUnavailable: "Canvas 2D context를 가져올 수 없습니다.",
@@ -37,7 +39,9 @@ messages.register("ko", {
37
39
  search: "검색...",
38
40
  rowCount: "{shown} / {total} 행",
39
41
  excelDownload: "Excel 다운로드",
40
- csvDownload: "CSV 다운로드"
42
+ csvDownload: "CSV 다운로드",
43
+ openImage: "이미지 {index} 열기",
44
+ previewFile: "{name} 미리보기"
41
45
  });
42
46
  //#endregion
43
47
  export { messages };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/chat-components",
3
3
  "description": "llm chat components for building chat interfaces",
4
- "version": "0.11.9",
4
+ "version": "0.12.1",
5
5
  "author": "iyulab",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -61,14 +61,13 @@
61
61
  "typecheck": "tsc --noEmit"
62
62
  },
63
63
  "dependencies": {
64
- "@iyulab/components": "^1.23.0",
65
64
  "highlight.js": "^11.11.1",
66
- "katex": "^0.17.0",
65
+ "katex": "^0.18.7",
67
66
  "lit": "^3.3.3",
68
- "marked": "^18.0.6",
69
- "marked-katex-extension": "5.1.10"
67
+ "marked": "^18.0.6"
70
68
  },
71
69
  "peerDependencies": {
70
+ "@iyulab/components": ">=1.23.0",
72
71
  "@lit/react": ">=1.0.8",
73
72
  "chart.js": ">=4.5.1",
74
73
  "react": ">=18.0.0"
@@ -85,19 +84,19 @@
85
84
  }
86
85
  },
87
86
  "devDependencies": {
88
- "@eslint/js": "^9.39.4",
87
+ "@eslint/js": "^10.0.1",
89
88
  "@types/node": "^26.1.1",
90
- "@vitest/browser-playwright": "^4.1.10",
89
+ "@vitest/browser-playwright": "^5.0.0",
91
90
  "chart.js": "^4.5.1",
92
- "eslint": "^9.39.4",
91
+ "eslint": "^10.1.0",
93
92
  "eslint-plugin-lit": "^2.3.1",
94
93
  "glob": "^13.0.6",
95
94
  "globals": "^17.7.0",
96
- "openai": "^6.47.0",
97
- "typescript": "^5.9.3",
95
+ "openai": "^7.15.0",
96
+ "typescript": "^6.0.2",
98
97
  "typescript-eslint": "^8.64.0",
99
98
  "vite": "^8.1.4",
100
99
  "vite-plugin-dts": "^5.0.3",
101
- "vitest": "^4.1.10"
100
+ "vitest": "^5.0.0"
102
101
  }
103
102
  }
@@ -48,6 +48,23 @@ LLM output example (`block-json` code fence, rendered via `u-element-block`):
48
48
 
49
49
  ---
50
50
 
51
+ ## Sizing
52
+
53
+ Content-sized by default: the toolbar plus the chart area. Measured at 600px wide with a small
54
+ bar chart, that is ~368px.
55
+
56
+ **A `max-height` (or `height`) on the host wins.** The toolbar keeps its height and the chart area
57
+ takes the rest, so nothing is clipped away:
58
+
59
+ ```css
60
+ u-chart-block { max-height: 200px; }
61
+ ```
62
+
63
+ Before this was fixed, the chart area kept its own height regardless — against a 120px host, 248px
64
+ of it was clipped by the block's `overflow: hidden` with no scrollbar left to reach it.
65
+
66
+ There is no size property or custom property for this — the host is the lever.
67
+
51
68
  ## Properties
52
69
 
53
70
  | Property | Type | Default | Description |
@@ -23,6 +23,18 @@ Syntax-highlighted code block powered by Highlight.js. Shows a language label an
23
23
 
24
24
  ---
25
25
 
26
+ ## Sizing
27
+
28
+ **There is no vertical cap.** Long code grows to its content rather than folding — measured: 200
29
+ lines render about 4,100px tall, with no vertical scrollbar of its own. Only long *lines* scroll,
30
+ horizontally, inside the code area.
31
+
32
+ That is deliberate, and it differs from [`u-table-block`](table-block.md), which caps at 480px:
33
+ folded code cannot be read, and the conversation the block sits in already scrolls.
34
+
35
+ Fenced code inside [`u-marked-block`](marked-block.md) renders as a nested `u-code-block`, so it
36
+ follows this same contract.
37
+
26
38
  ## Properties
27
39
 
28
40
  | Property | Type | Default | Reflect | Description |
@@ -38,6 +38,16 @@ LLM output example (`block-json` code fence):
38
38
 
39
39
  ---
40
40
 
41
+ ## Sizing
42
+
43
+ Height comes from width: each slide is `4 / 3`, so the block is as tall as its column is wide.
44
+ Measured at 600px wide, that is ~222px.
45
+
46
+ ⚠**A `max-height` on the host does *not* shrink the images, and that is deliberate.** Squeezing them
47
+ vertically would break the aspect ratio and distort the picture, so the block overflows **visibly**
48
+ instead — it never clips, and nothing becomes unreachable. To make it shorter, give it a narrower
49
+ column rather than a shorter host.
50
+
41
51
  ## Properties
42
52
 
43
53
  | Property | Type | Default | Description |
@@ -41,6 +41,21 @@ LLM output example (`block-json` code fence, rendered via `u-element-block`):
41
41
 
42
42
  ---
43
43
 
44
+ ## Sizing
45
+
46
+ The map is **300px tall when nothing constrains it** — that is a default, not a fixed height.
47
+
48
+ **A `max-height` (or `height`) on the host wins**, and the map frame shrinks to fit it:
49
+
50
+ ```css
51
+ u-map-block { max-height: 200px; }
52
+ ```
53
+
54
+ Before this was fixed, the frame stayed at 300px regardless — against a 120px host, 180px of the map
55
+ was clipped by the block's `overflow: hidden` with no scrollbar left to reach it.
56
+
57
+ There is no size property or custom property for this — the host is the lever.
58
+
44
59
  ## Properties
45
60
 
46
61
  | Property | Type | Default | Description |
@@ -35,6 +35,14 @@ Tracks a `streaming` state internally (resets a 1500ms idle timer on every `valu
35
35
 
36
36
  ---
37
37
 
38
+ ## Sizing
39
+
40
+ The block grows with its content — there is no cap, and the conversation around it does the
41
+ scrolling.
42
+
43
+ **Fenced code becomes a nested [`u-code-block`](code-block.md)**, not a plain `<pre>`, so fenced
44
+ code follows that component's contract (no vertical cap; long lines scroll horizontally).
45
+
38
46
  ## Properties
39
47
 
40
48
  | Property | Type | Default | Description |
@@ -36,6 +36,14 @@ Chat message wrapper component. Arranges content blocks via slots, shows a loadi
36
36
 
37
37
  ---
38
38
 
39
+ ## Sizing
40
+
41
+ Content-sized: the bubble is as tall as what you put in it.
42
+
43
+ ⚠**A `max-height` on the host does *not* fold the content, and that is deliberate.** A message that
44
+ hides part of itself cannot be read, and the conversation stream around it already scrolls. The
45
+ block overflows **visibly** rather than clipping. Constrain the stream, not the message.
46
+
39
47
  ## Slots
40
48
 
41
49
  | Name | Description |
@@ -47,6 +47,17 @@ prompt.submit();
47
47
 
48
48
  ---
49
49
 
50
+ ## Sizing
51
+
52
+ The prompt does not grow with its value. `minRows`/`maxRows` bound the inner `u-text-block`, and the
53
+ text scrolls **inside** that box — so the component stays short no matter how long the value is.
54
+ Measured at 600px wide with a 40-line value and `maxRows: 40`, the whole block is 72px tall.
55
+
56
+ ⚠**Its behaviour under a host `max-height` is therefore untested.** A 200px or 120px constraint never
57
+ engages, because the natural height is already below both. That is not the same as "it behaves
58
+ correctly under a constraint" — it means the question has not been answered. Use `minRows`/`maxRows`
59
+ to size this component.
60
+
50
61
  ## Slots
51
62
 
52
63
  | Name | Description |
@@ -37,6 +37,21 @@ Extends `UDataElement`, so data can also be injected via a `<script type="applic
37
37
 
38
38
  ---
39
39
 
40
+ ## Sizing
41
+
42
+ The table area caps at **480px** and scrolls inside that cap; below it the block is content-sized.
43
+ Measured at 600px wide: 40 rows give a 480px table area with ~1,000px to scroll, 2 rows give ~108px
44
+ and no scrollbar. That default keeps a long table from swallowing the conversation.
45
+
46
+ **A `max-height` (or `height`) on the host wins.** The table area shrinks to fit it and still
47
+ scrolls, so no rows become unreachable:
48
+
49
+ ```css
50
+ u-table-block { max-height: 200px; }
51
+ ```
52
+
53
+ There is no size property or custom property for this — the host is the lever.
54
+
40
55
  ## Properties
41
56
 
42
57
  | Property | Type | Default | Description |
@@ -38,6 +38,16 @@ LLM output example (`block-json` code fence, rendered via `u-element-block`):
38
38
 
39
39
  ---
40
40
 
41
+ ## Sizing
42
+
43
+ Height comes from width through `ratio` (`16:9` by default), capped at `max-width: 800px`. Measured
44
+ at 600px wide, a 16:9 video is ~342px tall. This holds for both branches — the embed iframe
45
+ (YouTube/Vimeo) and the native `<video>` element.
46
+
47
+ ⚠**A `max-height` on the host does *not* shrink the video, and that is deliberate** — the same
48
+ reason as the image block: vertical squeezing would break the aspect ratio. The block overflows
49
+ **visibly** rather than clipping. Use `ratio`, or a narrower column, to change its height.
50
+
41
51
  ## Properties
42
52
 
43
53
  | Property | Type | Default | Description |