@iyulab/chat-components 0.2.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (2026-01-22)
4
+
5
+ - Renamed `TextBlockReference` to `ReferenceCitation` and field `name` to `label`
6
+ - Updated `url` field in `ReferenceSource` to be optiona
7
+ - Added `URefBlock` component for displaying multiple reference sources
8
+ - Enhanced `URefCard` and `URefTag` components with improved functionality
9
+
3
10
  ## 0.2.0 (2026-01-16)
4
11
 
5
12
  ### Added
@@ -1,5 +1,5 @@
1
1
  import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
2
- import { TextBlockReference } from '../../types/BlockReference';
2
+ import { ReferenceCitation } from '../../types/References.js';
3
3
  /**
4
4
  * 마크다운 컨텐츠를 렌더링하는 컴포넌트입니다.
5
5
  *
@@ -15,7 +15,7 @@ export declare class UMarkedBlock extends BaseElement {
15
15
  /** 마크다운 컨텐츠를 렌더링할 때 사용할 값입니다. */
16
16
  value?: string;
17
17
  /** 컨텐츠의 인용 출처들입니다. */
18
- refs?: TextBlockReference[];
18
+ refs?: ReferenceCitation[];
19
19
  render(): import('lit-html/directive.js').DirectiveResult<typeof import('lit-html/directives/unsafe-html.js').UnsafeHTMLDirective>;
20
20
  /**
21
21
  * 인용/출처 참조객체가 있을때 문자열에 출처태그를 삽입합니다.
@@ -69,8 +69,8 @@ class UMarkedBlock extends BaseElement {
69
69
  reversed.forEach((ref) => {
70
70
  const sources = ref.sources ?? [];
71
71
  const firstSource = sources.at(0);
72
- const tagName = this.escapeHTML(ref.name ?? "");
73
- const tagLink = this.escapeHTML(firstSource?.url ?? "#");
72
+ const tagName = this.escapeHTML(ref.label ?? `[${refs.indexOf(ref) + 1}]`);
73
+ const tagLink = this.escapeHTML(firstSource?.url ?? "");
74
74
  let tooltip = "";
75
75
  if (firstSource && sources.length === 1) {
76
76
  tooltip = this.buildCard(firstSource, true);
@@ -88,8 +88,8 @@ class UMarkedBlock extends BaseElement {
88
88
  * 참조 카드 엘리먼트를 안전하게 빌드합니다.
89
89
  */
