ai-chat-ui-kit 0.1.0 → 0.1.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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -7
  3. package/package.json +8 -4
  4. package/.eslintrc.cjs +0 -74
  5. package/.github/actions/screenshot/action.yml +0 -35
  6. package/.github/workflows/pages.yml +0 -46
  7. package/docs/README.md +0 -176
  8. package/docs/api/components.md +0 -344
  9. package/docs/api/core.md +0 -349
  10. package/docs/chat-style-1-minimal.html +0 -78
  11. package/docs/chat-style-2-neon.html +0 -74
  12. package/docs/chat-style-3-glass.html +0 -73
  13. package/docs/chat-style-4-terminal.html +0 -84
  14. package/docs/chat-style-5-gradient.html +0 -69
  15. package/docs/chat-style-6-corporate.html +0 -116
  16. package/docs/examples/basic-chat.md +0 -291
  17. package/docs/examples/custom-plugins.md +0 -431
  18. package/docs/examples/multi-model.md +0 -466
  19. package/docs/guide/api-adapters.md +0 -431
  20. package/docs/guide/getting-started.md +0 -244
  21. package/docs/guide/headless-mode.md +0 -508
  22. package/docs/guide/plugins.md +0 -416
  23. package/docs/guide/themes.md +0 -327
  24. package/docs/index.html +0 -256
  25. package/docs/theme-preview-1-minimal.html +0 -74
  26. package/docs/theme-preview-2-neon.html +0 -73
  27. package/docs/theme-preview-3-glass.html +0 -77
  28. package/docs/theme-preview-4-terminal.html +0 -86
  29. package/docs/theme-preview-5-gradient.html +0 -79
  30. package/docs/theme-preview-6-corporate.html +0 -71
  31. package/examples/index.html +0 -414
  32. package/examples/react-app/App.tsx +0 -131
  33. package/examples/react-app/index.html +0 -12
  34. package/examples/react-app/main.tsx +0 -15
  35. package/examples/react-app/package.json +0 -24
  36. package/examples/vue-app/index.html +0 -12
  37. package/examples/vue-app/package.json +0 -22
  38. package/examples/vue-app/src/App.vue +0 -145
  39. package/examples/vue-app/src/main.ts +0 -9
  40. package/packages/components/package.json +0 -25
  41. package/packages/components/src/chat/chat.css +0 -80
  42. package/packages/components/src/chat/chat.ts +0 -236
  43. package/packages/components/src/index.ts +0 -36
  44. package/packages/components/src/input/input.css +0 -52
  45. package/packages/components/src/input/input.ts +0 -116
  46. package/packages/components/src/markdown/markdown.css +0 -118
  47. package/packages/components/src/markdown/markdown.ts +0 -229
  48. package/packages/components/src/message/message.css +0 -56
  49. package/packages/components/src/message/message.ts +0 -72
  50. package/packages/components/src/styles/global.css +0 -43
  51. package/packages/components/src/tool-call/tool-call.css +0 -98
  52. package/packages/components/src/tool-call/tool-call.ts +0 -171
  53. package/packages/components/src/types.ts +0 -55
  54. package/packages/components/src/utils/helpers.ts +0 -128
  55. package/packages/components/tsconfig.json +0 -25
  56. package/packages/components/tsup.config.ts +0 -18
  57. package/packages/core/package.json +0 -47
  58. package/packages/core/pnpm-lock.yaml +0 -2032
  59. package/packages/core/pnpm-workspace.yaml +0 -2
  60. package/packages/core/src/api/adapters.ts +0 -717
  61. package/packages/core/src/api/base.ts +0 -210
  62. package/packages/core/src/api/index.ts +0 -54
  63. package/packages/core/src/index.ts +0 -93
  64. package/packages/core/src/parser/latex.ts +0 -274
  65. package/packages/core/src/parser/markdown.test.ts +0 -58
  66. package/packages/core/src/parser/markdown.ts +0 -206
  67. package/packages/core/src/parser/mermaid.ts +0 -276
  68. package/packages/core/src/plugins/PluginManager.ts +0 -232
  69. package/packages/core/src/plugins/builtin.ts +0 -406
  70. package/packages/core/src/store/ChatStore.ts +0 -163
  71. package/packages/core/src/store/ModelConfigStore.ts +0 -136
  72. package/packages/core/src/store/ToolCallStore.ts +0 -164
  73. package/packages/core/src/store/base.ts +0 -75
  74. package/packages/core/src/types/index.ts +0 -133
  75. package/packages/core/tsup.config.ts +0 -18
  76. package/packages/themes/package.json +0 -33
  77. package/packages/themes/src/corporate/index.ts +0 -52
  78. package/packages/themes/src/corporate/theme.css +0 -228
  79. package/packages/themes/src/glass/index.ts +0 -52
  80. package/packages/themes/src/glass/theme.css +0 -237
  81. package/packages/themes/src/gradient/index.ts +0 -53
  82. package/packages/themes/src/gradient/theme.css +0 -218
  83. package/packages/themes/src/index.ts +0 -13
  84. package/packages/themes/src/minimal/index.ts +0 -52
  85. package/packages/themes/src/minimal/theme.css +0 -198
  86. package/packages/themes/src/neon/index.ts +0 -52
  87. package/packages/themes/src/neon/theme.css +0 -233
  88. package/packages/themes/src/terminal/index.ts +0 -52
  89. package/packages/themes/src/terminal/theme.css +0 -235
  90. package/packages/themes/src/types.ts +0 -10
  91. package/packages/themes/src/vite-env.d.ts +0 -9
  92. package/packages/themes/tsup.config.ts +0 -21
  93. package/pnpm-workspace.yaml +0 -4
  94. package/tsconfig.json +0 -27
  95. package/vite.config.ts +0 -25
  96. package/vitest.config.ts +0 -28
