perf: live mode entry

This commit is contained in:
Soulter
2026-01-21 15:59:17 +08:00
parent 991b85e0c0
commit aec5f4e9e6
2 changed files with 52 additions and 65 deletions
+50 -65
View File
@@ -1,19 +1,16 @@
<template>
<div class="input-area fade-in"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
<div class="input-area fade-in" @dragover.prevent="handleDragOver" @dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop">
<div class="input-container"
:style="{
width: '85%',
maxWidth: '900px',
margin: '0 auto',
border: isDark ? 'none' : '1px solid #e0e0e0',
borderRadius: '24px',
boxShadow: isDark ? 'none' : '0px 2px 2px rgba(0, 0, 0, 0.1)',
backgroundColor: isDark ? '#2d2d2d' : 'transparent',
position: 'relative'
}">
<div class="input-container" :style="{
width: '85%',
maxWidth: '900px',
margin: '0 auto',
border: isDark ? 'none' : '1px solid #e0e0e0',
borderRadius: '24px',
boxShadow: isDark ? 'none' : '0px 2px 2px rgba(0, 0, 0, 0.1)',
backgroundColor: isDark ? '#2d2d2d' : 'transparent',
position: 'relative'
}">
<!-- 拖拽上传遮罩 -->
<transition name="fade">
<div v-if="isDragging" class="drop-overlay">
@@ -30,35 +27,24 @@
<v-icon size="small" class="reply-icon">mdi-reply</v-icon>
"<span class="reply-text">{{ props.replyTo.selectedText }}</span>"
</div>
<v-btn @click="handleClearReply" class="remove-reply-btn" icon="mdi-close" size="x-small" color="grey" variant="text" />
<v-btn @click="handleClearReply" class="remove-reply-btn" icon="mdi-close" size="x-small"
color="grey" variant="text" />
</div>
</transition>
<textarea
ref="inputField"
v-model="localPrompt"
@keydown="handleKeyDown"
:disabled="disabled"
<textarea ref="inputField" v-model="localPrompt" @keydown="handleKeyDown" :disabled="disabled"
placeholder="Ask AstrBot..."
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>
<div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 14px;">
<div style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px;">
<div
style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px;">
<!-- Settings Menu -->
<StyledMenu offset="8" location="top start" :close-on-content-click="false">
<template v-slot:activator="{ props: activatorProps }">
<v-btn
v-bind="activatorProps"
icon="mdi-plus"
variant="text"
color="deep-purple"
/>
<v-btn v-bind="activatorProps" icon="mdi-plus" variant="text" color="deep-purple" />
</template>
<!-- Upload Files -->
<v-list-item
class="styled-menu-item"
rounded="md"
@click="triggerImageInput"
>
<v-list-item class="styled-menu-item" rounded="md" @click="triggerImageInput">
<template v-slot:prepend>
<v-icon icon="mdi-file-upload-outline" size="small"></v-icon>
</template>
@@ -66,22 +52,14 @@
{{ tm('input.upload') }}
</v-list-item-title>
</v-list-item>
<!-- Config Selector in Menu -->
<ConfigSelector
:session-id="sessionId || null"
:platform-id="sessionPlatformId"
:is-group="sessionIsGroup"
:initial-config-id="props.configId"
@config-changed="handleConfigChange"
/>
<ConfigSelector :session-id="sessionId || null" :platform-id="sessionPlatformId"
:is-group="sessionIsGroup" :initial-config-id="props.configId"
@config-changed="handleConfigChange" />
<!-- Streaming Toggle in Menu -->
<v-list-item
class="styled-menu-item"
rounded="md"
@click="$emit('toggleStreaming')"
>
<v-list-item class="styled-menu-item" rounded="md" @click="$emit('toggleStreaming')">
<template v-slot:prepend>
<v-icon :icon="enableStreaming ? 'mdi-flash' : 'mdi-flash-off'" size="small"></v-icon>
</template>
@@ -90,15 +68,14 @@
</v-list-item-title>
</v-list-item>
</StyledMenu>
<!-- Provider/Model Selector Menu -->
<ProviderModelMenu v-if="showProviderSelector" ref="providerModelMenuRef" />
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 8px; align-items: center;">
<input type="file" ref="imageInputRef" @change="handleFileSelect"
style="display: none" multiple />
<input type="file" ref="imageInputRef" @change="handleFileSelect" style="display: none" multiple />
<v-progress-circular v-if="disabled" indeterminate size="16" class="mr-1" width="1.5" />
<v-btn @click="$emit('openLiveMode')"
<!-- <v-btn @click="$emit('openLiveMode')"
icon
variant="text"
color="purple"
@@ -108,15 +85,11 @@
<v-tooltip activator="parent" location="top">
{{ tm('voice.liveMode') }}
</v-tooltip>
</v-btn>
<v-btn @click="handleRecordClick"
icon
variant="text"
:color="isRecording ? 'error' : 'deep-purple'"
class="record-btn"
size="small"
>
<v-icon :icon="isRecording ? 'mdi-stop-circle' : 'mdi-microphone'" variant="text" plain></v-icon>
</v-btn> -->
<v-btn @click="handleRecordClick" icon variant="text" :color="isRecording ? 'error' : 'deep-purple'"
class="record-btn" size="small">
<v-icon :icon="isRecording ? 'mdi-stop-circle' : 'mdi-microphone'" variant="text"
plain></v-icon>
<v-tooltip activator="parent" location="top">
{{ isRecording ? tm('voice.speaking') : tm('voice.startRecording') }}
</v-tooltip>
@@ -128,11 +101,12 @@
</div>
<!-- 附件预览区 -->
<div class="attachments-preview" v-if="stagedImagesUrl.length > 0 || stagedAudioUrl || (stagedFiles && stagedFiles.length > 0)">
<div class="attachments-preview"
v-if="stagedImagesUrl.length > 0 || stagedAudioUrl || (stagedFiles && stagedFiles.length > 0)">
<div v-for="(img, index) in stagedImagesUrl" :key="'img-' + index" class="image-preview">
<img :src="img" class="preview-image" />
<v-btn @click="$emit('removeImage', index)" class="remove-attachment-btn" icon="mdi-close"
size="small" color="error" variant="text" />
<v-btn @click="$emit('removeImage', index)" class="remove-attachment-btn" icon="mdi-close" size="small"
color="error" variant="text" />
</div>
<div v-if="stagedAudioUrl" class="audio-preview">
@@ -255,9 +229,17 @@ function handleReplyAfterLeave() {
}
function handleKeyDown(e: KeyboardEvent) {
// Enter 发送消息
// Enter 发送消息或触发命令
if (e.keyCode === 13 && !e.shiftKey) {
e.preventDefault();
// 检查是否是 /astr_live_dev 命令
if (localPrompt.value.trim() === '/astr_live_dev') {
emit('openLiveMode');
localPrompt.value = '';
return;
}
if (canSend.value) {
emit('send');
}
@@ -458,6 +440,7 @@ defineExpose({
padding-top: 0;
padding-bottom: 0;
}
to {
max-height: 500px;
opacity: 1;
@@ -475,6 +458,7 @@ defineExpose({
padding-top: 8px;
padding-bottom: 8px;
}
to {
max-height: 0;
opacity: 0;
@@ -571,6 +555,7 @@ defineExpose({
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
@@ -581,7 +566,7 @@ defineExpose({
.input-area {
padding: 0 !important;
}
.input-container {
width: 100% !important;
max-width: 100% !important;
@@ -8,6 +8,8 @@
@click="toggleNervousMode" flat variant="text" :color="isNervousMode ? 'primary' : ''" />
</div>
<span style="color: gray; padding-left: 16px;">We're developing Astr Live Mode on ChatUI & Desktop right now. Stay tuned!</span>
<div class="live-mode-content">
<div class="center-circle-container" @click="handleCircleClick">
<!-- 爆炸效果层 -->