Skip to content

Commit e8130a3

Browse files
authored
refactor(sender): refactor sender components for 0.4.0 version (#283)
1 parent d96cd38 commit e8130a3

File tree

93 files changed

+8677
-3699
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+8677
-3699
lines changed

packages/components/package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,19 @@
1919
"peerDependencies": {
2020
"dompurify": "^3.3.1",
2121
"markdown-it": "^14.1.0",
22-
"vue": "^3.3.11"
22+
"vue": "^3.3.11",
23+
"@tiptap/core": "^3.11.0",
24+
"@tiptap/vue-3": "^3.11.0",
25+
"@tiptap/pm": "^3.11.0",
26+
"@tiptap/extension-document": "^3.11.0",
27+
"@tiptap/extension-paragraph": "^3.11.0",
28+
"@tiptap/extension-text": "^3.11.0",
29+
"@tiptap/extension-history": "^3.11.0",
30+
"@tiptap/extension-placeholder": "^3.11.0",
31+
"@tiptap/extension-character-count": "^3.11.0"
2332
},
2433
"dependencies": {
34+
"@floating-ui/dom": "^1.6.0",
2535
"@opentiny/tiny-robot-svgs": "workspace:*",
2636
"@opentiny/vue": "^3.20.0",
2737
"@vueuse/core": "^13.1.0",

packages/components/src/index.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,26 @@ import History from './history'
1313
import IconButton from './icon-button'
1414
import { Prompt, Prompts } from './prompts'
1515
import Sender from './sender'
16+
import SenderCompat from './sender-compat'
1617
import SuggestionPills, { SuggestionPillButton } from './suggestion-pills'
1718
import SuggestionPopover from './suggestion-popover'
1819
import ThemeProvider from './theme-provider'
1920
import Welcome from './welcome'
2021
import McpServerPicker from './mcp-server-picker'
2122
import McpAddForm from './mcp-add-form'
23+
import {
24+
ActionButton,
25+
SubmitButton,
26+
ClearButton,
27+
UploadButton,
28+
VoiceButton,
29+
WordCounter,
30+
DefaultActionButtons,
31+
} from './sender-actions'
2232

33+
// ============================================
34+
// 组件类型导出
35+
// ============================================
2336
export * from './attachments/index.type'
2437
export * from './bubble/index.type'
2538
export * from './container/index.type'
@@ -30,6 +43,7 @@ export * from './history/index.type'
3043
export * from './icon-button/index.type'
3144
export * from './prompts/index.type'
3245
export * from './sender/index.type'
46+
export * from './sender-actions/index.type'
3347
export * from './suggestion-pills/index.type'
3448
export * from './suggestion-popover/index.type'
3549
export * from './theme-provider/index.type'
@@ -47,6 +61,7 @@ export {
4761
useOmitMessageFields,
4862
} from './bubble'
4963
export { useTheme } from './theme-provider/useTheme'
64+
export { useSenderContext } from './sender'
5065
export { vDropzone } from './drag-overlay/directives/vDropzone'
5166
export { useAutoScroll, useTouchDevice } from './shared/composables'
5267

@@ -65,13 +80,21 @@ const components = [
6580
Prompt,
6681
Prompts,
6782
Sender,
83+
SenderCompat,
6884
SuggestionPills,
6985
SuggestionPillButton,
7086
SuggestionPopover,
7187
ThemeProvider,
7288
Welcome,
7389
McpServerPicker,
7490
McpAddForm,
91+
ActionButton,
92+
SubmitButton,
93+
ClearButton,
94+
UploadButton,
95+
VoiceButton,
96+
WordCounter,
97+
DefaultActionButtons,
7598
]
7699

77100
export default {
@@ -112,6 +135,8 @@ export {
112135
Prompts as TrPrompts,
113136
Sender,
114137
Sender as TrSender,
138+
SenderCompat,
139+
SenderCompat as TrSenderCompat,
115140
SuggestionPillButton,
116141
SuggestionPillButton as TrSuggestionPillButton,
117142
SuggestionPills,
@@ -126,4 +151,18 @@ export {
126151
McpServerPicker as TrMcpServerPicker,
127152
McpAddForm,
128153
McpAddForm as TrMcpAddForm,
154+
ActionButton,
155+
ActionButton as TrActionButton,
156+
SubmitButton,
157+
SubmitButton as TrSubmitButton,
158+
ClearButton,
159+
ClearButton as TrClearButton,
160+
UploadButton,
161+
UploadButton as TrUploadButton,
162+
VoiceButton,
163+
VoiceButton as TrVoiceButton,
164+
WordCounter,
165+
WordCounter as TrWordCounter,
166+
DefaultActionButtons,
167+
DefaultActionButtons as TrDefaultActionButtons,
129168
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { TinyTooltip } from '@opentiny/vue'
4+
import type { ActionButtonProps } from '../types/common'
5+
import { normalizeTooltipContent } from '../utils/tooltip'
6+
7+
const props = withDefaults(defineProps<ActionButtonProps>(), {
8+
disabled: false,
9+
active: false,
10+
tooltipPlacement: 'top',
11+
})
12+
13+
const tooltipRenderFn = computed(() => normalizeTooltipContent(props.tooltip))
14+
15+
const sizeStyle = computed(() => {
16+
if (props.size) {
17+
const finalSize = typeof props.size === 'number' ? `${props.size}px` : props.size
18+
return { fontSize: finalSize }
19+
}
20+
return {}
21+
})
22+
</script>
23+
24+
<template>
25+
<tiny-tooltip
26+
v-if="props.tooltip"
27+
:render-content="tooltipRenderFn"
28+
:placement="props.tooltipPlacement"
29+
effect="light"
30+
:visible-arrow="false"
31+
popper-class="tr-action-button-tooltip-popper"
32+
>
33+
<button
34+
:class="['tr-action-button', { active: props.active }]"
35+
:disabled="props.disabled"
36+
@focus.capture="(event: FocusEvent) => event.stopPropagation()"
37+
>
38+
<!-- 优先使用插槽,如果没有插槽则使用 icon prop -->
39+
<slot name="icon">
40+
<component :is="props.icon" :style="sizeStyle" />
41+
</slot>
42+
</button>
43+
</tiny-tooltip>
44+
45+
<!-- 无 tooltip 时直接渲染按钮 -->
46+
<button v-else :class="['tr-action-button', { active: props.active }]" :disabled="props.disabled">
47+
<slot name="icon">
48+
<component :is="props.icon" :style="sizeStyle" />
49+
</slot>
50+
</button>
51+
</template>
52+
53+
<style lang="less" scoped>
54+
.tr-action-button {
55+
display: inline-flex;
56+
align-items: center;
57+
justify-content: center;
58+
border: none;
59+
border-radius: 6px;
60+
background: transparent;
61+
cursor: pointer;
62+
padding: 0;
63+
transition: background-color 0.2s;
64+
color: var(--tr-text-secondary);
65+
66+
:deep(svg) {
67+
font-size: var(--tr-sender-button-size);
68+
}
69+
70+
&:hover:not(:disabled) {
71+
background-color: var(--tr-sender-button-hover-bg, rgba(0, 0, 0, 0.08));
72+
}
73+
74+
&:active:not(:disabled) {
75+
background-color: var(--tr-sender-button-active-bg, rgba(0, 0, 0, 0.12));
76+
}
77+
78+
&.active {
79+
background-color: var(--tr-sender-button-active-bg, rgba(0, 0, 0, 0.12));
80+
color: var(--tr-text-primary);
81+
}
82+
83+
&:disabled {
84+
opacity: 0.4;
85+
cursor: not-allowed;
86+
}
87+
}
88+
</style>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { useSenderContext } from '../../sender/context'
4+
import { IconClear } from '@opentiny/tiny-robot-svgs'
5+
import ActionButton from '../action-button/index.vue'
6+
import { normalizeTooltipContent } from '../utils/tooltip'
7+
8+
// 从 Context 读取状态和配置
9+
const { hasContent, clearable, clear, loading, defaultActions } = useSenderContext()
10+
11+
/**
12+
* 是否禁用
13+
*/
14+
const isDisabled = computed(() => {
15+
if (defaultActions.value?.clear?.disabled !== undefined) {
16+
return defaultActions.value.clear.disabled
17+
}
18+
return false
19+
})
20+
21+
const tooltipRenderFn = computed(() => normalizeTooltipContent(defaultActions.value?.clear?.tooltip))
22+
23+
const tooltipPlacement = computed(() => defaultActions.value?.clear?.tooltipPlacement ?? 'top')
24+
25+
/**
26+
* 显示条件
27+
* - clearable: 允许清空
28+
* - hasContent: 有内容
29+
* - !loading: 非加载中
30+
* - !isDisabled: 非禁用
31+
*/
32+
const show = computed(() => clearable.value && hasContent.value && !loading.value && !isDisabled.value)
33+
34+
/**
35+
* 点击处理
36+
*/
37+
const handleClick = () => {
38+
if (!isDisabled.value) {
39+
clear()
40+
}
41+
}
42+
</script>
43+
44+
<template>
45+
<ActionButton
46+
v-if="show"
47+
:icon="IconClear"
48+
:disabled="isDisabled"
49+
:tooltip="tooltipRenderFn"
50+
:tooltip-placement="tooltipPlacement"
51+
@click="handleClick"
52+
/>
53+
</template>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<script setup lang="ts">
2+
import { useSenderContext } from '../../sender/context'
3+
import ClearButton from '../clear-button/index.vue'
4+
import SubmitButton from '../submit-button/index.vue'
5+
6+
const { hasContent, loading } = useSenderContext()
7+
</script>
8+
9+
<template>
10+
<div class="tr-default-action-buttons">
11+
<Transition name="tr-slide-right">
12+
<div v-if="hasContent || loading" class="tr-action-buttons-group">
13+
<Transition name="tr-slide-right">
14+
<ClearButton />
15+
</Transition>
16+
<div class="tr-submit-wrapper">
17+
<SubmitButton />
18+
</div>
19+
</div>
20+
</Transition>
21+
</div>
22+
</template>
23+
24+
<style lang="less" scoped>
25+
.tr-default-action-buttons {
26+
display: flex;
27+
align-items: center;
28+
gap: 12px;
29+
min-height: var(--tr-sender-button-size-submit);
30+
31+
.tr-action-buttons-group {
32+
display: flex;
33+
align-items: center;
34+
gap: 8px;
35+
}
36+
37+
.tr-submit-wrapper {
38+
display: flex;
39+
align-items: center;
40+
}
41+
}
42+
43+
// 动画样式
44+
.tr-slide-right-enter-active,
45+
.tr-slide-right-leave-active {
46+
transition: all 0.3s cubic-bezier(0.34, 0.69, 0.1, 1);
47+
}
48+
49+
.tr-slide-right-enter-from,
50+
.tr-slide-right-leave-to {
51+
opacity: 0;
52+
transform: translateX(10px);
53+
}
54+
</style>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Sender Actions 组件导出
3+
*
4+
* 包含所有操作按钮组件:
5+
* - ActionButton: 基础按钮
6+
* - SubmitButton: 提交按钮
7+
* - ClearButton: 清空按钮
8+
* - UploadButton: 上传按钮
9+
* - VoiceButton: 语音输入按钮
10+
* - WordCounter: 字数统计
11+
* - DefaultActionButtons: 默认按钮组合
12+
*/
13+
export { default as ActionButton } from './action-button/index.vue'
14+
export { default as SubmitButton } from './submit-button/index.vue'
15+
export { default as ClearButton } from './clear-button/index.vue'
16+
export { default as UploadButton } from './upload-button/index.vue'
17+
export { default as VoiceButton } from './voice-button/index.vue'
18+
export { default as WordCounter } from './word-counter/index.vue'
19+
export { default as DefaultActionButtons } from './default-actions/index.vue'
20+
21+
// 导出语音相关 Hook
22+
export { useSpeechHandler } from './voice-button/useSpeechHandler'
23+
export { WebSpeechHandler } from './voice-button/webSpeechHandler'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Sender Actions 类型导出
3+
*/
4+
5+
// 导出共享类型
6+
export type { TooltipPlacement, TooltipContent, ActionButtonProps } from './types/common'
7+
8+
// 导出组件特有类型
9+
export type { UploadButtonProps, UploadButtonEmits } from './upload-button/index.type'
10+
export type { VoiceButtonProps, VoiceButtonEmits } from './voice-button/index.type'
11+
export type {
12+
SpeechConfig,
13+
SpeechHandler,
14+
SpeechState,
15+
SpeechCallbacks,
16+
SpeechHookOptions,
17+
SpeechHandlerResult,
18+
} from './voice-button/speech.types'

0 commit comments

Comments
 (0)