@@ -1,145 +0,0 @@
1
- <!--
2
- @generated-by AI: edenxpzhang
3
- @generated-date 2026-05-13
4
- -->
5
- <template>
6
- <div class="container">
7
- <header class="header">
8
- <h1 class="title">🤖 AI Chat - Vue 示例</h1>
9
- <div class="theme-buttons">
10
- <button
11
- :class="['theme-btn', { active: theme === 'default' }]"
12
- @click="handleThemeChange('default')"
13
- >
14
- 默认主题
15
- </button>
16
- <button
17
- :class="['theme-btn', { active: theme === 'bubble' }]"
18
- @click="handleThemeChange('bubble')"
19
- >
20
- 气泡主题
21
- </button>
22
- <button
23
- :class="['theme-btn', { active: theme === 'flat' }]"
24
- @click="handleThemeChange('flat')"
25
- >
26
- 扁平主题
27
- </button>
28
- </div>
29
- </header>
30
- <main class="chat-container">
31
- <ai-chat ref="chatRef" class="chat"></ai-chat>
32
- </main>
33
- </div>
34
- </template>
35
-
36
- <script setup lang="ts">
37
- import { ref, onMounted } from 'vue';
38
- import '@ai-chat/components';
39
- import '@ai-chat/themes/default';
40
-
41
- const chatRef = ref<HTMLElement>();
42
- const theme = ref<'default' | 'bubble' | 'flat'>('default');
43
-
44
- onMounted(() => {
45
- if (chatRef.value) {
46
- // 设置消息处理回调
47
- (chatRef.value as any).setMessageHandler(async (message: string) => {
48
- // 模拟 AI 回复延迟
49
- await new Promise(resolve => setTimeout(resolve, 1000));
50
- return `AI 回复:${message}`;
51
- });
52
-
53
- // 添加欢迎消息
54
- (chatRef.value as any).addMessage({
55
- role: 'assistant',
56
- content: '你好!我是 AI 助手。有什么可以帮助你的吗?'
57
- });
58
- }
59
- });
60
-
61
- // 切换主题
62
- const handleThemeChange = (newTheme: 'default' | 'bubble' | 'flat') => {
63
- theme.value = newTheme;
64
- console.log(`切换到 ${newTheme} 主题`);
65
- // 在实际使用中,这里应该动态加载对应的主题 CSS
66
- // 可以通过动态导入实现:
67
- // import(`@ai-chat/themes/${newTheme}`);
68
- };
69
- </script>
70
-
71
- <style>
72
- * {
73
- margin: 0;
74
- padding: 0;
75
- box-sizing: border-box;
76
- }
77
-
78
- html, body {
79
- height: 100%;
80
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
81
- }
82
-
83
- #app {
84
- height: 100%;
85
- }
86
- </style>
87
-
88
- <style scoped>
89
- .container {
90
- height: 100vh;
91
- display: flex;
92
- flex-direction: column;
93
- }
94
-
95
- .header {
96
- padding: 16px 24px;
97
- background: #1677ff;
98
- color: white;
99
- display: flex;
100
- align-items: center;
101
- justify-content: space-between;
102
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
103
- }
104
-
105
- .title {
106
- font-size: 20px;
107
- font-weight: 600;
108
- }
109
-
110
- .theme-buttons {
111
- display: flex;
112
- gap: 8px;
113
- }
114
-
115
- .theme-btn {
116
- padding: 6px 16px;
117
- border: 1px solid rgba(255, 255, 255, 0.5);
118
- background: transparent;
119
- color: white;
120
- border-radius: 4px;
121
- cursor: pointer;
122
- font-size: 14px;
123
- transition: all 0.3s;
124
- }
125
-
126
- .theme-btn:hover {
127
- background: rgba(255, 255, 255, 0.15);
128
- }
129
-
130
- .theme-btn.active {
131
- background: rgba(255, 255, 255, 0.25);
132
- border-color: white;
133
- }
134
-
135
- .chat-container {
136
- flex: 1;
137
- height: 0;
138
- overflow: hidden;
139
- }
140
-
141
- .chat {
142
- height: 100%;
143
- display: block;
144
- }
145
- </style>
@@ -1,9 +0,0 @@
1
- /**
2
- * @generated-by AI: edenxpzhang
3
- * @generated-date 2026-05-13
4
- */
5
-
6
- import { createApp } from 'vue';
7
- import App from './App.vue';
8
-
9
- createApp(App).mount('#app');
@@ -1,25 +0,0 @@
1
- {
2
- "name": "@ai-chat/components",
3
- "version": "0.1.0",
4
- "type": "module",
5
- "main": "dist/index.cjs",
6
- "module": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "files": ["dist"],
9
- "scripts": {
10
- "dev": "tsup --watch",
11
- "build": "tsup"
12
- },
13
- "dependencies": {
14
- "lit": "^3.0.0"
15
- },
16
- "devDependencies": {
17
- "tsup": "^7.0.0",
18
- "typescript": "^5.0.0"
19
- },
20
- "peerDependenciesMeta": {
21
- "@ai-chat/core": {
22
- "optional": true
23
- }
24
- }
25
- }
@@ -1,80 +0,0 @@
1
- /**
2
- * @generated-by AI: edenxpzhang
3
- * @generated-date 2026-05-13
4
- *
5
- * 仅作为最小内置兜底样式(当用户没引入任何主题时仍能正常显示)。
6
- * 任何视觉风格(颜色/圆角/阴影/字体)都应由主题包接管,本文件不应硬编码风格细节。
7
- */
8
-
9
- ai-chat {
10
- display: block;
11
- width: 100%;
12
- height: 100%;
13
- }
14
-
15
- ai-chat .ai-chat {
16
- display: flex;
17
- flex-direction: column;
18
- height: 100%;
19
- min-height: 0;
20
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
21
- font-size: 14px;
22
- color: #333;
23
- background: #fff;
24
- }
25
-
26
- ai-chat .ai-chat__header {
27
- display: flex;
28
- align-items: center;
29
- gap: 12px;
30
- padding: 16px 20px;
31
- flex-shrink: 0;
32
- }
33
-
34
- ai-chat .ai-chat__avatar {
35
- width: 40px;
36
- height: 40px;
37
- border-radius: 50%;
38
- display: flex;
39
- align-items: center;
40
- justify-content: center;
41
- font-size: 18px;
42
- flex-shrink: 0;
43
- }
44
-
45
- ai-chat .ai-chat__title-group {
46
- flex: 1;
47
- min-width: 0;
48
- }
49
-
50
- ai-chat .ai-chat__title {
51
- font-size: 16px;
52
- font-weight: 600;
53
- margin: 0;
54
- line-height: 1.3;
55
- }
56
-
57
- ai-chat .ai-chat__subtitle {
58
- font-size: 12px;
59
- margin: 2px 0 0;
60
- opacity: 0.7;
61
- }
62
-
63
- ai-chat .ai-chat__messages {
64
- flex: 1;
65
- min-height: 0;
66
- overflow-y: auto;
67
- padding: 16px;
68
- display: flex;
69
- flex-direction: column;
70
- gap: 12px;
71
- }
72
-
73
- ai-chat .ai-chat__empty {
74
- display: flex;
75
- align-items: center;
76
- justify-content: center;
77
- padding: 24px;
78
- color: #999;
79
- font-size: 13px;
80
- }
@@ -1,236 +0,0 @@
1
- /**
2
- * @generated-by AI: edenxpzhang
3
- * @generated-date 2026-05-13
4
- */
5
-
6
- import { LitElement, html, PropertyValues } from 'lit';
7
- import { customElement, property, state } from 'lit/decorators.js';
8
- import './chat.css';
9
-
10
- // 导入共享类型
11
- import { Message, ModelConfig } from '../types.js';
12
-
13
- @customElement('ai-chat')
14
- export class AiChat extends LitElement {
15
- // 默认 light DOM,让外部主题 CSS 能命中
16
- @property({ type: Boolean })
17
- headless = true;
18
-
19
- @property({ type: String })
20
- theme = 'default';
21
-
22
- @property({ type: String, attribute: 'header-title' })
23
- headerTitle = 'AI Assistant';
24
-
25
- @property({ type: String, attribute: 'header-subtitle' })
26
- headerSubtitle = '在线 · 随时为您服务';
27
-
28
- @property({ type: String, attribute: 'header-avatar' })
29
- headerAvatar = '🤖';
30
-
31
- @property({ type: Boolean, attribute: 'hide-header' })
32
- hideHeader = false;
33
-
34
- @property({ type: Object })
35
- config?: ModelConfig;
36
-
37
- @state()
38
- private messages: Message[] = [];
39
-
40
- @state()
41
- private isLoading = false;
42
-
43
- // 默认禁用 Shadow DOM(让全局主题 CSS 能命中所有内部元素)
44
- createRenderRoot() {
45
- if (this.headless) {
46
- return this;
47
- }
48
- return super.createRenderRoot();
49
- }
50
-
51
- updated(changedProperties: PropertyValues) {
52
- if (changedProperties.has('theme')) {
53
- this.applyTheme();
54
- }
55
- }
56
-
57
- private applyTheme() {
58
- document.documentElement.setAttribute('data-theme', this.theme);
59
- }
60
-
61
- render() {
62
- return html`
63
- <div class="ai-chat" data-theme=${this.theme}>
64
- <slot name="header">
65
- ${this.hideHeader
66
- ? ''
67
- : html`
68
- <header class="ai-chat__header">
69
- <div class="ai-chat__avatar">${this.headerAvatar}</div>
70
- <div class="ai-chat__title-group">
71
- <h3 class="ai-chat__title">${this.headerTitle}</h3>
72
- <p class="ai-chat__subtitle">${this.headerSubtitle}</p>
73
- </div>
74
- <slot name="header-actions"></slot>
75
- </header>
76
- `}
77
- </slot>
78
-
79
- <div class="ai-chat__messages">
80
- ${this.messages.length === 0
81
- ? html`<div class="ai-chat__empty">开始对话吧</div>`
82
- : this.messages.map(
83
- (message) => html`
84
- <ai-message
85
- .message=${message}
86
- .theme=${this.theme}
87
- ?headless=${this.headless}
88
- ></ai-message>
89
- `
90
- )}
91
- ${this.isLoading
92
- ? html`
93
- <div class="ai-typing-indicator">
94
- <span class="ai-typing-indicator__dot"></span>
95
- <span class="ai-typing-indicator__dot"></span>
96
- <span class="ai-typing-indicator__dot"></span>
97
- </div>
98
- `
99
- : ''}
100
- </div>
101
-
102
- <ai-input
103
- .disabled=${this.isLoading}
104
- ?headless=${this.headless}
105
- @send=${this.handleSend}
106
- @input-change=${this.handleInputChange}
107
- ></ai-input>
108
-
109
- <slot name="footer"></slot>
110
- </div>
111
- `;
112
- }
113
-
114
- private handleSend(event: CustomEvent) {
115
- const content = event.detail.content;
116
- this.sendMessage(content);
117
- }
118
-
119
- private handleInputChange(event: CustomEvent) {
120
- this.dispatchEvent(
121
- new CustomEvent('input-change', {
122
- detail: event.detail,
123
- bubbles: true,
124
- composed: true,
125
- })
126
- );
127
- }
128
-
129
- private messageIdCounter = 0;
130
-
131
- /**
132
- * 生成唯一的消息 ID
133
- */
134
- private generateMessageId(): string {
135
- this.messageIdCounter++;
136
- return `msg-${Date.now()}-${this.messageIdCounter}-${Math.random().toString(36).substr(2, 9)}`;
137
- }
138
-
139
- /**
140
- * 发送消息
141
- */
142
- sendMessage(content: string): void {
143
- if (!content.trim()) return;
144
-
145
- const userMessage: Message = {
146
- id: this.generateMessageId(),
147
- role: 'user',
148
- content,
149
- timestamp: Date.now(),
150
- };
151
-
152
- this.messages = [...this.messages, userMessage];
153
- this.isLoading = true;
154
-
155
- this.dispatchEvent(
156
- new CustomEvent('message-sent', {
157
- detail: { message: userMessage },
158
- bubbles: true,
159
- composed: true,
160
- })
161
- );
162
-
163
- // 模拟 AI 回复(实际应调用 API)
164
- setTimeout(() => {
165
- const aiMessage: Message = {
166
- id: this.generateMessageId(),
167
- role: 'assistant',
168
- content: `回复:${content}`,
169
- timestamp: Date.now(),
170
- };
171
-
172
- this.messages = [...this.messages, aiMessage];
173
- this.isLoading = false;
174
-
175
- this.dispatchEvent(
176
- new CustomEvent('message-received', {
177
- detail: { message: aiMessage },
178
- bubbles: true,
179
- composed: true,
180
- })
181
- );
182
- }, 1000);
183
- }
184
-
185
- /**
186
- * 添加预设消息(用于演示/初始化)
187
- */
188
- addMessage(message: Partial<Message> & { role: Message['role']; content: string }): void {
189
- const full: Message = {
190
- id: message.id ?? this.generateMessageId(),
191
- role: message.role,
192
- content: message.content,
193
- timestamp: message.timestamp ?? Date.now(),
194
- toolCalls: message.toolCalls,
195
- };
196
- this.messages = [...this.messages, full];
197
- }
198
-
199
- /**
200
- * 清空消息
201
- */
202
- clearMessages(): void {
203
- this.messages = [];
204
- }
205
-
206
- /**
207
- * 导出会话
208
- */
209
- exportSession(): string {
210
- return JSON.stringify(this.messages, null, 2);
211
- }
212
-
213
- /**
214
- * 导入会话
215
- */
216
- importSession(data: string): void {
217
- try {
218
- const messages = JSON.parse(data) as Message[];
219
- this.messages = messages;
220
- } catch (error) {
221
- this.dispatchEvent(
222
- new CustomEvent('error', {
223
- detail: { error: 'Failed to import session' },
224
- bubbles: true,
225
- composed: true,
226
- })
227
- );
228
- }
229
- }
230
- }
231
-
232
- declare global {
233
- interface HTMLElementTagNameMap {
234
- 'ai-chat': AiChat;
235
- }
236
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * @generated-by AI: edenxpzhang
3
- * @generated-date 2026-05-13
4
- */
5
-
6
- // 导入所有组件
7
- import { AiChat } from './chat/chat.js';
8
- import { AiMessage } from './message/message.js';
9
- import { AiInput } from './input/input.js';
10
- import { AiToolCall } from './tool-call/tool-call.js';
11
- import { AiMarkdown } from './markdown/markdown.js';
12
-
13
- // 导出组件类
14
- export { AiChat, AiMessage, AiInput, AiToolCall, AiMarkdown };
15
-
16
- // 批量注册组件
17
- export function registerComponents(): void {
18
- if (!customElements.get('ai-chat')) {
19
- customElements.define('ai-chat', AiChat);
20
- }
21
- if (!customElements.get('ai-message')) {
22
- customElements.define('ai-message', AiMessage);
23
- }
24
- if (!customElements.get('ai-input')) {
25
- customElements.define('ai-input', AiInput);
26
- }
27
- if (!customElements.get('ai-tool-call')) {
28
- customElements.define('ai-tool-call', AiToolCall);
29
- }
30
- if (!customElements.get('ai-markdown')) {
31
- customElements.define('ai-markdown', AiMarkdown);
32
- }
33
- }
34
-
35
- // 自动注册(如果直接导入此模块)
36
- registerComponents();
@@ -1,52 +0,0 @@
1
- /**
2
- * @generated-by AI: edenxpzhang
3
- * @generated-date 2026-05-13
4
- *
5
- * 兜底样式 —— 实际视觉由主题接管
6
- */
7
-
8
- ai-input {
9
- display: block;
10
- }
11
-
12
- ai-input .ai-input {
13
- padding: 12px 16px;
14
- }
15
-
16
- ai-input .ai-input__wrapper {
17
- display: flex;
18
- gap: 8px;
19
- align-items: flex-end;
20
- }
21
-
22
- ai-input .ai-input__textarea {
23
- flex: 1;
24
- resize: none;
25
- outline: none;
26
- border: 1px solid #e8e8e8;
27
- border-radius: 8px;
28
- padding: 10px 12px;
29
- font-family: inherit;
30
- font-size: 14px;
31
- line-height: 1.5;
32
- min-height: 40px;
33
- max-height: 120px;
34
- background: #fff;
35
- color: inherit;
36
- }
37
-
38
- ai-input .ai-input__send {
39
- flex-shrink: 0;
40
- padding: 10px 18px;
41
- border: none;
42
- border-radius: 8px;
43
- background: #1677ff;
44
- color: #fff;
45
- cursor: pointer;
46
- font-size: 14px;
47
- }
48
-
49
- ai-input .ai-input__send:disabled {
50
- opacity: 0.5;
51
- cursor: not-allowed;
52
- }
@@ -1,116 +0,0 @@
1
- /**
2
- * @generated-by AI: edenxpzhang
3
- * @generated-date 2026-05-13
4
- */
5
-
6
- import { LitElement, html } from 'lit';
7
- import { customElement, property, state } from 'lit/decorators.js';
8
- import './input.css';
9
-
10
- @customElement('ai-input')
11
- export class AiInput extends LitElement {
12
- @property({ type: Boolean })
13
- headless = true;
14
-
15
- @property({ type: Boolean })
16
- disabled = false;
17
-
18
- @property({ type: String })
19
- placeholder = '输入消息...';
20
-
21
- @property({ type: String, attribute: 'send-text' })
22
- sendText = '发送';
23
-
24
- @state()
25
- private value = '';
26
-
27
- private textarea?: HTMLTextAreaElement;
28
-
29
- // 默认 light DOM
30
- createRenderRoot() {
31
- if (this.headless) {
32
- return this;
33
- }
34
- return super.createRenderRoot();
35
- }
36
-
37
- firstUpdated() {
38
- this.textarea = this.renderRoot.querySelector('textarea') as HTMLTextAreaElement;
39
- }
40
-
41
- render() {
42
- return html`
43
- <div class="ai-input">
44
- <div class="ai-input__wrapper">
45
- <textarea
46
- class="ai-input__textarea"
47
- .value=${this.value}
48
- placeholder=${this.placeholder}
49
- ?disabled=${this.disabled}
50
- @input=${this.handleInput}
51
- @keydown=${this.handleKeyDown}
52
- rows="1"
53
- ></textarea>
54
- <button
55
- class="ai-input__send"
56
- ?disabled=${this.disabled || !this.value.trim()}
57
- @click=${this.handleSend}
58
- >
59
- ${this.sendText}
60
- </button>
61
- </div>
62
- </div>
63
- `;
64
- }
65
-
66
- private handleInput(e: Event) {
67
- const target = e.target as HTMLTextAreaElement;
68
- this.value = target.value;
69
- this.adjustHeight(target);
70
-
71
- this.dispatchEvent(
72
- new CustomEvent('input-change', {
73
- detail: { value: this.value },
74
- bubbles: true,
75
- composed: true,
76
- })
77
- );
78
- }
79
-
80
- private handleKeyDown(e: KeyboardEvent) {
81
- if (e.key === 'Enter' && !e.shiftKey) {
82
- e.preventDefault();
83
- this.handleSend();
84
- }
85
- }
86
-
87
- private handleSend() {
88
- if (!this.value.trim() || this.disabled) return;
89
-
90
- this.dispatchEvent(
91
- new CustomEvent('send', {
92
- detail: { content: this.value },
93
- bubbles: true,
94
- composed: true,
95
- })
96
- );
97
-
98
- this.value = '';
99
- if (this.textarea) {
100
- this.textarea.style.height = 'auto';
101
- }
102
- }
103
-
104
- private adjustHeight(textarea: HTMLTextAreaElement) {
105
- textarea.style.height = 'auto';
106
- const maxHeight = 120;
107
- const newHeight = Math.min(textarea.scrollHeight, maxHeight);
108
- textarea.style.height = `${newHeight}px`;
109
- }
110
- }
111
-
112
- declare global {
113
- interface HTMLElementTagNameMap {
114
- 'ai-input': AiInput;
115
- }
116
- }