|
32 | 32 | </div> |
33 | 33 | </transition> |
34 | 34 | <textarea ref="inputField" v-model="localPrompt" @keydown="handleKeyDown" :disabled="disabled" |
35 | | - placeholder="Ask AstrBot..." |
36 | | - style="width: 100%; resize: none; outline: none; border: 1px solid var(--v-theme-border); border-radius: 12px; padding: 12px 16px; min-height: 40px; font-family: inherit; font-size: 16px; background-color: var(--v-theme-surface);"></textarea> |
| 35 | + placeholder="Ask AstrBot..." class="chat-textarea" |
| 36 | + autocomplete="off" autocorrect="off" autocapitalize="sentences" spellcheck="false" |
| 37 | + style="width: 100%; resize: none; outline: none; border: 1px solid var(--v-theme-border); border-radius: 12px; padding: 16px 20px; min-height: 40px; max-height: 200px; overflow-y: auto; font-family: inherit; font-size: 16px; background-color: var(--v-theme-surface);"></textarea> |
37 | 38 | <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 14px;"> |
38 | 39 | <div |
39 | | - style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px;"> |
| 40 | + style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px; min-width: 0; flex: 1; overflow: hidden;"> |
40 | 41 | <!-- Settings Menu --> |
41 | 42 | <StyledMenu offset="8" location="top start" :close-on-content-click="false"> |
42 | 43 | <template v-slot:activator="{ props: activatorProps }"> |
|
72 | 73 | <!-- Provider/Model Selector Menu --> |
73 | 74 | <ProviderModelMenu v-if="showProviderSelector" ref="providerModelMenuRef" /> |
74 | 75 | </div> |
75 | | - <div style="display: flex; justify-content: flex-end; margin-top: 8px; align-items: center;"> |
| 76 | + <div style="display: flex; justify-content: flex-end; margin-top: 8px; align-items: center; flex-shrink: 0;"> |
76 | 77 | <input type="file" ref="imageInputRef" @change="handleFileSelect" style="display: none" multiple /> |
77 | | - <v-progress-circular v-if="disabled" indeterminate size="16" class="mr-1" width="1.5" /> |
| 78 | + <v-progress-circular v-if="disabled && !mobile" indeterminate size="16" class="mr-1" width="1.5" /> |
78 | 79 | <!-- <v-btn @click="$emit('openLiveMode')" |
79 | 80 | icon |
80 | 81 | variant="text" |
|
87 | 88 | </v-tooltip> |
88 | 89 | </v-btn> --> |
89 | 90 | <v-btn @click="handleRecordClick" icon variant="text" :color="isRecording ? 'error' : 'deep-purple'" |
90 | | - class="record-btn" size="small"> |
| 91 | + class="record-btn"> |
91 | 92 | <v-icon :icon="isRecording ? 'mdi-stop-circle' : 'mdi-microphone'" variant="text" |
92 | 93 | plain></v-icon> |
93 | 94 | <v-tooltip activator="parent" location="top"> |
94 | 95 | {{ isRecording ? tm('voice.speaking') : tm('voice.startRecording') }} |
95 | 96 | </v-tooltip> |
96 | 97 | </v-btn> |
97 | | - <v-btn |
98 | | - icon |
99 | | - v-if="isRunning" |
100 | | - @click="$emit('stop')" |
101 | | - variant="text" |
102 | | - class="send-btn" |
103 | | - size="small" |
104 | | - > |
| 98 | + <v-btn icon v-if="isRunning" @click="$emit('stop')" variant="tonal" color="deep-purple" class="send-btn"> |
105 | 99 | <v-icon icon="mdi-stop" variant="text" plain></v-icon> |
106 | 100 | <v-tooltip activator="parent" location="top"> |
107 | 101 | {{ tm('input.stopGenerating') }} |
108 | 102 | </v-tooltip> |
109 | 103 | </v-btn> |
110 | | - <v-btn |
111 | | - v-else |
112 | | - @click="$emit('send')" |
113 | | - icon="mdi-send" |
114 | | - variant="text" |
115 | | - color="deep-purple" |
116 | | - :disabled="!canSend" |
117 | | - class="send-btn" |
118 | | - size="small" |
119 | | - /> |
| 104 | + <v-btn v-else @click="$emit('send')" icon="mdi-send" variant="tonal" color="deep-purple" |
| 105 | + :disabled="!canSend" class="send-btn" /> |
120 | 106 | </div> |
121 | 107 | </div> |
122 | 108 | </div> |
|
152 | 138 | </template> |
153 | 139 |
|
154 | 140 | <script setup lang="ts"> |
155 | | -import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; |
| 141 | +import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'; |
| 142 | +import { useDisplay } from 'vuetify'; |
156 | 143 | import { useModuleI18n } from '@/i18n/composables'; |
157 | 144 | import { useCustomizerStore } from '@/stores/customizer'; |
158 | 145 | import ConfigSelector from './ConfigSelector.vue'; |
@@ -251,21 +238,34 @@ function handleReplyAfterLeave() { |
251 | 238 | isReplyClosing.value = false; |
252 | 239 | } |
253 | 240 |
|
| 241 | +const { mobile } = useDisplay(); |
| 242 | +
|
| 243 | +// Auto-resize textarea |
| 244 | +function autoResize() { |
| 245 | + const el = inputField.value; |
| 246 | + if (!el) return; |
| 247 | + el.style.height = 'auto'; |
| 248 | + el.style.height = Math.min(el.scrollHeight, 200) + 'px'; |
| 249 | +} |
| 250 | +
|
| 251 | +watch(localPrompt, () => { |
| 252 | + nextTick(autoResize); |
| 253 | +}); |
| 254 | +
|
254 | 255 | function handleKeyDown(e: KeyboardEvent) { |
255 | | - // Enter 发送消息或触发命令 |
256 | | - if (e.keyCode === 13 && !e.shiftKey) { |
| 256 | + // Enter 插入换行(桌面和手机端均如此,发送通过右下角发送按鈕) |
| 257 | + // Shift+Enter 发送(Ctrl+Enter / Cmd+Enter 也保留) |
| 258 | + if (e.keyCode === 13 && (e.shiftKey || e.ctrlKey || e.metaKey)) { |
257 | 259 | e.preventDefault(); |
258 | | -
|
259 | | - // 检查是否是 /astr_live_dev 命令 |
260 | 260 | if (localPrompt.value.trim() === '/astr_live_dev') { |
261 | 261 | emit('openLiveMode'); |
262 | 262 | localPrompt.value = ''; |
263 | 263 | return; |
264 | 264 | } |
265 | | -
|
266 | 265 | if (canSend.value) { |
267 | 266 | emit('send'); |
268 | 267 | } |
| 268 | + return; |
269 | 269 | } |
270 | 270 |
|
271 | 271 | // Ctrl+B 录音 |
@@ -588,11 +588,20 @@ defineExpose({ |
588 | 588 | @media (max-width: 768px) { |
589 | 589 | .input-area { |
590 | 590 | padding: 0 !important; |
| 591 | + padding-bottom: 10px !important; |
591 | 592 | } |
592 | 593 |
|
593 | 594 | .input-container { |
594 | 595 | width: 100% !important; |
595 | 596 | max-width: 100% !important; |
596 | 597 | } |
| 598 | +
|
| 599 | + .input-area textarea, |
| 600 | + .chat-textarea { |
| 601 | + min-height: 32px !important; |
| 602 | + max-height: 160px !important; |
| 603 | + font-size: 16px !important; |
| 604 | + padding: 16px 16px 12px 16px !important; |
| 605 | + } |
597 | 606 | } |
598 | 607 | </style> |
0 commit comments