90
90
  buildCard(source, slot) {
91
- const type = this.escapeHTML(source.type ?? "");
92
- const href = this.escapeHTML(source.url ?? "#");
91
+ const type = this.escapeHTML(source.type || "web");
92
+ const href = this.escapeHTML(source.url ?? "");
93
93
  const heading = this.escapeHTML(source.title ?? "");
94
94
  const tags = this.escapeHTML((source.tags ?? []).join(","));
95
95
  const snippet = this.escapeHTML(source.snippet ?? "");
@@ -0,0 +1,17 @@
1
+ import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
2
+ import { ReferenceSource } from '../../types/References.js';
3
+ /**
4
+ * 레퍼런스 정보를 블록 형태로 표시하는 컴포넌트입니다.
5
+ * 단일 또는 다중 레퍼런스 카드를 그리드 레이아웃으로 표시합니다.
6
+ */
7
+ export declare class URefBlock extends BaseElement {
8
+ static styles: import('lit').CSSResultGroup[];
9
+ static dependencies: Record<string, typeof BaseElement>;
10
+ /** 접힘 상태 @default: true */
11
+ collapsed: boolean;
12
+ /** 블록 제목 */
13
+ heading?: string;
14
+ /** 출처 목록 */
15
+ sources?: ReferenceSource[];
16
+ render(): import('lit-html').TemplateResult<1>;
17
+ }
@@ -0,0 +1,74 @@
1
+ import { html } from 'lit';
2
+ import { property } from 'lit/decorators.js';
3
+ import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
4
+ import { URefCard } from '../references/URefCard.component.js';
5
+ import { styles } from './URefBlock.styles.js';
6
+ import { repeat } from 'lit/directives/repeat.js';
7
+
8
+ var __defProp = Object.defineProperty;
9
+ var __decorateClass = (decorators, target, key, kind) => {
10
+ var result = void 0 ;
11
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
12
+ if (decorator = decorators[i])
13
+ result = (decorator(target, key, result) ) || result;
14
+ if (result) __defProp(target, key, result);
15
+ return result;
16
+ };
17
+ class URefBlock extends BaseElement {
18
+ constructor() {
19
+ super(...arguments);
20
+ this.collapsed = true;
21
+ }
22
+ static {
23
+ this.styles = [super.styles, styles];
24
+ }
25
+ static {
26
+ this.dependencies = {
27
+ "u-ref-card": URefCard
28
+ };
29
+ }
30
+ render() {
31
+ return html`
32
+ <button class="header" @click=${() => this.collapsed = !this.collapsed}>
33
+ <u-icon
34
+ ?collapsed=${this.collapsed}
35
+ lib="internal"
36
+ name="chevron-down"
37
+ ></u-icon>
38
+ <div class="title">
39
+ ${this.heading || "References"}
40
+ </div>
41
+ <div style="flex: 1;"></div>
42
+ <div class="count">
43
+ ${this.sources ? this.sources.length : 0}
44
+ </div>
45
+ </button>
46
+
47
+ <div class="body" ?collapsed=${this.collapsed}>
48
+ ${repeat(this.sources || [], (_, idx) => idx, (source) => {
49
+ return html`
50
+ <u-ref-card
51
+ .type=${source.type}
52
+ .href=${source.url}
53
+ .heading=${source.title}
54
+ .tags=${source.tags}
55
+ >
56
+ ${source.snippet || ""}
57
+ </u-ref-card>
58
+ `;
59
+ })}
60
+ </div>
61
+ `;
62
+ }
63
+ }
64
+ __decorateClass([
65
+ property({ type: Boolean, reflect: true })
66
+ ], URefBlock.prototype, "collapsed");
67
+ __decorateClass([
68
+ property({ type: String })
69
+ ], URefBlock.prototype, "heading");
70
+ __decorateClass([
71
+ property({ type: Array })
72
+ ], URefBlock.prototype, "sources");
73
+
74
+ export { URefBlock };
@@ -0,0 +1,7 @@
1
+ import { URefBlock } from './URefBlock.component.js';
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ "u-ref-block": URefBlock;
5
+ }
6
+ }
7
+ export { URefBlock };
@@ -0,0 +1,5 @@
1
+ import { URefBlock } from './URefBlock.component.js';
2
+
3
+ URefBlock.define("u-ref-block");
4
+
5
+ export { URefBlock };
@@ -0,0 +1 @@
1
+ export declare const styles: import('lit').CSSResult;
@@ -0,0 +1,75 @@
1
+ import { css } from 'lit';
2
+
3
+ const styles = css`
4
+ :host {
5
+ display: flex;
6
+ flex-direction: column;
7
+ width: 100%;
8
+ }
9
+
10
+ /* 헤더 영역 */
11
+ .header {
12
+ all: unset;
13
+ display: flex;
14
+ flex-direction: row;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ gap: 0.5em;
18
+ color: var(--u-txt-color-strong);
19
+ user-select: none;
20
+ cursor: pointer;
21
+ }
22
+ .header:hover {
23
+ color: var(--u-txt-color-hover);
24
+ }
25
+ .header:focus-visible {
26
+ outline: 2px solid rgba(100, 150, 250, 0.6);
27
+ outline-offset: 2px;
28
+ }
29
+
30
+ u-icon {
31
+ color: inherit;
32
+ font-size: 1em;
33
+ transition: transform 0.2s ease-in-out;
34
+ }
35
+ u-icon[collapsed] {
36
+ transform: rotate(-90deg);
37
+ }
38
+
39
+ .title {
40
+ color: inherit;
41
+ font-size: 1em;
42
+ line-height: 1.5;
43
+ font-weight: 600;
44
+ color: var(--u-text-color-weak);
45
+ }
46
+
47
+ .count {
48
+ color: inherit;
49
+ font-size: 0.75em;
50
+ font-weight: 400;
51
+ line-height: 2em;
52
+ }
53
+
54
+ /* 바디 영역 */
55
+ .body {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 0.75em;
59
+ margin-top: 0.75em;
60
+ transition: all 0.3s ease-in-out;
61
+ }
62
+ .body[collapsed] {
63
+ height: 0;
64
+ opacity: 0;
65
+ margin: 0;
66
+ padding: 0;
67
+ overflow: hidden;
68
+ }
69
+
70
+ .body u-ref-card {
71
+ width: 100%;
72
+ }
73
+ `;
74
+
75
+ export { styles };
@@ -6,6 +6,7 @@ import { UTextBlock } from '../blocks/UTextBlock.component.js';
6
6
  import { UMarkedBlock } from '../blocks/UMarkedBlock.component.js';
