|
|
@@ -87,9 +87,10 @@ const localError = ref('')
|
|
|
let errorTimer: number | undefined
|
|
|
|
|
|
const canSubmit = computed(() => !props.disabled && !props.sending && (text.value.trim().length > 0 || attachments.value.length > 0))
|
|
|
-// A running group still accepts new room messages. Only an ordinary session
|
|
|
-// turns its primary send affordance into an interrupt control.
|
|
|
-const showStop = computed(() => props.streaming && !text.value.trim() && !attachments.value.length && (props.stopWhileRunning || props.mode === 'chat'))
|
|
|
+const canStop = computed(() => props.streaming && (props.stopWhileRunning || props.mode === 'chat'))
|
|
|
+// A running session keeps an explicit interrupt control even when the composer
|
|
|
+// holds a draft, so resource waits can always be cancelled by the user.
|
|
|
+const showStop = computed(() => canStop.value && !text.value.trim() && !attachments.value.length)
|
|
|
const compactModel = computed(() => props.modelLabel.split('/').filter(Boolean).at(-1) || props.modelLabel)
|
|
|
const contextPercent = computed(() => props.contextLimit > 0 ? Math.min(100, Math.round(props.contextUsed / props.contextLimit * 100)) : 0)
|
|
|
const hasContext = computed(() => props.contextLimit > 0)
|
|
|
@@ -539,6 +540,11 @@ defineExpose({
|
|
|
{{ queueMode ? '排队' : 'Steer' }}
|
|
|
</button>
|
|
|
<span v-if="sending" class="composer-sending" role="status" aria-live="polite"><i />正在发送</span>
|
|
|
+ <span v-if="canStop && !showStop" class="composer-stop-hitbox">
|
|
|
+ <button class="composer-stop" type="button" aria-label="停止生成" title="停止生成" @click="emit('stop')">
|
|
|
+ <AppIcon name="stop" :size="15" />
|
|
|
+ </button>
|
|
|
+ </span>
|
|
|
<span class="composer-send-hitbox">
|
|
|
<button
|
|
|
class="composer-send"
|
|
|
@@ -640,10 +646,13 @@ defineExpose({
|
|
|
.queue-toggle { height: 26px; padding: 0 7px; border: 0; border-radius: 8px; background: transparent; color: var(--text-muted); cursor: pointer; font-size: 10px; }
|
|
|
.queue-toggle:hover, .queue-toggle.active { background: var(--surface-hover); color: var(--text-primary); }
|
|
|
.composer-send-hitbox { display: grid; place-items: center; width: 34px; height: 34px; }
|
|
|
+.composer-stop-hitbox { display: grid; place-items: center; width: 30px; height: 34px; }
|
|
|
.composer-send { display: grid; place-items: center; width: 34px; min-width: 34px; height: 34px; padding: 0; border: 0; border-radius: 50%; background: var(--accent); color: var(--text-on-solid); cursor: pointer; box-shadow: 0 4px 12px rgba(41,36,39,.18); transition: transform 150ms ease, box-shadow 150ms ease; }
|
|
|
.composer-send:not(:disabled):hover { transform: translateY(-1px); box-shadow: 0 7px 16px rgba(41,36,39,.22); }
|
|
|
.composer-send:disabled { cursor: not-allowed; background: #9f9f9f; color: #fff; }
|
|
|
.composer-send--stop { background: var(--accent); }
|
|
|
+.composer-stop { display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 1px solid var(--line); border-radius: 50%; background: var(--surface-raised); color: var(--text-secondary); cursor: pointer; transition: color 150ms ease, border-color 150ms ease; }
|
|
|
+.composer-stop:hover { color: var(--danger); border-color: var(--danger); }
|
|
|
.composer-sending { display: inline-flex; align-items: center; gap: 5px; color: var(--text-muted); font-size: 9px; white-space: nowrap; }.composer-sending i { width: 5px; height: 5px; border-radius: 50%; background: var(--warning); animation: composer-pulse 1s ease-in-out infinite; }
|
|
|
@keyframes composer-pulse { 50% { opacity: .25; transform: scale(.7); } }
|
|
|
.composer-attachments { display: flex; width: 100%; max-width: 760px; margin: 0 auto 9px; flex-wrap: wrap; gap: 7px; }
|