@designbasekorea/ui-wc 0.5.6 → 0.6.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/dist/chunks/lottie-0e23d630.js +1 -0
- package/dist/esm/components/animation-background/canvas-controller.js +1 -121
- package/dist/esm/components/animation-background/controls.js +1 -180
- package/dist/esm/components/animation-background/graphite-controller.js +1 -196
- package/dist/esm/components/animation-background/graphite-presets.js +1 -42
- package/dist/esm/components/animation-background/graphite-shader.js +1 -111
- package/dist/esm/components/animation-background/layers.js +1 -369
- package/dist/esm/components/animation-background/lumina-presets.js +1 -53
- package/dist/esm/components/animation-background/lumina-shaders.js +1 -181
- package/dist/esm/components/animation-background/lumina-utils.js +1 -60
- package/dist/esm/components/animation-background/particle-defaults.js +1 -36
- package/dist/esm/components/animation-background/renderers.js +1 -167
- package/dist/esm/components/animation-background/shader-controller.js +1 -154
- package/dist/esm/components/color-picker-utils.js +1 -134
- package/dist/esm/components/date-picker-utils.js +1 -140
- package/dist/esm/components/db-accordion.js +1 -304
- package/dist/esm/components/db-ad-banner.js +1 -455
- package/dist/esm/components/db-ai-chat-list.d.ts +43 -0
- package/dist/esm/components/db-ai-chat-list.js +1 -0
- package/dist/esm/components/db-ai-chat.d.ts +159 -0
- package/dist/esm/components/db-ai-chat.js +1 -0
- package/dist/esm/components/db-alert.js +1 -149
- package/dist/esm/components/db-animation-background.js +1 -486
- package/dist/esm/components/db-animation-text.js +1 -601
- package/dist/esm/components/db-app-badge.js +1 -88
- package/dist/esm/components/db-audio-player.js +1 -752
- package/dist/esm/components/db-avatar.js +1 -188
- package/dist/esm/components/db-backdrop.js +1 -139
- package/dist/esm/components/db-badge.js +1 -91
- package/dist/esm/components/db-banner.js +1 -188
- package/dist/esm/components/db-bottom-navigation.js +1 -146
- package/dist/esm/components/db-bottom-sheet.js +1 -481
- package/dist/esm/components/db-breadcrumbs.js +1 -329
- package/dist/esm/components/db-button.js +1 -316
- package/dist/esm/components/db-calendar.js +1 -675
- package/dist/esm/components/db-card.js +1 -265
- package/dist/esm/components/db-carousel.js +1 -264
- package/dist/esm/components/db-chat-composer.d.ts +134 -0
- package/dist/esm/components/db-chat-composer.js +1 -0
- package/dist/esm/components/db-chat-list.d.ts +47 -0
- package/dist/esm/components/db-chat-list.js +1 -0
- package/dist/esm/components/db-chat-message.d.ts +39 -0
- package/dist/esm/components/db-chat-message.js +1 -0
- package/dist/esm/components/db-chat-orb.d.ts +99 -0
- package/dist/esm/components/db-chat-orb.js +1 -0
- package/dist/esm/components/db-chat-room.d.ts +71 -0
- package/dist/esm/components/db-chat-room.js +1 -0
- package/dist/esm/components/db-chat-suggestions.d.ts +44 -0
- package/dist/esm/components/db-chat-suggestions.js +1 -0
- package/dist/esm/components/db-chat-typing.d.ts +22 -0
- package/dist/esm/components/db-chat-typing.js +1 -0
- package/dist/esm/components/db-chat-welcome.d.ts +32 -0
- package/dist/esm/components/db-chat-welcome.js +1 -0
- package/dist/esm/components/db-chat-widget.d.ts +146 -0
- package/dist/esm/components/db-chat-widget.js +1 -0
- package/dist/esm/components/db-checkbox.js +1 -202
- package/dist/esm/components/db-chip.js +1 -104
- package/dist/esm/components/db-code-block.js +1 -304
- package/dist/esm/components/db-color-picker.js +1 -746
- package/dist/esm/components/db-confirm.js +1 -191
- package/dist/esm/components/db-container.js +1 -39
- package/dist/esm/components/db-context-menu.js +1 -265
- package/dist/esm/components/db-countdown.js +1 -146
- package/dist/esm/components/db-cursor-follower.js +1 -173
- package/dist/esm/components/db-date-picker.js +1 -837
- package/dist/esm/components/db-divider.js +1 -66
- package/dist/esm/components/db-download-progress.d.ts +89 -0
- package/dist/esm/components/db-download-progress.js +1 -0
- package/dist/esm/components/db-drawer.js +1 -245
- package/dist/esm/components/db-dropdown.js +1 -493
- package/dist/esm/components/db-dropzone.js +1 -266
- package/dist/esm/components/db-email-verify.d.ts +62 -0
- package/dist/esm/components/db-email-verify.js +1 -0
- package/dist/esm/components/db-empty-state.js +1 -97
- package/dist/esm/components/db-file-uploader.js +1 -256
- package/dist/esm/components/db-floating-action-button.js +1 -139
- package/dist/esm/components/db-footer.js +1 -385
- package/dist/esm/components/db-form.js +1 -566
- package/dist/esm/components/db-gantt.js +1 -882
- package/dist/esm/components/db-gradient.js +1 -133
- package/dist/esm/components/db-grid-background.js +1 -909
- package/dist/esm/components/db-grid.js +1 -73
- package/dist/esm/components/db-icons.d.ts +3 -0
- package/dist/esm/components/db-icons.js +1 -82
- package/dist/esm/components/db-image-list.js +1 -214
- package/dist/esm/components/db-image-placeholder.js +1 -96
- package/dist/esm/components/db-image.js +1 -289
- package/dist/esm/components/db-indicator.js +1 -517
- package/dist/esm/components/db-input.js +1 -420
- package/dist/esm/components/db-kanban.js +1 -538
- package/dist/esm/components/db-label.js +1 -66
- package/dist/esm/components/db-lightbox.js +1 -372
- package/dist/esm/components/db-list.js +1 -340
- package/dist/esm/components/db-logo-svg.js +1 -72
- package/dist/esm/components/db-logo.js +1 -106
- package/dist/esm/components/db-lottie.js +1 -109
- package/dist/esm/components/db-markdown-editor.js +1 -458
- package/dist/esm/components/db-marquee.js +1 -402
- package/dist/esm/components/db-masonry.js +1 -94
- package/dist/esm/components/db-menu-item.js +1 -211
- package/dist/esm/components/db-modal.js +1 -522
- package/dist/esm/components/db-navbar.js +1 -680
- package/dist/esm/components/db-onboarding-modal.js +1 -322
- package/dist/esm/components/db-otp-input.d.ts +53 -0
- package/dist/esm/components/db-otp-input.js +1 -0
- package/dist/esm/components/db-page-header.js +1 -312
- package/dist/esm/components/db-pagination.js +1 -271
- package/dist/esm/components/db-phone-verify.d.ts +61 -0
- package/dist/esm/components/db-phone-verify.js +1 -0
- package/dist/esm/components/db-popover.js +1 -580
- package/dist/esm/components/db-progress-step.js +1 -166
- package/dist/esm/components/db-progress.js +1 -171
- package/dist/esm/components/db-progressbar.js +1 -72
- package/dist/esm/components/db-radio.js +1 -172
- package/dist/esm/components/db-random-gradient.js +1 -121
- package/dist/esm/components/db-range-slider.js +1 -811
- package/dist/esm/components/db-rating.js +1 -325
- package/dist/esm/components/db-reorder.js +1 -286
- package/dist/esm/components/db-resizable-panels.js +1 -282
- package/dist/esm/components/db-scroll-area.js +1 -126
- package/dist/esm/components/db-search-bar.js +1 -668
- package/dist/esm/components/db-section-app-download.js +1 -101
- package/dist/esm/components/db-section-contact.js +1 -102
- package/dist/esm/components/db-section-cta.js +1 -150
- package/dist/esm/components/db-section-faq.js +1 -120
- package/dist/esm/components/db-section-feature-grid.js +1 -137
- package/dist/esm/components/db-section-feature.js +1 -179
- package/dist/esm/components/db-section-hero.js +1 -243
- package/dist/esm/components/db-section-logo-cloud.js +1 -138
- package/dist/esm/components/db-section-pricing.js +1 -166
- package/dist/esm/components/db-section-stats.js +1 -129
- package/dist/esm/components/db-section-testimonials.js +1 -252
- package/dist/esm/components/db-section.js +1 -385
- package/dist/esm/components/db-segment-control.js +1 -231
- package/dist/esm/components/db-select.js +1 -571
- package/dist/esm/components/db-share-icons.js +1 -40
- package/dist/esm/components/db-share.js +1 -489
- package/dist/esm/components/db-sidebar.js +1 -1068
- package/dist/esm/components/db-skeleton.js +1 -94
- package/dist/esm/components/db-spinner.js +1 -102
- package/dist/esm/components/db-split-view.js +1 -297
- package/dist/esm/components/db-stack.js +1 -46
- package/dist/esm/components/db-stat.js +1 -218
- package/dist/esm/components/db-stepper.js +1 -398
- package/dist/esm/components/db-table.js +1 -412
- package/dist/esm/components/db-tabs.js +1 -335
- package/dist/esm/components/db-testimonial.d.ts +2 -0
- package/dist/esm/components/db-testimonial.js +1 -162
- package/dist/esm/components/db-textarea.js +1 -233
- package/dist/esm/components/db-time-picker.js +1 -523
- package/dist/esm/components/db-timeline.js +1 -174
- package/dist/esm/components/db-toast.js +1 -158
- package/dist/esm/components/db-toggle.js +1 -162
- package/dist/esm/components/db-toolbar.js +1 -94
- package/dist/esm/components/db-tooltip.js +1 -440
- package/dist/esm/components/db-top-banner.js +1 -121
- package/dist/esm/components/db-tutorial.js +1 -324
- package/dist/esm/components/db-two-factor.d.ts +60 -0
- package/dist/esm/components/db-two-factor.js +1 -0
- package/dist/esm/components/db-video-player.js +1 -503
- package/dist/esm/components/db-youtube-player.js +1 -194
- package/dist/esm/components/highlight-code.js +1 -65
- package/dist/esm/components/input-validation.js +1 -50
- package/dist/esm/components/marketing-block-host-utils.js +1 -9
- package/dist/esm/components/number-scrub.js +1 -117
- package/dist/esm/components/random-gradient-palettes.js +1 -67
- package/dist/esm/components/section-block-header-utils.js +1 -48
- package/dist/esm/helpers/confirm.js +1 -52
- package/dist/esm/helpers/toast.js +1 -73
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +1 -111
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-bash.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-clike.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-css.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-javascript.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-json.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-jsx.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-markup.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-scss.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-tsx.js +1 -0
- package/dist/esm/node_modules/.pnpm/prismjs@1.30.0/node_modules/prismjs/components/prism-typescript.js +1 -0
- package/dist/index.cjs +1 -55414
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1091 -2
- package/dist/index.esm.js +1 -38284
- package/dist/react/chunks/lottie-14e7addb.js +1 -0
- package/dist/react/index.cjs +1 -56503
- package/dist/react/index.d.ts +1620 -54
- package/dist/react/index.esm.js +1 -39373
- package/package.json +1 -1
- package/dist/chunks/lottie-e273b936.js +0 -17032
- package/dist/esm/node_modules/prismjs/components/prism-bash.js +0 -235
- package/dist/esm/node_modules/prismjs/components/prism-clike.js +0 -31
- package/dist/esm/node_modules/prismjs/components/prism-css.js +0 -64
- package/dist/esm/node_modules/prismjs/components/prism-javascript.js +0 -172
- package/dist/esm/node_modules/prismjs/components/prism-json.js +0 -27
- package/dist/esm/node_modules/prismjs/components/prism-jsx.js +0 -143
- package/dist/esm/node_modules/prismjs/components/prism-markup.js +0 -186
- package/dist/esm/node_modules/prismjs/components/prism-scss.js +0 -81
- package/dist/esm/node_modules/prismjs/components/prism-tsx.js +0 -15
- package/dist/esm/node_modules/prismjs/components/prism-typescript.js +0 -60
- package/dist/react/chunks/lottie-72a4c6d4.js +0 -17032
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { DbIconName } from './db-icons.js';
|
|
2
|
+
import './db-button.js';
|
|
3
|
+
import './db-chip.js';
|
|
4
|
+
|
|
5
|
+
interface DbChatTool {
|
|
6
|
+
/** 고유 id (이벤트 detail 로 전달) */
|
|
7
|
+
id: string;
|
|
8
|
+
/** 표시 라벨 */
|
|
9
|
+
label: string;
|
|
10
|
+
/** 시작 아이콘 (DbIconName) */
|
|
11
|
+
icon?: DbIconName;
|
|
12
|
+
/** 활성 여부 */
|
|
13
|
+
active?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface DbChatAttachOption {
|
|
16
|
+
/** 고유 id (예: 'image', 'file', 'camera') */
|
|
17
|
+
id: string;
|
|
18
|
+
/** 표시 라벨 */
|
|
19
|
+
label: string;
|
|
20
|
+
/** 메뉴 아이콘 (DbIconName) */
|
|
21
|
+
icon?: DbIconName;
|
|
22
|
+
/** 파일 선택 시 accept 값. 지정하면 파일 선택창을 엽니다. 없으면 db-attach 이벤트만 발행 */
|
|
23
|
+
accept?: string;
|
|
24
|
+
/** 카메라/캡처 (모바일). 'user' | 'environment' */
|
|
25
|
+
capture?: 'user' | 'environment';
|
|
26
|
+
/** 다중 선택 허용 */
|
|
27
|
+
multiple?: boolean;
|
|
28
|
+
/** 보조 설명 (선택) */
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
interface DbChatAttachment {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
/** 바이트 크기 */
|
|
35
|
+
size: number;
|
|
36
|
+
/** MIME 타입 */
|
|
37
|
+
mime: string;
|
|
38
|
+
/** 표시 종류 (아이콘 결정) */
|
|
39
|
+
kind: 'image' | 'video' | 'file';
|
|
40
|
+
/** 원본 File (파일 선택 시) */
|
|
41
|
+
file?: File;
|
|
42
|
+
/** 이미지 미리보기 objectURL */
|
|
43
|
+
preview?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* AI 채팅 입력 컴포저. 채팅 시작 전/중 모두에서 사용합니다.
|
|
47
|
+
* 자동 높이 textarea + 툴바(첨부·툴 토글·음성·전송)로 구성되며,
|
|
48
|
+
* 전송 버튼은 `db-button`, 툴 토글·첨부 미리보기는 `db-chip` 을 재사용합니다.
|
|
49
|
+
*
|
|
50
|
+
* - 툴 토글: `db-chip` 의 `default`↔`primary` variant 로 선택 상태를 표현합니다.
|
|
51
|
+
* - 첨부: 첨부 버튼을 누르면 드롭다운이 열려 이미지·동영상·파일·카메라 등
|
|
52
|
+
* 여러 종류를 선택할 수 있고, 고른 파일은 삭제 가능한 칩으로 미리보기됩니다.
|
|
53
|
+
* - 전송 중(`streaming`)에는 전송 버튼이 정지 버튼으로 바뀝니다.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* const c = document.querySelector('db-chat-composer');
|
|
57
|
+
* c.tools = [{ id:'reason', label:'추론', icon:'bulb' }];
|
|
58
|
+
* c.addEventListener('db-send', (e) => console.log(e.detail.value, e.detail.attachments));
|
|
59
|
+
*/
|
|
60
|
+
declare class DbChatComposer extends HTMLElement {
|
|
61
|
+
static get observedAttributes(): string[];
|
|
62
|
+
private built;
|
|
63
|
+
private _tools;
|
|
64
|
+
private _attachOptions;
|
|
65
|
+
private _attachments;
|
|
66
|
+
private attachMenuOpen;
|
|
67
|
+
private field;
|
|
68
|
+
private attachmentsEl;
|
|
69
|
+
private toolsEl;
|
|
70
|
+
private attachWrap;
|
|
71
|
+
private attachBtn;
|
|
72
|
+
private attachMenu;
|
|
73
|
+
private fileInput;
|
|
74
|
+
private micBtn;
|
|
75
|
+
private sendBtn;
|
|
76
|
+
get value(): string;
|
|
77
|
+
set value(v: string);
|
|
78
|
+
get streaming(): boolean;
|
|
79
|
+
set streaming(v: boolean);
|
|
80
|
+
get disabled(): boolean;
|
|
81
|
+
set disabled(v: boolean);
|
|
82
|
+
/** 툴 토글 목록 (JSON 프로퍼티) */
|
|
83
|
+
get tools(): DbChatTool[];
|
|
84
|
+
set tools(list: DbChatTool[]);
|
|
85
|
+
/** 첨부 드롭다운 옵션 목록 */
|
|
86
|
+
get attachOptions(): DbChatAttachOption[];
|
|
87
|
+
set attachOptions(list: DbChatAttachOption[]);
|
|
88
|
+
/** 현재 첨부 목록 (읽기 전용 복사본) */
|
|
89
|
+
get attachments(): DbChatAttachment[];
|
|
90
|
+
connectedCallback(): void;
|
|
91
|
+
disconnectedCallback(): void;
|
|
92
|
+
attributeChangedCallback(name: string): void;
|
|
93
|
+
private build;
|
|
94
|
+
private makeIconButton;
|
|
95
|
+
private sync;
|
|
96
|
+
private renderTools;
|
|
97
|
+
private renderAttachMenu;
|
|
98
|
+
private renderAttachments;
|
|
99
|
+
private renderSendButton;
|
|
100
|
+
private syncSendState;
|
|
101
|
+
private autoGrow;
|
|
102
|
+
private handleAttachClick;
|
|
103
|
+
private openAttachMenu;
|
|
104
|
+
private closeAttachMenu;
|
|
105
|
+
private handleOutsideClick;
|
|
106
|
+
private chooseAttachOption;
|
|
107
|
+
private handleFilePicked;
|
|
108
|
+
private addFile;
|
|
109
|
+
private attachmentMeta;
|
|
110
|
+
private handleInput;
|
|
111
|
+
private handleKeyDown;
|
|
112
|
+
private handleSendClick;
|
|
113
|
+
private submit;
|
|
114
|
+
/** 입력 필드를 비웁니다. */
|
|
115
|
+
clear(): void;
|
|
116
|
+
/** 첨부를 하나 제거합니다. */
|
|
117
|
+
removeAttachment(id: string): void;
|
|
118
|
+
/** 모든 첨부를 제거합니다. */
|
|
119
|
+
clearAttachments(): void;
|
|
120
|
+
/** 프로그램적으로 첨부를 추가합니다(파일 또는 메타). */
|
|
121
|
+
addAttachment(input: File | Omit<DbChatAttachment, 'id' | 'kind'>): void;
|
|
122
|
+
/** 입력 필드에 포커스합니다. */
|
|
123
|
+
focusInput(): void;
|
|
124
|
+
/** 전송 중(정지 버튼) 상태를 설정합니다. */
|
|
125
|
+
setStreaming(streaming: boolean): void;
|
|
126
|
+
}
|
|
127
|
+
declare global {
|
|
128
|
+
interface HTMLElementTagNameMap {
|
|
129
|
+
'db-chat-composer': DbChatComposer;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { DbChatComposer };
|
|
134
|
+
export type { DbChatAttachOption, DbChatAttachment, DbChatTool };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isDbIconName as t,createDbIcon as e}from"./db-icons.js";import"./db-button.js";import"./db-chip.js";const s=[{id:"image",label:"사진·이미지",icon:"gallery",accept:"image/*",multiple:!0},{id:"video",label:"동영상",icon:"video",accept:"video/*",multiple:!0},{id:"file",label:"문서·파일",icon:"file-new",accept:"",multiple:!0},{id:"camera",label:"카메라 촬영",icon:"camera",accept:"image/*",capture:"environment"}];function kindFromMime(t){return t.startsWith("image/")?"image":t.startsWith("video/")?"video":"file"}const i={image:"gallery",video:"video",file:"file-new"};let a=0;class DbChatComposer extends HTMLElement{constructor(){super(...arguments),this.built=!1,this._tools=[],this._attachOptions=s,this._attachments=[],this.attachMenuOpen=!1,this.handleAttachClick=()=>{this.disabled||(this.attachMenuOpen?this.closeAttachMenu():this.openAttachMenu())},this.handleOutsideClick=t=>{this.attachMenuOpen&&(this.attachWrap.contains(t.target)||this.closeAttachMenu())},this.handleFilePicked=()=>{const t=Array.from(this.fileInput.files||[]);if(!t.length)return;const e=this.fileInput.dataset.type||"file";t.forEach(t=>this.addFile(t)),this.dispatchEvent(new CustomEvent("db-attach-files",{detail:{type:e,files:t},bubbles:!0,composed:!0}))},this.handleInput=()=>{this.autoGrow(),this.syncSendState(),this.dispatchEvent(new CustomEvent("db-input",{detail:{value:this.field.value},bubbles:!0,composed:!0}))},this.handleKeyDown=t=>{if("Escape"===t.key&&this.attachMenuOpen)return t.preventDefault(),void this.closeAttachMenu();"Enter"!==t.key||t.shiftKey||t.isComposing||(t.preventDefault(),this.submit())},this.handleSendClick=()=>{this.disabled||(this.streaming?this.dispatchEvent(new CustomEvent("db-stop",{bubbles:!0,composed:!0})):this.submit())}}static get observedAttributes(){return["placeholder","value","disabled","streaming","voice","attach","max-length","send-label"]}get value(){return this.field?.value??this.getAttribute("value")??""}set value(t){this.field&&(this.field.value=t,this.autoGrow(),this.syncSendState()),t?this.setAttribute("value",t):this.removeAttribute("value")}get streaming(){return this.hasAttribute("streaming")}set streaming(t){this.toggleAttribute("streaming",t)}get disabled(){return this.hasAttribute("disabled")}set disabled(t){this.toggleAttribute("disabled",t)}get tools(){return this._tools}set tools(t){this._tools=Array.isArray(t)?t:[],this.built&&this.renderTools()}get attachOptions(){return this._attachOptions}set attachOptions(t){this._attachOptions=Array.isArray(t)&&t.length?t:s,this.built&&this.renderAttachMenu()}get attachments(){return this._attachments.map(t=>({...t}))}connectedCallback(){this.built||this.build(),this.sync(),requestAnimationFrame(()=>this.autoGrow()),document.addEventListener("click",this.handleOutsideClick,!0)}disconnectedCallback(){document.removeEventListener("click",this.handleOutsideClick,!0),this._attachments.forEach(t=>t.preview&&URL.revokeObjectURL(t.preview))}attributeChangedCallback(t){if(this.built){if("value"===t){const t=this.getAttribute("value")||"";return this.field.value!==t&&(this.field.value=t,this.autoGrow()),void this.syncSendState()}"streaming"!==t?this.sync():this.renderSendButton()}}build(){this.built=!0,this.className="designbase-wc-chat-composer",this.replaceChildren();const t=document.createElement("div");t.className="designbase-wc-chat-composer__surface",this.attachmentsEl=document.createElement("div"),this.attachmentsEl.className="designbase-wc-chat-composer__attachments",this.attachmentsEl.hidden=!0;const e=document.createElement("div");e.className="designbase-wc-chat-composer__field-row",this.field=document.createElement("textarea"),this.field.className="designbase-wc-chat-composer__field",this.field.rows=1,this.field.addEventListener("input",this.handleInput),this.field.addEventListener("keydown",this.handleKeyDown),e.append(this.field);const s=document.createElement("div");s.className="designbase-wc-chat-composer__toolbar";const i=document.createElement("div");i.className="designbase-wc-chat-composer__leading",this.attachWrap=document.createElement("div"),this.attachWrap.className="designbase-wc-chat-composer__attach-wrap",this.attachBtn=document.createElement("db-button"),this.attachBtn.className="designbase-wc-chat-composer__attach",this.attachBtn.setAttribute("variant","ghost"),this.attachBtn.setAttribute("icon-only",""),this.attachBtn.setAttribute("radius","pill"),this.attachBtn.setAttribute("size","m"),this.attachBtn.setAttribute("start-icon","paperclip"),this.attachBtn.setAttribute("aria-label","첨부"),this.attachBtn.setAttribute("aria-haspopup","menu"),this.attachBtn.addEventListener("db-click",this.handleAttachClick),this.attachMenu=document.createElement("div"),this.attachMenu.className="designbase-wc-chat-composer__attach-menu",this.attachMenu.setAttribute("role","menu"),this.attachMenu.hidden=!0,this.attachWrap.append(this.attachBtn,this.attachMenu),this.fileInput=document.createElement("input"),this.fileInput.type="file",this.fileInput.className="designbase-wc-chat-composer__file-input",this.fileInput.hidden=!0,this.fileInput.addEventListener("change",this.handleFilePicked),this.toolsEl=document.createElement("div"),this.toolsEl.className="designbase-wc-chat-composer__tools",i.append(this.attachWrap,this.toolsEl);const a=document.createElement("div");a.className="designbase-wc-chat-composer__trailing",this.micBtn=this.makeIconButton("microphone","음성 입력","db-voice"),this.micBtn.classList.add("designbase-wc-chat-composer__mic"),this.sendBtn=document.createElement("db-button"),this.sendBtn.className="designbase-wc-chat-composer__send",this.sendBtn.setAttribute("icon-only",""),this.sendBtn.setAttribute("radius","pill"),this.sendBtn.setAttribute("size","m"),this.sendBtn.addEventListener("db-click",this.handleSendClick),a.append(this.micBtn,this.sendBtn),s.append(i,a),t.append(this.attachmentsEl,e,s),this.append(t,this.fileInput),this.renderTools(),this.renderAttachMenu(),this.renderSendButton()}makeIconButton(t,e,s){const i=document.createElement("db-button");return i.setAttribute("variant","ghost"),i.setAttribute("icon-only",""),i.setAttribute("radius","pill"),i.setAttribute("size","m"),i.setAttribute("start-icon",t),i.setAttribute("aria-label",e),i.addEventListener("db-click",()=>{this.disabled||this.dispatchEvent(new CustomEvent(s,{bubbles:!0,composed:!0}))}),i}sync(){this.classList.toggle("designbase-wc-chat-composer--disabled",this.disabled),this.classList.toggle("designbase-wc-chat-composer--streaming",this.streaming),this.field.placeholder=this.getAttribute("placeholder")||"메시지를 입력하세요…",this.field.disabled=this.disabled;const t=this.getAttribute("max-length");t?this.field.maxLength=Number(t):this.field.removeAttribute("maxlength"),this.attachWrap.hidden=!this.hasAttribute("attach"),this.micBtn.hidden=!this.hasAttribute("voice"),this.hasAttribute("attach")||this.closeAttachMenu(),this.autoGrow(),this.syncSendState()}renderTools(){this.toolsEl.replaceChildren();for(const e of this._tools){const s=document.createElement("db-chip");s.setAttribute("label",e.label),s.setAttribute("size","s"),s.setAttribute("clickable",""),s.setAttribute("variant",e.active?"primary":"default"),e.icon&&t(e.icon)&&s.setAttribute("start-icon",e.icon),s.setAttribute("aria-pressed",String(!!e.active)),s.addEventListener("db-click",()=>{this.disabled||(e.active=!e.active,s.setAttribute("variant",e.active?"primary":"default"),s.setAttribute("aria-pressed",String(!!e.active)),this.dispatchEvent(new CustomEvent("db-tool-toggle",{detail:{id:e.id,active:!!e.active},bubbles:!0,composed:!0})))}),this.toolsEl.append(s)}}renderAttachMenu(){this.attachMenu.replaceChildren();for(const s of this._attachOptions){const i=document.createElement("button");i.type="button",i.className="designbase-wc-chat-composer__attach-item",i.setAttribute("role","menuitem");const a=document.createElement("span");a.className="designbase-wc-chat-composer__attach-item-icon",s.icon&&t(s.icon)&&a.append(e(s.icon,18));const n=document.createElement("span");n.className="designbase-wc-chat-composer__attach-item-text";const c=document.createElement("span");if(c.className="designbase-wc-chat-composer__attach-item-label",c.textContent=s.label,n.append(c),s.description){const t=document.createElement("span");t.className="designbase-wc-chat-composer__attach-item-desc",t.textContent=s.description,n.append(t)}i.append(a,n),i.addEventListener("click",()=>this.chooseAttachOption(s)),this.attachMenu.append(i)}}renderAttachments(){this.attachmentsEl.replaceChildren(),this.attachmentsEl.hidden=0===this._attachments.length;for(const t of this._attachments){const e=document.createElement("db-chip");e.setAttribute("label",t.name),e.setAttribute("size","s"),e.setAttribute("variant","neutral"),e.setAttribute("deletable",""),e.setAttribute("start-icon",i[t.kind]),e.addEventListener("db-delete",()=>this.removeAttachment(t.id)),this.attachmentsEl.append(e)}this.syncSendState()}renderSendButton(){const t=this.streaming;this.sendBtn.setAttribute("variant",t?"secondary":"primary"),this.sendBtn.setAttribute("start-icon",t?"stop":"send"),this.sendBtn.setAttribute("aria-label",t?"생성 중지":"전송"),this.syncSendState()}syncSendState(){if(!this.sendBtn)return;const t=0===this.value.trim().length&&0===this._attachments.length,e=this.disabled||!this.streaming&&t;this.sendBtn.toggleAttribute("disabled",e)}autoGrow(){this.field&&0!==this.field.clientWidth&&(this.field.style.height="auto",this.field.style.height=`${Math.min(this.field.scrollHeight,200)}px`)}openAttachMenu(){this.attachMenuOpen=!0,this.attachMenu.hidden=!1,this.attachBtn.setAttribute("aria-expanded","true"),this.classList.add("designbase-wc-chat-composer--attach-open"),requestAnimationFrame(()=>this.attachMenu.classList.add("designbase-wc-chat-composer__attach-menu--visible"))}closeAttachMenu(){this.attachMenuOpen&&(this.attachMenuOpen=!1,this.attachMenu.hidden=!0,this.attachMenu.classList.remove("designbase-wc-chat-composer__attach-menu--visible"),this.attachBtn.setAttribute("aria-expanded","false"),this.classList.remove("designbase-wc-chat-composer--attach-open"))}chooseAttachOption(t){this.closeAttachMenu(),this.dispatchEvent(new CustomEvent("db-attach",{detail:{type:t.id},bubbles:!0,composed:!0})),void 0!==t.accept&&(this.fileInput.value="",this.fileInput.accept=t.accept,this.fileInput.multiple=!!t.multiple,t.capture?this.fileInput.setAttribute("capture",t.capture):this.fileInput.removeAttribute("capture"),this.fileInput.dataset.type=t.id,this.fileInput.click())}addFile(t){const e=kindFromMime(t.type),s={id:`att-${a+=1}`,name:t.name,size:t.size,mime:t.type,kind:e,file:t,preview:"image"===e?URL.createObjectURL(t):void 0};this._attachments.push(s),this.renderAttachments(),this.dispatchEvent(new CustomEvent("db-attachment-add",{detail:{attachment:this.attachmentMeta(s)},bubbles:!0,composed:!0}))}attachmentMeta(t){return{id:t.id,name:t.name,size:t.size,mime:t.mime,kind:t.kind,file:t.file}}submit(){const t=this.field.value.trim();if(!t&&0===this._attachments.length||this.disabled||this.streaming)return;const e=new CustomEvent("db-send",{detail:{value:t,attachments:this._attachments.map(t=>this.attachmentMeta(t))},bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(e)&&(this.clear(),this.clearAttachments())}clear(){this.value=""}removeAttachment(t){const e=this._attachments.find(e=>e.id===t);e&&(e.preview&&URL.revokeObjectURL(e.preview),this._attachments=this._attachments.filter(e=>e.id!==t),this.renderAttachments(),this.dispatchEvent(new CustomEvent("db-attachment-remove",{detail:{id:t},bubbles:!0,composed:!0})))}clearAttachments(){this._attachments.forEach(t=>t.preview&&URL.revokeObjectURL(t.preview)),this._attachments=[],this.renderAttachments()}addAttachment(t){if(t instanceof File)return void this.addFile(t);const e=kindFromMime(t.mime);this._attachments.push({id:`att-${a+=1}`,kind:e,...t}),this.renderAttachments()}focusInput(){this.field?.focus()}setStreaming(t){this.streaming=t}}customElements.get("db-chat-composer")||customElements.define("db-chat-composer",DbChatComposer);export{DbChatComposer};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import './db-avatar.js';
|
|
2
|
+
|
|
3
|
+
interface DbChatListItem {
|
|
4
|
+
id: string;
|
|
5
|
+
/** 상대(대화방) 이름 */
|
|
6
|
+
name: string;
|
|
7
|
+
avatarSrc?: string;
|
|
8
|
+
avatarInitials?: string;
|
|
9
|
+
/** 마지막 메시지 미리보기 */
|
|
10
|
+
preview?: string;
|
|
11
|
+
/** 시간 라벨 (예: '방금', '어제') */
|
|
12
|
+
time?: string;
|
|
13
|
+
/** 안 읽은 수 */
|
|
14
|
+
unread?: number;
|
|
15
|
+
/** 온라인 표시 */
|
|
16
|
+
online?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 사람 간 대화(메신저) 목록. 아바타 + 이름 + 미리보기 + 시간 + 안읽음 배지의 행 리스트로,
|
|
20
|
+
* 아바타는 `db-avatar` 를 재사용합니다. AI 대화 이력은 `db-ai-chat-list` 를 쓰세요.
|
|
21
|
+
*
|
|
22
|
+
* @fires db-select - 대화 선택 (detail: { id })
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const list = document.querySelector('db-chat-list');
|
|
26
|
+
* list.items = [{ id:'1', name:'디지털 상담원', preview:'무엇을 도와드릴까요?', time:'방금', unread:1, online:true }];
|
|
27
|
+
* list.addEventListener('db-select', (e) => console.log(e.detail.id));
|
|
28
|
+
*/
|
|
29
|
+
declare class DbChatList extends HTMLElement {
|
|
30
|
+
static get observedAttributes(): string[];
|
|
31
|
+
private _items;
|
|
32
|
+
get items(): DbChatListItem[];
|
|
33
|
+
set items(v: DbChatListItem[]);
|
|
34
|
+
get selectedId(): string;
|
|
35
|
+
set selectedId(v: string);
|
|
36
|
+
connectedCallback(): void;
|
|
37
|
+
attributeChangedCallback(): void;
|
|
38
|
+
private render;
|
|
39
|
+
}
|
|
40
|
+
declare global {
|
|
41
|
+
interface HTMLElementTagNameMap {
|
|
42
|
+
'db-chat-list': DbChatList;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { DbChatList };
|
|
47
|
+
export type { DbChatListItem };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./db-avatar.js";class DbChatList extends HTMLElement{constructor(){super(...arguments),this._items=[]}static get observedAttributes(){return["selected-id"]}get items(){return this._items}set items(t){this._items=Array.isArray(t)?t:[],this.render()}get selectedId(){return this.getAttribute("selected-id")||""}set selectedId(t){t?this.setAttribute("selected-id",t):this.removeAttribute("selected-id")}connectedCallback(){if(!this._items.length&&this.hasAttribute("items"))try{this._items=JSON.parse(this.getAttribute("items")||"[]")}catch{this._items=[]}this.render()}attributeChangedCallback(){this.render()}render(){this.className="designbase-wc-chat-list",this.setAttribute("role","list"),this.replaceChildren();const t=this.selectedId;for(const e of this._items){const s=document.createElement("button");s.type="button",s.className=["designbase-wc-chat-list__item",e.id===t&&"designbase-wc-chat-list__item--selected"].filter(Boolean).join(" "),s.setAttribute("role","listitem");const a=document.createElement("db-avatar");a.className="designbase-wc-chat-list__avatar",a.setAttribute("size","l"),a.setAttribute("variant","primary"),e.avatarSrc?a.setAttribute("src",e.avatarSrc):e.avatarInitials&&a.setAttribute("initials",e.avatarInitials),e.online&&a.setAttribute("status","online");const i=document.createElement("span");i.className="designbase-wc-chat-list__body";const n=document.createElement("span");n.className="designbase-wc-chat-list__top";const c=document.createElement("span");if(c.className="designbase-wc-chat-list__name",c.textContent=e.name,n.append(c),e.time){const t=document.createElement("span");t.className="designbase-wc-chat-list__time",t.textContent=e.time,n.append(t)}const r=document.createElement("span");r.className="designbase-wc-chat-list__bottom";const d=document.createElement("span");if(d.className="designbase-wc-chat-list__preview",d.textContent=e.preview||"",r.append(d),e.unread&&e.unread>0){const t=document.createElement("span");t.className="designbase-wc-chat-list__unread",t.textContent=e.unread>99?"99+":String(e.unread),r.append(t)}i.append(n,r),s.append(a,i),s.addEventListener("click",()=>{this.selectedId=e.id,this.dispatchEvent(new CustomEvent("db-select",{detail:{id:e.id},bubbles:!0,composed:!0}))}),this.append(s)}}}customElements.get("db-chat-list")||customElements.define("db-chat-list",DbChatList);export{DbChatList};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import './db-avatar.js';
|
|
2
|
+
|
|
3
|
+
declare const ROLES: readonly ["user", "assistant", "system"];
|
|
4
|
+
type Role = (typeof ROLES)[number];
|
|
5
|
+
/**
|
|
6
|
+
* 채팅 메시지 한 줄. 본문은 라이트 DOM 자식으로 전달하며 컴포넌트가 감쌉니다.
|
|
7
|
+
* 아바타는 `db-avatar` 를 재사용합니다.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <db-chat-message role="assistant" name="Orion" actions="copy,regenerate,like,dislike">
|
|
11
|
+
* 안녕하세요! 무엇을 도와드릴까요?
|
|
12
|
+
* </db-chat-message>
|
|
13
|
+
*/
|
|
14
|
+
declare class DbChatMessage extends HTMLElement {
|
|
15
|
+
static get observedAttributes(): string[];
|
|
16
|
+
private built;
|
|
17
|
+
private contentEl;
|
|
18
|
+
private avatarWrap;
|
|
19
|
+
private headerEl;
|
|
20
|
+
private nameEl;
|
|
21
|
+
private timeEl;
|
|
22
|
+
private actionsEl;
|
|
23
|
+
get role(): Role;
|
|
24
|
+
set role(v: Role);
|
|
25
|
+
connectedCallback(): void;
|
|
26
|
+
attributeChangedCallback(name: string): void;
|
|
27
|
+
private build;
|
|
28
|
+
private sync;
|
|
29
|
+
private renderAvatar;
|
|
30
|
+
private renderActions;
|
|
31
|
+
private handleAction;
|
|
32
|
+
}
|
|
33
|
+
declare global {
|
|
34
|
+
interface HTMLElementTagNameMap {
|
|
35
|
+
'db-chat-message': DbChatMessage;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { DbChatMessage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createDbIcon as t,isDbIconName as e}from"./db-icons.js";import"./db-avatar.js";const s=["user","assistant","system"],a={copy:"copy",regenerate:"refresh",like:"thumb-up",dislike:"thumb-down",edit:"edit"},i={copy:"복사",regenerate:"다시 생성",like:"좋아요",dislike:"싫어요",edit:"편집"};class DbChatMessage extends HTMLElement{constructor(){super(...arguments),this.built=!1}static get observedAttributes(){return["role","name","avatar-src","avatar-initials","timestamp","status","actions","variant"]}get role(){return s.includes(this.getAttribute("role"))?this.getAttribute("role"):"assistant"}set role(t){this.setAttribute("role",t)}connectedCallback(){this.built||this.build(),this.sync()}attributeChangedCallback(t){this.built&&("actions"===t&&this.renderActions(),this.sync())}build(){for(this.built=!0,this.contentEl=document.createElement("div"),this.contentEl.className="designbase-wc-chat-message__content";this.firstChild;)this.contentEl.append(this.firstChild);this.avatarWrap=document.createElement("div"),this.avatarWrap.className="designbase-wc-chat-message__avatar";const t=document.createElement("div");t.className="designbase-wc-chat-message__bubble",this.headerEl=document.createElement("div"),this.headerEl.className="designbase-wc-chat-message__header",this.nameEl=document.createElement("span"),this.nameEl.className="designbase-wc-chat-message__name",this.timeEl=document.createElement("span"),this.timeEl.className="designbase-wc-chat-message__time",this.headerEl.append(this.nameEl,this.timeEl),this.actionsEl=document.createElement("div"),this.actionsEl.className="designbase-wc-chat-message__actions",t.append(this.headerEl,this.contentEl,this.actionsEl),this.append(this.avatarWrap,t),this.renderAvatar(),this.renderActions()}sync(){this.className=["designbase-wc-chat-message",`designbase-wc-chat-message--${this.role}`,`designbase-wc-chat-message--${this.getAttribute("variant")||"bubble"}`,this.getAttribute("status")&&`designbase-wc-chat-message--${this.getAttribute("status")}`].filter(Boolean).join(" ");const t=this.getAttribute("name")||"";this.nameEl.textContent=t,this.nameEl.hidden=!t;const e=this.getAttribute("timestamp")||"";this.timeEl.textContent=e,this.timeEl.hidden=!e,this.headerEl.hidden=!t&&!e,this.renderAvatar()}renderAvatar(){const t=this.getAttribute("avatar-src"),e=this.getAttribute("avatar-initials"),s="assistant"===this.role||Boolean(t||e);if(this.avatarWrap.hidden=!s,!s)return void this.avatarWrap.replaceChildren();let a=this.avatarWrap.querySelector("db-avatar");a||(a=document.createElement("db-avatar"),this.avatarWrap.append(a)),a.setAttribute("size","s"),t?a.setAttribute("src",t):a.removeAttribute("src"),e?a.setAttribute("initials",e):a.removeAttribute("initials"),"assistant"!==this.role||t||e||a.setAttribute("variant","primary")}renderActions(){this.actionsEl.replaceChildren();const s=(this.getAttribute("actions")||"").split(",").map(t=>t.trim()).filter(Boolean);for(const n of s){const s=document.createElement("button");s.type="button",s.className="designbase-wc-chat-message__action",s.setAttribute("aria-label",i[n]||n);const r=a[n];s.append(t(r&&e(r)?r:"sparkle",16)),s.addEventListener("click",()=>this.handleAction(n,s)),this.actionsEl.append(s)}this.actionsEl.hidden=0===s.length}handleAction(t,e){if("copy"===t){const t=this.contentEl.textContent||"";navigator.clipboard?.writeText(t).catch(()=>{})}"like"!==t&&"dislike"!==t||e.classList.toggle("designbase-wc-chat-message__action--active"),this.dispatchEvent(new CustomEvent("db-action",{detail:{action:t,text:this.contentEl.textContent||""},bubbles:!0,composed:!0}))}}customElements.get("db-chat-message")||customElements.define("db-chat-message",DbChatMessage);export{DbChatMessage};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
declare const SIZES: readonly ["xs", "s", "m", "l", "xl"];
|
|
2
|
+
declare const VARIANTS: readonly ["aurora", "ocean", "emerald", "violet", "mono"];
|
|
3
|
+
declare const STATES: readonly ["idle", "thinking", "listening"];
|
|
4
|
+
declare const THEMES: readonly ["auto", "light", "dark"];
|
|
5
|
+
type OrbSize = (typeof SIZES)[number];
|
|
6
|
+
type OrbVariant = (typeof VARIANTS)[number];
|
|
7
|
+
type OrbState = (typeof STATES)[number];
|
|
8
|
+
type OrbTheme = (typeof THEMES)[number];
|
|
9
|
+
/**
|
|
10
|
+
* AI 어시스턴트를 상징하는 캔버스 기반 애니메이션 오브(구체).
|
|
11
|
+
* 광택 있는 3D 구체 위에 유체처럼 흐르는 내부 광원 + 프레넬 림 라이트 + 스페큘러 하이라이트로
|
|
12
|
+
* 고품질 오브를 렌더링합니다. `state` 에 따라 모양(진폭·난류)·색(밝기)·인터랙션(리플)이 바뀌며,
|
|
13
|
+
* 포인터를 올리면 하이라이트와 내부 광원이 살짝 따라옵니다. 순수 장식이라 `aria-hidden` 입니다.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <db-chat-orb variant="aurora" state="thinking"></db-chat-orb>
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const orb = document.querySelector('db-chat-orb');
|
|
20
|
+
* orb.state = 'listening';
|
|
21
|
+
* orb.minScale = 0.86;
|
|
22
|
+
* orb.maxScale = 1.2;
|
|
23
|
+
* orb.audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
24
|
+
*/
|
|
25
|
+
declare class DbChatOrb extends HTMLElement {
|
|
26
|
+
static get observedAttributes(): string[];
|
|
27
|
+
private canvas;
|
|
28
|
+
private ctx;
|
|
29
|
+
private dpr;
|
|
30
|
+
private cssW;
|
|
31
|
+
private cssH;
|
|
32
|
+
private t0;
|
|
33
|
+
private lastFrame;
|
|
34
|
+
private resizeObs;
|
|
35
|
+
private io;
|
|
36
|
+
private themeObserver;
|
|
37
|
+
private themeMedia;
|
|
38
|
+
private visible;
|
|
39
|
+
private hover;
|
|
40
|
+
private ripples;
|
|
41
|
+
private lastAutoRipple;
|
|
42
|
+
private lastVoiceRipple;
|
|
43
|
+
private previousVoiceLevel;
|
|
44
|
+
private displayedVoiceLevel;
|
|
45
|
+
private inputStream;
|
|
46
|
+
private audioContext;
|
|
47
|
+
private audioSource;
|
|
48
|
+
private analyser;
|
|
49
|
+
private analyserData;
|
|
50
|
+
get size(): OrbSize;
|
|
51
|
+
set size(v: OrbSize);
|
|
52
|
+
get variant(): OrbVariant;
|
|
53
|
+
set variant(v: OrbVariant);
|
|
54
|
+
get state(): OrbState;
|
|
55
|
+
set state(v: OrbState);
|
|
56
|
+
get theme(): OrbTheme;
|
|
57
|
+
set theme(v: OrbTheme);
|
|
58
|
+
/** 외부 오디오 분석기가 전달하는 정규화 음성 레벨(0–1). */
|
|
59
|
+
get voiceLevel(): number;
|
|
60
|
+
set voiceLevel(v: number);
|
|
61
|
+
/** listening 무음 구간의 최소 구체 배율. */
|
|
62
|
+
get minScale(): number;
|
|
63
|
+
set minScale(v: number);
|
|
64
|
+
/** listening 최대 음량 구간의 최대 구체 배율. */
|
|
65
|
+
get maxScale(): number;
|
|
66
|
+
set maxScale(v: number);
|
|
67
|
+
/**
|
|
68
|
+
* 실제 마이크/통화 MediaStream. 내부 AnalyserNode로 RMS를 계산하며 오디오를 재생하거나
|
|
69
|
+
* 전달받은 트랙을 stop()하지 않습니다. null을 할당하면 분석만 해제합니다.
|
|
70
|
+
*/
|
|
71
|
+
get audioStream(): MediaStream | null;
|
|
72
|
+
set audioStream(stream: MediaStream | null);
|
|
73
|
+
connectedCallback(): void;
|
|
74
|
+
disconnectedCallback(): void;
|
|
75
|
+
attributeChangedCallback(name: string): void;
|
|
76
|
+
private setVisible;
|
|
77
|
+
private syncClasses;
|
|
78
|
+
private build;
|
|
79
|
+
private onPointerMove;
|
|
80
|
+
private onPointerLeave;
|
|
81
|
+
private onPointerDown;
|
|
82
|
+
private addRipple;
|
|
83
|
+
private observeTheme;
|
|
84
|
+
private onThemeChange;
|
|
85
|
+
private isDarkContext;
|
|
86
|
+
private startAudioAnalysis;
|
|
87
|
+
private stopAudioAnalysis;
|
|
88
|
+
private sampleVoiceLevel;
|
|
89
|
+
private resize;
|
|
90
|
+
/** 공유 틱커가 호출 — 한 프레임 렌더 */
|
|
91
|
+
frame(now: number): void;
|
|
92
|
+
}
|
|
93
|
+
declare global {
|
|
94
|
+
interface HTMLElementTagNameMap {
|
|
95
|
+
'db-chat-orb': DbChatOrb;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { DbChatOrb };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=["xs","s","m","l","xl"],e=["aurora","ocean","emerald","violet","mono"],i=["idle","thinking","listening"],s=["auto","light","dark"];function pick(t,e,i){return t.includes(e)?e:i}const a={aurora:{core:[22,20,52],lights:[[236,92,220],[70,140,250],[42,208,158]],rim:[199,210,254],glow:[120,110,240]},ocean:{core:[10,32,62],lights:[[56,189,248],[34,211,238],[96,165,250]],rim:[165,243,252],glow:[59,130,246]},emerald:{core:[6,42,38],lights:[[52,211,153],[74,222,128],[163,230,53]],rim:[187,247,208],glow:[34,197,94]},violet:{core:[40,12,82],lights:[[192,132,252],[168,85,247],[240,171,252]],rim:[245,208,254],glow:[139,92,246]},mono:{core:[30,34,44],lights:[[229,231,235],[156,163,175],[249,250,251]],rim:[255,255,255],glow:[156,163,175]}},r={aurora:{core:[240,240,250],lights:[[235,96,224],[74,144,246],[26,196,150]],rim:[150,130,210],glow:[138,128,244]},ocean:{core:[226,248,255],lights:[[14,165,233],[34,211,238],[96,165,250]],rim:[64,150,181],glow:[56,189,248]},emerald:{core:[228,250,240],lights:[[16,185,129],[74,222,128],[163,230,53]],rim:[57,145,111],glow:[52,211,153]},violet:{core:[246,233,255],lights:[[168,85,247],[217,70,239],[240,171,252]],rim:[143,91,183],glow:[192,132,252]},mono:{core:[243,246,250],lights:[[148,163,184],[203,213,225],[255,255,255]],rim:[100,116,139],glow:[148,163,184]}},n={idle:{speed:1.55,amp:.38,turb:.95,glow:1.15,breatheSpeed:1.9,breatheAmt:.055,lightAlpha:.95,autoRipple:0},thinking:{speed:3.2,amp:.5,turb:1.45,glow:1.85,breatheSpeed:4.2,breatheAmt:.075,lightAlpha:1.35,autoRipple:0},listening:{speed:1.45,amp:.32,turb:.9,glow:1.2,breatheSpeed:2.1,breatheAmt:.035,lightAlpha:.95,autoRipple:1.7}};function rgba(t,e){return`rgba(${t[0]},${t[1]},${t[2]},${e})`}function mix(t,e,i){return[t[0]+(e[0]-t[0])*i,t[1]+(e[1]-t[1])*i,t[2]+(e[2]-t[2])*i]}function clamp(t,e,i){return Math.min(i,Math.max(e,t))}function numberAttribute(t,e,i,s,a){const r=t.getAttribute(e);if(null===r||""===r.trim())return i;const n=Number(r);return Number.isFinite(n)?clamp(n,s,a):i}const h=2*Math.PI,o="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,l=new Set;let c=0;function tick(t){l.forEach(e=>e.frame(t)),c=l.size?requestAnimationFrame(tick):0}class DbChatOrb extends HTMLElement{constructor(){super(...arguments),this.dpr=1,this.cssW=0,this.cssH=0,this.t0=0,this.lastFrame=0,this.resizeObs=null,this.io=null,this.themeObserver=null,this.themeMedia=null,this.visible=!1,this.hover={x:0,y:0,tx:0,ty:0},this.ripples=[],this.lastAutoRipple=0,this.lastVoiceRipple=0,this.previousVoiceLevel=0,this.displayedVoiceLevel=0,this.inputStream=null,this.audioContext=null,this.audioSource=null,this.analyser=null,this.analyserData=null,this.onPointerMove=t=>{const e=this.getBoundingClientRect();e.width&&(this.hover.tx=Math.max(-1,Math.min(1,2*((t.clientX-e.left)/e.width-.5))),this.hover.ty=Math.max(-1,Math.min(1,2*((t.clientY-e.top)/e.height-.5))))},this.onPointerLeave=()=>{this.hover.tx=0,this.hover.ty=0},this.onPointerDown=()=>{this.addRipple(performance.now(),1)},this.onThemeChange=()=>{o&&this.frame(performance.now())}}static get observedAttributes(){return["size","variant","state","theme","voice-level","min-scale","max-scale"]}get size(){return pick(t,this.getAttribute("size"),"l")}set size(t){this.setAttribute("size",t)}get variant(){return pick(e,this.getAttribute("variant"),"aurora")}set variant(t){this.setAttribute("variant",t)}get state(){return pick(i,this.getAttribute("state"),"idle")}set state(t){this.setAttribute("state",t)}get theme(){return pick(s,this.getAttribute("theme"),"auto")}set theme(t){this.setAttribute("theme",t)}get voiceLevel(){return numberAttribute(this,"voice-level",0,0,1)}set voiceLevel(t){const e=Number(t);this.setAttribute("voice-level",String(clamp(Number.isFinite(e)?e:0,0,1)))}get minScale(){return numberAttribute(this,"min-scale",.88,.5,1.5)}set minScale(t){const e=Number(t);this.setAttribute("min-scale",String(clamp(Number.isFinite(e)?e:.88,.5,1.5)))}get maxScale(){return Math.max(this.minScale,numberAttribute(this,"max-scale",1.18,.5,1.7))}set maxScale(t){const e=Number(t);this.setAttribute("max-scale",String(clamp(Number.isFinite(e)?e:1.18,.5,1.7)))}get audioStream(){return this.inputStream}set audioStream(t){(t!==this.inputStream||null!==t&&!this.analyser)&&(this.stopAudioAnalysis(),this.inputStream=t&&"function"==typeof t.getAudioTracks?t:null,this.isConnected&&this.inputStream&&this.startAudioAnalysis(this.inputStream))}connectedCallback(){this.syncClasses(),this.setAttribute("aria-hidden","true"),this.canvas||this.build(),this.t0=performance.now(),this.lastFrame=0,this.observeTheme(),this.inputStream&&this.startAudioAnalysis(this.inputStream),"undefined"!=typeof ResizeObserver&&(this.resizeObs=new ResizeObserver(()=>this.resize()),this.resizeObs.observe(this)),this.resize(),o?this.frame(this.t0+1600):"undefined"!=typeof IntersectionObserver?(this.io=new IntersectionObserver(t=>{for(const e of t)this.setVisible(e.isIntersecting)},{threshold:.01}),this.io.observe(this)):this.setVisible(!0)}disconnectedCallback(){this.setVisible(!1),this.resizeObs?.disconnect(),this.resizeObs=null,this.io?.disconnect(),this.io=null,this.themeObserver?.disconnect(),this.themeObserver=null,this.themeMedia?.removeEventListener("change",this.onThemeChange),this.themeMedia=null,this.stopAudioAnalysis()}attributeChangedCallback(t){this.canvas&&("size"!==t&&"variant"!==t&&"state"!==t||this.syncClasses(),o&&this.frame(performance.now()))}setVisible(t){t!==this.visible&&(this.visible=t,t?(l.add(this),function ensureTicking(){c||!l.size||o||(c=requestAnimationFrame(tick))}()):l.delete(this))}syncClasses(){this.className=["designbase-wc-chat-orb",`designbase-wc-chat-orb--${this.size}`,`designbase-wc-chat-orb--${this.variant}`,`designbase-wc-chat-orb--${this.state}`].join(" ")}build(){this.replaceChildren(),this.canvas=document.createElement("canvas"),this.canvas.className="designbase-wc-chat-orb__canvas",this.ctx=this.canvas.getContext("2d"),this.append(this.canvas),this.addEventListener("pointermove",this.onPointerMove),this.addEventListener("pointerleave",this.onPointerLeave),this.addEventListener("pointerdown",this.onPointerDown)}addRipple(t,e){this.ripples.push({start:t,strength:clamp(e,.2,1)})}observeTheme(){"undefined"==typeof MutationObserver||this.themeObserver||(this.themeObserver=new MutationObserver(this.onThemeChange),this.themeObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme","data-color-mode","class"]})),"function"!=typeof window.matchMedia||this.themeMedia||(this.themeMedia=window.matchMedia("(prefers-color-scheme: dark)"),this.themeMedia.addEventListener("change",this.onThemeChange))}isDarkContext(){if("dark"===this.theme)return!0;if("light"===this.theme)return!1;const t=this.parentElement?.closest("[data-theme], [data-color-mode]"),e=t?.getAttribute("data-color-mode");if("dark"===e)return!0;if("light"===e)return!1;const i=t?.getAttribute("data-theme");if("dark"===i)return!0;if("light"===i)return!1;const s=document.documentElement,a=s.getAttribute("data-color-mode");if("dark"===a)return!0;if("light"===a)return!1;const r=s.getAttribute("data-theme");return"dark"===r||"light"!==r&&(this.themeMedia?.matches??!1)}startAudioAnalysis(t){if(this.analyser)return;const e=window.AudioContext??window.webkitAudioContext;if(e)try{const i=new e,s=i.createAnalyser();s.fftSize=512,s.smoothingTimeConstant=.58;const a=i.createMediaStreamSource(t);a.connect(s),this.audioContext=i,this.audioSource=a,this.analyser=s,this.analyserData=new Float32Array(s.fftSize),"suspended"===i.state&&i.resume().catch(()=>{})}catch{this.stopAudioAnalysis()}}stopAudioAnalysis(){try{this.audioSource?.disconnect(),this.analyser?.disconnect()}catch{}this.audioContext&&this.audioContext.close().catch(()=>{}),this.audioContext=null,this.audioSource=null,this.analyser=null,this.analyserData=null}sampleVoiceLevel(t){if(this.analyser&&this.analyserData){this.analyser.getFloatTimeDomainData(this.analyserData);let t=0;for(const e of this.analyserData)t+=e*e;return{level:clamp((Math.sqrt(t/this.analyserData.length)-.01)/.11,0,1),hasInput:!0}}if(this.hasAttribute("voice-level"))return{level:this.voiceLevel,hasInput:!0};return{level:clamp(.16+.12*(Math.sin(2.05*t)+1)+.42*Math.pow(Math.max(0,Math.sin(.86*t+.7)),5),0,1),hasInput:!1}}resize(){this.canvas&&(this.dpr=Math.min(2,window.devicePixelRatio||1),this.cssW=this.clientWidth||72,this.cssH=this.clientHeight||this.cssW,this.canvas.width=Math.round(this.cssW*this.dpr),this.canvas.height=Math.round(this.cssH*this.dpr),o&&this.frame(this.t0+1600))}frame(t){const e=this.ctx;if(!e||!this.cssW)return;const i=this.lastFrame?Math.min((t-this.lastFrame)/1e3,.1):1/60;this.lastFrame=t;const s=(t-this.t0)/1e3,o=this.cssW,l=this.cssH,c=o/2,d=l/2,m=Math.min(o,l)/2,u=this.isDarkContext(),p=u?a[this.variant]:r[this.variant],g=n[this.state];this.hover.x+=.08*(this.hover.tx-this.hover.x),this.hover.y+=.08*(this.hover.ty-this.hover.y);const b="listening"===this.state?this.sampleVoiceLevel(s):{level:0,hasInput:!1},v=b.level>this.displayedVoiceLevel?19:7;this.displayedVoiceLevel+=(b.level-this.displayedVoiceLevel)*(1-Math.exp(-v*i)),"listening"!==this.state&&this.displayedVoiceLevel<.001&&(this.displayedVoiceLevel=0);const f=Math.pow(clamp(this.displayedVoiceLevel,0,1),.72),y="listening"===this.state?this.minScale+(this.maxScale-this.minScale)*f:1,w=g.breatheAmt*("listening"===this.state?.55:1),A=1+Math.sin(s*g.breatheSpeed)*w,S="listening"===this.state?f:"thinking"===this.state?1:.55,M="thinking"===this.state?1.38:1,C=.68*m*A*y*("thinking"===this.state?1.1:1),x=.99*m;e.setTransform(this.dpr,0,0,this.dpr,0,0),e.clearRect(0,0,o,l),e.globalCompositeOperation="source-over",e.filter="none";const k="thinking"===this.state?u?.42:.22:u?.28:.5,O=u?mix(p.core,p.lights[0],k):"thinking"===this.state?mix(p.core,p.lights[0],.35):mix(p.core,[255,255,255],.5),z=mix(p.core,p.lights[1],u?"thinking"===this.state?.35:0:"thinking"===this.state?.28:.12),L=e.createRadialGradient(c,d-.12*C,.1*C,c,d,C);L.addColorStop(0,rgba(O,u?1:.96)),L.addColorStop(.72,rgba(z,u?1:.9)),L.addColorStop(.93,rgba(z,u?.72:.5)),L.addColorStop(1,rgba(z,0)),e.fillStyle=L,e.beginPath(),e.arc(c,d,C,0,h),e.fill(),e.globalCompositeOperation=u?"lighter":"source-over";const R=p.lights.length;for(let t=0;t<R;t+=1){const i=2.1*t,a=g.turb*(.85+.55*S),r=Math.sin(s*g.speed+i)*g.amp+Math.sin(s*g.speed*1.7+1.4*i)*g.amp*a*.75+Math.sin(s*g.speed*.45+.7*i)*g.amp*.35,n=Math.cos(s*g.speed*.9+1.3*i)*g.amp+Math.cos(s*g.speed*2+i)*g.amp*a*.75+Math.cos(s*g.speed*.55+.9*i)*g.amp*.35,o="thinking"===this.state?.42:.28,l=t/R*h-.5*Math.PI+s*g.speed*o,m=C*("thinking"===this.state?.52:.44),b=c+Math.cos(l)*m+r*C+this.hover.x*C*.12,v=d+Math.sin(l)*m+n*C+this.hover.y*C*.12,f=C*M*(.92+.28*S+.12*Math.sin(s*g.speed*1.3+i)),y="thinking"===this.state?mix(p.lights[t],u?[255,255,255]:p.core,u?.1:.18):p.lights[t],w=(u?.62:.78)*g.lightAlpha*(.85+.28*S)*("thinking"===this.state?1.22:1);e.save(),e.filter=`blur(${C*(u?.15:.18)*("thinking"===this.state?.92:1)}px)`;const A=e.createRadialGradient(b,v,0,b,v,f);A.addColorStop(0,rgba(y,Math.min(1,w))),A.addColorStop(.48,rgba(y,Math.min(1,.5*w))),A.addColorStop(1,rgba(y,0)),e.fillStyle=A,e.beginPath(),e.arc(b,v,f,0,h),e.fill(),e.restore()}e.filter="none",e.globalCompositeOperation="destination-in";const D=e.createRadialGradient(c,d,.6*C,c,d,C);D.addColorStop(0,"rgba(0,0,0,1)"),D.addColorStop(.86,"rgba(0,0,0,1)"),D.addColorStop(1,"rgba(0,0,0,0)"),e.fillStyle=D,e.beginPath(),e.arc(c,d,C,0,h),e.fill(),e.globalCompositeOperation="source-atop";const V=c-.22*C+this.hover.x*C*.1,F=d-.28*C+this.hover.y*C*.1,E=e.createRadialGradient(V,F,0,V,F,.95*C);E.addColorStop(0,`rgba(255,255,255,${u?.12:.2})`),E.addColorStop(1,"rgba(255,255,255,0)"),e.fillStyle=E,e.fillRect(c-C,d-C,2*C,2*C),e.globalCompositeOperation="destination-over";const P=e.createRadialGradient(c,d,.7*C,c,d,x),T=(u?.32:.14)*g.glow*(.85+.28*S);P.addColorStop(0,rgba(p.glow,T)),P.addColorStop(1,rgba(p.glow,0)),e.fillStyle=P,e.beginPath(),e.arc(c,d,x,0,h),e.fill(),e.globalCompositeOperation="source-over",e.filter="none"}}customElements.get("db-chat-orb")||customElements.define("db-chat-orb",DbChatOrb);export{DbChatOrb};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import './db-avatar.js';
|
|
2
|
+
import './db-chat-message.js';
|
|
3
|
+
import './db-chat-typing.js';
|
|
4
|
+
import { DbChatComposer } from './db-chat-composer.js';
|
|
5
|
+
|
|
6
|
+
interface DbChatRoomMessage {
|
|
7
|
+
id?: string;
|
|
8
|
+
/** 내가 보낸 메시지면 true(우측 정렬). 아니면 상대(좌측) */
|
|
9
|
+
mine?: boolean;
|
|
10
|
+
text?: string;
|
|
11
|
+
html?: string;
|
|
12
|
+
time?: string;
|
|
13
|
+
/** 상대 이름(그룹 채팅에서 유용) */
|
|
14
|
+
name?: string;
|
|
15
|
+
avatarSrc?: string;
|
|
16
|
+
avatarInitials?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 사람 간 1:1/그룹 대화 화면. 상단 헤더(상대 정보) + 메시지 스레드 + 하단 컴포저.
|
|
20
|
+
* 메시지·아바타·입력창은 `db-chat-message` · `db-avatar` · `db-chat-composer` 를 재사용합니다.
|
|
21
|
+
* AI 대화 화면은 `db-ai-chat` 을 쓰세요.
|
|
22
|
+
*
|
|
23
|
+
* @fires db-send - 전송 (detail: { value, attachments })
|
|
24
|
+
* @fires db-back - 뒤로가기
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const room = document.querySelector('db-chat-room');
|
|
28
|
+
* room.setAttribute('peer-name', '디지털 상담원');
|
|
29
|
+
* room.messages = [{ mine:false, text:'무엇을 도와드릴까요?', time:'오후 2:31' }];
|
|
30
|
+
* room.addEventListener('db-send', (e) => room.addMessage({ mine:true, text:e.detail.value }));
|
|
31
|
+
*/
|
|
32
|
+
declare class DbChatRoom extends HTMLElement {
|
|
33
|
+
static get observedAttributes(): string[];
|
|
34
|
+
private built;
|
|
35
|
+
private typing;
|
|
36
|
+
private _messages;
|
|
37
|
+
private headerEl;
|
|
38
|
+
private scrollEl;
|
|
39
|
+
private threadEl;
|
|
40
|
+
private dockEl;
|
|
41
|
+
private composer;
|
|
42
|
+
set messages(v: DbChatRoomMessage[]);
|
|
43
|
+
get messages(): DbChatRoomMessage[];
|
|
44
|
+
connectedCallback(): void;
|
|
45
|
+
attributeChangedCallback(): void;
|
|
46
|
+
private build;
|
|
47
|
+
private sync;
|
|
48
|
+
private renderHeader;
|
|
49
|
+
private renderThread;
|
|
50
|
+
private buildMessage;
|
|
51
|
+
private buildTyping;
|
|
52
|
+
private scrollToEnd;
|
|
53
|
+
/** 메시지 추가. 추가된 id 반환 */
|
|
54
|
+
addMessage(msg: DbChatRoomMessage): string;
|
|
55
|
+
/** 전체 메시지 교체 */
|
|
56
|
+
setMessages(list: DbChatRoomMessage[]): void;
|
|
57
|
+
/** 상대 입력중(…) 표시 토글 */
|
|
58
|
+
setTyping(on: boolean): void;
|
|
59
|
+
/** 입력창 포커스 */
|
|
60
|
+
focusInput(): void;
|
|
61
|
+
/** 컴포저 인스턴스 접근 */
|
|
62
|
+
getComposer(): DbChatComposer;
|
|
63
|
+
}
|
|
64
|
+
declare global {
|
|
65
|
+
interface HTMLElementTagNameMap {
|
|
66
|
+
'db-chat-room': DbChatRoom;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { DbChatRoom };
|
|
71
|
+
export type { DbChatRoomMessage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createDbIcon as t}from"./db-icons.js";import"./db-avatar.js";import"./db-chat-message.js";import"./db-chat-typing.js";import"./db-chat-composer.js";const e="designbase-wc-chat-room";let s=0;function el(t,e,s){const i=document.createElement(t);return e&&(i.className=e),null!=s&&(i.textContent=s),i}class DbChatRoom extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this._messages=[]}static get observedAttributes(){return["peer-name","peer-avatar","peer-initials","peer-status","placeholder","online","show-back"]}set messages(t){this._messages=Array.isArray(t)?t.map(t=>({id:`r${s+=1}`,...t})):[],this.built&&this.renderThread()}get messages(){return this._messages}connectedCallback(){this.built||this.build(),this.sync()}attributeChangedCallback(){this.built&&this.sync()}build(){this.built=!0,this.className=e,this.replaceChildren(),this.headerEl=el("div",`${e}__header`),this.scrollEl=el("div",`${e}__scroll`),this.threadEl=el("div",`${e}__thread`),this.scrollEl.append(this.threadEl),this.dockEl=el("div",`${e}__dock`),this.composer=document.createElement("db-chat-composer"),this.composer.setAttribute("attach",""),this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지 보내기…"),this.composer.addEventListener("db-send",t=>{t.stopPropagation();const e=t.detail;this.dispatchEvent(new CustomEvent("db-send",{detail:e,bubbles:!0,composed:!0}))}),this.dockEl.append(this.composer),this.append(this.headerEl,this.scrollEl,this.dockEl),this.renderThread()}sync(){this.renderHeader(),this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||"메시지 보내기…")}renderHeader(){if(this.headerEl.replaceChildren(),this.hasAttribute("show-back")){const s=el("button",`${e}__back`);s.type="button",s.setAttribute("aria-label","뒤로"),s.append(t("chevron-left",20)),s.addEventListener("click",()=>this.dispatchEvent(new CustomEvent("db-back",{bubbles:!0,composed:!0}))),this.headerEl.append(s)}const s=document.createElement("db-avatar");s.className=`${e}__avatar`,s.setAttribute("size","m"),s.setAttribute("variant","primary");const i=this.getAttribute("peer-avatar"),a=this.getAttribute("peer-initials");i?s.setAttribute("src",i):a&&s.setAttribute("initials",a),this.hasAttribute("online")&&s.setAttribute("status","online");const r=el("div",`${e}__meta`);r.append(el("span",`${e}__name`,this.getAttribute("peer-name")||"대화"));const n=this.getAttribute("peer-status")||(this.hasAttribute("online")?"온라인":"");n&&r.append(el("span",`${e}__status`,n)),this.headerEl.append(s,r)}renderThread(){this.threadEl.replaceChildren(),this._messages.forEach(t=>this.threadEl.append(this.buildMessage(t))),this.typing&&this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())}buildMessage(t){const e=el("db-chat-message");return e.dataset.id=t.id||"",e.setAttribute("role",t.mine?"user":"assistant"),!t.mine&&t.name&&e.setAttribute("name",t.name),t.time&&e.setAttribute("timestamp",t.time),!t.mine&&t.avatarSrc&&e.setAttribute("avatar-src",t.avatarSrc),!t.mine&&t.avatarInitials&&e.setAttribute("avatar-initials",t.avatarInitials),null!=t.html?e.innerHTML=t.html:e.textContent=t.text||"",e}buildTyping(){const t=el("db-chat-typing");return t.setAttribute("name",this.getAttribute("peer-name")||""),t.setAttribute("avatar-initials",this.getAttribute("peer-initials")||""),t}scrollToEnd(){this.scrollEl.scrollTop=this.scrollEl.scrollHeight}addMessage(t){const e={id:`r${s+=1}`,...t};this._messages.push(e);const i=this.threadEl.querySelector("db-chat-typing"),a=this.buildMessage(e);return i?this.threadEl.insertBefore(a,i):this.threadEl.append(a),requestAnimationFrame(()=>this.scrollToEnd()),e.id}setMessages(t){this.messages=t}setTyping(t){if(this.typing=Boolean(t),!this.built)return;const e=this.threadEl.querySelector("db-chat-typing");this.typing&&!e?(this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())):!this.typing&&e&&e.remove()}focusInput(){this.composer?.focusInput()}getComposer(){return this.composer}}customElements.get("db-chat-room")||customElements.define("db-chat-room",DbChatRoom);export{DbChatRoom};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DbIconName } from './db-icons.js';
|
|
2
|
+
import './db-chip.js';
|
|
3
|
+
|
|
4
|
+
interface DbChatSuggestion {
|
|
5
|
+
/** 고유 id */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** 제목 (카드/칩 라벨) */
|
|
8
|
+
title: string;
|
|
9
|
+
/** 설명 (카드 레이아웃에서만 표시) */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** 아이콘 (DbIconName) */
|
|
12
|
+
icon?: DbIconName;
|
|
13
|
+
/** 선택 시 전달될 값 (기본값: title) */
|
|
14
|
+
value?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 채팅 시작 화면의 추천 프롬프트 목록.
|
|
18
|
+
* `layout="card"` 는 아이콘·제목·설명이 있는 카드 그리드,
|
|
19
|
+
* `layout="chip"` 은 클릭 가능한 칩(db-chip 재사용) 행으로 렌더링합니다.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const s = document.querySelector('db-chat-suggestions');
|
|
23
|
+
* s.items = [{ title:'Write Copy', description:'...', icon:'edit' }];
|
|
24
|
+
* s.addEventListener('db-select', (e) => console.log(e.detail.value));
|
|
25
|
+
*/
|
|
26
|
+
declare class DbChatSuggestions extends HTMLElement {
|
|
27
|
+
static get observedAttributes(): string[];
|
|
28
|
+
private _items;
|
|
29
|
+
get items(): DbChatSuggestion[];
|
|
30
|
+
set items(list: DbChatSuggestion[]);
|
|
31
|
+
get layout(): string;
|
|
32
|
+
connectedCallback(): void;
|
|
33
|
+
attributeChangedCallback(): void;
|
|
34
|
+
private select;
|
|
35
|
+
private render;
|
|
36
|
+
}
|
|
37
|
+
declare global {
|
|
38
|
+
interface HTMLElementTagNameMap {
|
|
39
|
+
'db-chat-suggestions': DbChatSuggestions;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { DbChatSuggestions };
|
|
44
|
+
export type { DbChatSuggestion };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isDbIconName as t,createDbIcon as e}from"./db-icons.js";import"./db-chip.js";const s=["card","chip"];class DbChatSuggestions extends HTMLElement{constructor(){super(...arguments),this._items=[]}static get observedAttributes(){return["layout","columns"]}get items(){return this._items}set items(t){this._items=Array.isArray(t)?t:[],this.render()}get layout(){return s.includes(this.getAttribute("layout"))?this.getAttribute("layout"):"card"}connectedCallback(){if(!this._items.length&&this.hasAttribute("items"))try{this._items=JSON.parse(this.getAttribute("items")||"[]")}catch{this._items=[]}this.render()}attributeChangedCallback(){this.render()}select(t){this.dispatchEvent(new CustomEvent("db-select",{detail:{id:t.id??t.title,title:t.title,value:t.value??t.title},bubbles:!0,composed:!0}))}render(){const s=this.layout,i=Number(this.getAttribute("columns")||3);if(this.className=`designbase-wc-chat-suggestions designbase-wc-chat-suggestions--${s}`,this.style.setProperty("--dbcs-columns",String(i)),this.replaceChildren(),"chip"!==s)for(const s of this._items){const i=document.createElement("button");i.type="button",i.className="designbase-wc-chat-suggestions__card";const n=document.createElement("span");n.className="designbase-wc-chat-suggestions__icon",n.setAttribute("aria-hidden","true"),s.icon&&t(s.icon)&&n.append(e(s.icon,20));const c=document.createElement("span");c.className="designbase-wc-chat-suggestions__body";const a=document.createElement("span");if(a.className="designbase-wc-chat-suggestions__title",a.textContent=s.title,c.append(a),s.description){const t=document.createElement("span");t.className="designbase-wc-chat-suggestions__desc",t.textContent=s.description,c.append(t)}i.append(n,c),i.addEventListener("click",()=>this.select(s)),this.append(i)}else for(const e of this._items){const s=document.createElement("db-chip");s.className="designbase-wc-chat-suggestions__chip",s.setAttribute("label",e.title),s.setAttribute("size","m"),s.setAttribute("clickable",""),e.icon&&t(e.icon)&&s.setAttribute("start-icon",e.icon),s.addEventListener("db-click",()=>this.select(e)),this.append(s)}}}customElements.get("db-chat-suggestions")||customElements.define("db-chat-suggestions",DbChatSuggestions);export{DbChatSuggestions};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import './db-avatar.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI 응답 대기(생성 중) 표시. 3점 바운스 인디케이터.
|
|
5
|
+
* 아바타는 `db-avatar` 를 재사용합니다.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <db-chat-typing name="Orion" label="생각 중…"></db-chat-typing>
|
|
9
|
+
*/
|
|
10
|
+
declare class DbChatTyping extends HTMLElement {
|
|
11
|
+
static get observedAttributes(): string[];
|
|
12
|
+
connectedCallback(): void;
|
|
13
|
+
attributeChangedCallback(): void;
|
|
14
|
+
private render;
|
|
15
|
+
}
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
'db-chat-typing': DbChatTyping;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { DbChatTyping };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./db-avatar.js";class DbChatTyping extends HTMLElement{static get observedAttributes(){return["name","avatar-src","avatar-initials","label"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}render(){this.className="designbase-wc-chat-typing",this.setAttribute("role","status"),this.setAttribute("aria-live","polite"),this.setAttribute("aria-label",this.getAttribute("label")||"AI가 응답을 생성하고 있습니다"),this.replaceChildren();const t=this.getAttribute("avatar-src"),e=this.getAttribute("avatar-initials"),a=document.createElement("div");a.className="designbase-wc-chat-typing__avatar";const s=document.createElement("db-avatar");s.setAttribute("size","s"),s.setAttribute("variant","primary"),t&&s.setAttribute("src",t),e&&s.setAttribute("initials",e),a.append(s);const i=document.createElement("div");i.className="designbase-wc-chat-typing__bubble";const n=this.getAttribute("label");if(n){const t=document.createElement("span");t.className="designbase-wc-chat-typing__label",t.textContent=n,i.append(t)}const r=document.createElement("span");r.className="designbase-wc-chat-typing__dots",r.setAttribute("aria-hidden","true");for(let t=0;t<3;t+=1){const t=document.createElement("span");t.className="designbase-wc-chat-typing__dot",r.append(t)}i.append(r),this.append(a,i)}}customElements.get("db-chat-typing")||customElements.define("db-chat-typing",DbChatTyping);export{DbChatTyping};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import './db-chat-orb.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 채팅 시작 전 화면의 히어로 블록.
|
|
5
|
+
* 오브(또는 로고 이미지) + 인사말 + 타이틀 + 서브타이틀을 렌더링합니다.
|
|
6
|
+
* 타이틀의 `highlight` 부분은 그라디언트로 강조됩니다.
|
|
7
|
+
*
|
|
8
|
+
* 컴포저(`db-chat-composer`)와 추천(`db-chat-suggestions`)은 형제 요소로 배치합니다.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <db-chat-welcome
|
|
12
|
+
* greeting="안녕하세요"
|
|
13
|
+
* title="오늘은 무엇을 도와드릴까요?"
|
|
14
|
+
* highlight="도와드릴까요"
|
|
15
|
+
* orb-variant="aurora">
|
|
16
|
+
* </db-chat-welcome>
|
|
17
|
+
*/
|
|
18
|
+
declare class DbChatWelcome extends HTMLElement {
|
|
19
|
+
static get observedAttributes(): string[];
|
|
20
|
+
connectedCallback(): void;
|
|
21
|
+
attributeChangedCallback(): void;
|
|
22
|
+
private render;
|
|
23
|
+
/** 타이틀에서 highlight 구간만 그라디언트 span 으로 감싼다. */
|
|
24
|
+
private appendTitle;
|
|
25
|
+
}
|
|
26
|
+
declare global {
|
|
27
|
+
interface HTMLElementTagNameMap {
|
|
28
|
+
'db-chat-welcome': DbChatWelcome;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { DbChatWelcome };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./db-chat-orb.js";const e=["center","left"];class DbChatWelcome extends HTMLElement{static get observedAttributes(){return["greeting","title","highlight","subtitle","align","orb-variant","orb-state","orb-size","hide-orb","avatar-src"]}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}render(){const t=this.getAttribute("greeting")||"",a=this.getAttribute("title")||"",s=this.getAttribute("highlight")||"",c=this.getAttribute("subtitle")||"",i=e.includes(this.getAttribute("align"))?this.getAttribute("align"):"center",n=this.getAttribute("avatar-src"),r=this.hasAttribute("hide-orb");if(this.className=`designbase-wc-chat-welcome designbase-wc-chat-welcome--${i}`,this.replaceChildren(),n){const e=document.createElement("div");e.className="designbase-wc-chat-welcome__media";const t=document.createElement("img");t.className="designbase-wc-chat-welcome__logo",t.src=n,t.alt="",t.setAttribute("aria-hidden","true"),e.append(t),this.append(e)}else if(!r){const e=document.createElement("db-chat-orb");e.className="designbase-wc-chat-welcome__orb",e.setAttribute("size",this.getAttribute("orb-size")||"l"),e.setAttribute("variant",this.getAttribute("orb-variant")||"aurora"),e.setAttribute("state",this.getAttribute("orb-state")||"idle"),this.append(e)}const l=document.createElement("h1");if(l.className="designbase-wc-chat-welcome__heading",t){const e=document.createElement("span");e.className="designbase-wc-chat-welcome__greeting",e.textContent=t,l.append(e)}if(a){const e=document.createElement("span");e.className="designbase-wc-chat-welcome__title",this.appendTitle(e,a,s),l.append(e)}if(this.append(l),c){const e=document.createElement("p");e.className="designbase-wc-chat-welcome__subtitle",e.textContent=c,this.append(e)}}appendTitle(e,t,a){const s=a?t.indexOf(a):-1;if(-1===s)return void(e.textContent=t);const c=t.slice(0,s),i=t.slice(s+a.length);c&&e.append(document.createTextNode(c));const n=document.createElement("span");n.className="designbase-wc-chat-welcome__highlight",n.textContent=a,e.append(n),i&&e.append(document.createTextNode(i))}}customElements.get("db-chat-welcome")||customElements.define("db-chat-welcome",DbChatWelcome);export{DbChatWelcome};
|