7
7
  import { UThinkBlock } from '../blocks/UThinkBlock.component.js';
8
8
  import { UToolBlock } from '../blocks/UToolBlock.component.js';
9
+ import { URefBlock } from '../blocks/URefBlock.component.js';
9
10
  import { UDotLoader } from '../loaders/UDotLoader.component.js';
10
11
  import { styles } from './UMessage.styles.js';
11
12
 
@@ -34,6 +35,7 @@ class UMessage extends BaseElement {
34
35
  "u-marked-block": UMarkedBlock,
35
36
  "u-think-block": UThinkBlock,
36
37
  "u-tool-block": UToolBlock,
38
+ "u-ref-block": URefBlock,
37
39
  "u-dot-loader": UDotLoader
38
40
  };
39
41
  }
@@ -58,7 +60,11 @@ class UMessage extends BaseElement {
58
60
  .heading=${item.title}
59
61
  .input=${item.input}
60
62
  .output=${item.output}
61
- ></u-tool-block>` : nothing)}
63
+ ></u-tool-block>` : item.type === "reference" ? html`
64
+ <u-ref-block
65
+ .heading=${"References"}
66
+ .sources=${item.sources}
67
+ ></u-ref-block>` : nothing)}
62
68
  <u-dot-loader
63
69
  ?hidden=${!this.loading}
64
70
  ></u-dot-loader>
@@ -9,14 +9,16 @@ export declare class URefCard extends BaseElement {
9
9
  /** 카드 타입 (web 또는 document) */
10
10
  type: 'web' | 'document';
11
11
  /** 외부 링크 URL */
12
- href: string;
12
+ href?: string;
13
13
  /** 카드 타이틀 */
14
14
  heading?: string;
15
15
  /** 태그 목록 */
16
16
  tags?: string[];
17
17
  render(): import('lit-html').TemplateResult<1>;
18
+ /** 링크 클릭 핸들러 */
19
+ private handleAnchorClick;
18
20
  /** URL에서 Google의 파비콘 URL을 반환합니다. */
19
21
  private getFaviconUrl;
20
22
  /** URL에서 도메인 사이트 주소를 반환합니다. */
21
- private getPageTitle;
23
+ private getDomainName;
22
24
  }
@@ -19,7 +19,6 @@ class URefCard extends BaseElement {
19
19
  constructor() {
20
20
  super(...arguments);
21
21
  this.type = "web";
22
- this.href = "#";
23
22
  }
24
23
  static {
25
24
  this.styles = [super.styles, styles];
@@ -31,14 +30,15 @@ class URefCard extends BaseElement {
31
30
  }
32
31
  render() {
33
32
  return html`
34
- <a href="${this.href}" target="_blank" rel="noopener noreferrer">
33
+ <a href="${ifDefined(this.href)}" target="_blank" rel="noopener noreferrer"
34
+ @click=${this.handleAnchorClick}>
35
35
  <div class="header">
36
36
  <img class="favicon"
37
37
  src="${this.getFaviconUrl(this.href)}"
38
38
  alt="favicon"
39
39
  />
40
40
  <div class="title" title="${ifDefined(this.heading)}">
41
- ${this.heading || this.getPageTitle(this.href)}
41
+ ${this.heading || this.getDomainName(this.href)}
42
42
  </div>
43
43
 
44
44
  <div style="flex: 1;"></div>
@@ -62,8 +62,16 @@ class URefCard extends BaseElement {
62
62
  </a>
63
63
  `;
64
64
  }
65
+ /** 링크 클릭 핸들러 */
66
+ handleAnchorClick(e) {
67
+ if (!this.href) {
68
+ e.preventDefault();
69
+ e.stopPropagation();
70
+ }
71
+ }
65
72
  /** URL에서 Google의 파비콘 URL을 반환합니다. */
66
73
  getFaviconUrl(url) {
74
+ if (!url) return `/favicon.ico`;
67
75
  try {
68
76
  const hostname = new URL(url).hostname;
69
77
  return `https://www.google.com/s2/favicons?sz=64&domain=${hostname}`;
@@ -72,11 +80,12 @@ class URefCard extends BaseElement {
72
80
  }
73
81
  }
74
82
  /** URL에서 도메인 사이트 주소를 반환합니다. */
75
- getPageTitle(url) {
83
+ getDomainName(url) {
84
+ if (!url) return "";
76
85
  try {
77
86
  return new URL(url).hostname;
78
87
  } catch {
79
- return "Unkown";
88
+ return "";
80
89
  }
81
90
  }
82
91
  }
@@ -6,6 +6,8 @@ export declare class URefTag extends BaseElement {
6
6
  static styles: import('lit').CSSResultGroup[];
7
7
  static dependencies: Record<string, typeof BaseElement>;
8
8
  /** 인용 출처 소스 데이터 */
9
- href: string;
9
+ href?: string;
10
10
  render(): import('lit-html').TemplateResult<1>;
11
+ /** 링크 클릭 핸들러 */
12
+ private handleAnchorClick;
11
13
  }
@@ -1,5 +1,6 @@
1
1
  import { html } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
+ import { ifDefined } from 'lit/directives/if-defined.js';
3
4
  import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
4
5
  import { UTooltip } from '@iyulab/components/dist/components/tooltip/UTooltip.component.js';
5
6
  import { UIcon } from '@iyulab/components/dist/components/icon/UIcon.component.js';
@@ -15,10 +16,6 @@ var __decorateClass = (decorators, target, key, kind) => {
15
16
  return result;
16
17
  };
17
18
  class URefTag extends BaseElement {
18
- constructor() {
19
- super(...arguments);
20
- this.href = "#";
21
- }
22
19
  static {
23
20
  this.styles = [super.styles, styles];
24
21
  }
@@ -30,7 +27,8 @@ class URefTag extends BaseElement {
30
27
  }
31
28
  render() {
32
29
  return html`
33
- <a href="${this.href}" target="_blank" rel="noopener noreferrer">
30
+ <a href="${ifDefined(this.href)}" target="_blank" rel="noopener noreferrer"
31
+ @click=${this.handleAnchorClick}>
34
32
  <slot></slot>
35
33
  </a>
36
34
 
@@ -41,6 +39,13 @@ class URefTag extends BaseElement {
41
39
  </u-tooltip>
42
40
  `;
43
41
  }
42
+ /** 링크 클릭 핸들러 */
43
+ handleAnchorClick(e) {
44
+ if (!this.href) {
45
+ e.preventDefault();
46
+ e.stopPropagation();
47
+ }
48
+ }
44
49
  }
45
50
  __decorateClass([
46
51
  property({ type: String })
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './components/blocks/UMarkedBlock.js';
4
4
  export * from './components/blocks/UTextBlock.js';
5
5
  export * from './components/blocks/UThinkBlock.js';
6
6
  export * from './components/blocks/UToolBlock.js';
7
+ export * from './components/blocks/URefBlock.js';
7
8
  export * from './components/buttons/UAttachButton.js';
8
9
  export * from './components/buttons/UCopyButton.js';
9
10
  export * from './components/buttons/UVoteButton.js';
@@ -17,7 +18,7 @@ export * from './components/references/URefTag.js';
17
18
  export * from './components/references/URefCard.js';
18
19
  export * from './components/references/URefCardGroup.js';
19
20
  export type * from './types/BlockItem';
20
- export type * from './types/BlockReference';
21
21
  export type * from './types/JsonNode';
22
+ export type * from './types/References';
22
23
  export type * from './events/UCancelEvent';
23
24
  export type * from './events/USubmitEvent';
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import './components/blocks/UMarkedBlock.js';
4
4
  import './components/blocks/UTextBlock.js';
5
5
  import './components/blocks/UThinkBlock.js';
6
6
  import './components/blocks/UToolBlock.js';
7
+ import './components/blocks/URefBlock.js';
7
8
  import './components/buttons/UAttachButton.js';
8
9
  import './components/buttons/UCopyButton.js';
9
10
  import './components/buttons/UVoteButton.js';
@@ -16,21 +17,22 @@ import './components/prompt/UPrompt.js';
16
17
  import './components/references/URefTag.js';
17
18
  import './components/references/URefCard.js';
18
19
  import './components/references/URefCardGroup.js';
19
- export { UCodeBlock } from './components/blocks/UCodeBlock.component.js';
20
- export { UJsonBlock } from './components/blocks/UJsonBlock.component.js';
21
- export { UMarkedBlock } from './components/blocks/UMarkedBlock.component.js';
22
- export { UTextBlock } from './components/blocks/UTextBlock.component.js';
23
- export { UThinkBlock } from './components/blocks/UThinkBlock.component.js';
24
- export { UToolBlock } from './components/blocks/UToolBlock.component.js';
25
20
  export { UAttachButton } from './components/buttons/UAttachButton.component.js';
21
+ export { UCodeBlock } from './components/blocks/UCodeBlock.component.js';
26
22
  export { UCopyButton } from './components/buttons/UCopyButton.component.js';
27
- export { UVoteButton } from './components/buttons/UVoteButton.component.js';
28
- export { UReportButton } from './components/buttons/UReportButton.component.js';
29
- export { URetryButton } from './components/buttons/URetryButton.component.js';
30
- export { UShareButton } from './components/buttons/UShareButton.component.js';
31
23
  export { UDotLoader } from './components/loaders/UDotLoader.component.js';
24
+ export { UJsonBlock } from './components/blocks/UJsonBlock.component.js';
25
+ export { UMarkedBlock } from './components/blocks/UMarkedBlock.component.js';
32
26
  export { UMessage } from './components/message/UMessage.component.js';
33
27
  export { UPrompt } from './components/prompt/UPrompt.component.js';
34
- export { URefTag } from './components/references/URefTag.component.js';
28
+ export { URefBlock } from './components/blocks/URefBlock.component.js';
35
29
  export { URefCard } from './components/references/URefCard.component.js';
36
30
  export { URefCardGroup } from './components/references/URefCardGroup.component.js';
31
+ export { URefTag } from './components/references/URefTag.component.js';
32
+ export { UReportButton } from './components/buttons/UReportButton.component.js';
33
+ export { URetryButton } from './components/buttons/URetryButton.component.js';
34
+ export { UShareButton } from './components/buttons/UShareButton.component.js';
35
+ export { UTextBlock } from './components/blocks/UTextBlock.component.js';
36
+ export { UThinkBlock } from './components/blocks/UThinkBlock.component.js';
37
+ export { UToolBlock } from './components/blocks/UToolBlock.component.js';
38
+ export { UVoteButton } from './components/buttons/UVoteButton.component.js';
@@ -1,4 +1,4 @@
1
- import { BlockReference } from './BlockReference';
1
+ import { ReferenceSource, ReferenceCitation } from './References';
2
2
  import { JsonNode } from './JsonNode';
3
3
  /** LLM 추론 텍스트 블록입니다. */
4
4
  export interface ThinkingBlockItem {
@@ -17,8 +17,8 @@ export interface MarkdownBlockItem {
17
17
  type: "markdown";
18
18
  /** 마크다운 텍스트 */
19
19
  value?: string;
20
- /** 마크다운 내 인용 출처들 */
21
- refs?: BlockReference[];
20
+ /** 마크다운 내 인용 정보 */
21
+ refs?: ReferenceCitation[];
22
22
  }
23
23
  /** 툴 사용 블록입니다. */
24
24
  export interface ToolBlockItem {
@@ -30,7 +30,13 @@ export interface ToolBlockItem {
30
30
  /** 도구 블록 출력값(json) */
31
31
  output?: JsonNode;
32
32
  }
33
+ /** 출처 정보 */
34
+ export interface ReferenceBlockItem {
35
+ type: "reference";
36
+ /** 출처 목록 */
37
+ sources: ReferenceSource[];
38
+ }
33
39
  /**
34
40
  * 타입별 메시지 컨텐츠 아이템
35
41
  */
36
- export type BlockItem = (ThinkingBlockItem | TextBlockItem | MarkdownBlockItem | ToolBlockItem);
42
+ export type BlockItem = (ThinkingBlockItem | TextBlockItem | MarkdownBlockItem | ToolBlockItem | ReferenceBlockItem);
@@ -1,11 +1,11 @@
1
1
  /**
2
- * 블록 참조에서 사용되는 참조 내용입니다.
2
+ * 참조에서 사용되는 원본 내용입니다.
3
3
  */
4
4
  export interface ReferenceSource {
5
5
  /** 자료의 종류 입니다. */
6
6
  type: 'web' | 'document';
7
7
  /** 참조 링크 URL */
8
- url: string;
8
+ url?: string;
9
9
  /** 제목 */
10
10
  title?: string;
11
11
  /** 발췌 내용 */
@@ -14,19 +14,15 @@ export interface ReferenceSource {
14
14
  tags?: string[];
15
15
  }
16
16
  /**
17
- * 텍스트 컨텐츠 내 인용 참조 블록
17
+ * 본문참조 인용 정보입니다.
18
18
  */
19
- export interface TextBlockReference {
20
- /** 인용 출처 이름 */
21
- name: string;
19
+ export interface ReferenceCitation {
22
20
  /** 참조 텍스트 시작 위치 (문자 인덱스) */
23
21
  startIndex: number;
24
22
  /** 참조 텍스트 종료 위치 (문자 인덱스) */
25
23
  endIndex: number;
24
+ /** 본문에 표시될 참조 라벨 (예: [1], (Smith et al., 2023)) */
25
+ label?: string;
26
26
  /** 원본 참조 내용 */
27
27
  sources: ReferenceSource[];
28
28
  }
29
- /**
30
- * 메시지 컨텐츠 내에서 인용 참조 블록
31
- */
32
- export type BlockReference = TextBlockReference;
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.2.0",
4
+ "version": "0.3.0",
5
5
  "author": "iyulab",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -37,7 +37,7 @@
37
37
  }
38
38
  },
39
39
  "scripts": {
40
- "start": "vite",
40
+ "test": "vite",
41
41
  "build": "eslint && vite build"
42
42
  },
43
43
  "dependencies": {
@@ -57,7 +57,7 @@
57
57
  "globals": "^17.0.0",
58
58
  "openai": "^6.16.0",
59
59
  "typescript": "^5.9.3",
60
- "typescript-eslint": "^8.53.0",
60
+ "typescript-eslint": "^8.53.1",
61
61
  "vite": "^7.3.1",
62
62
  "vite-plugin-dts": "^4.5.4"
63
63
  }