Files
resume-agent/frontend/src/components/ComposerBar.vue
T

193 lines
4.6 KiB
Vue

<script setup lang="ts">
import { nextTick, ref } from 'vue'
import type { ComposerConfig } from '../types/resumeAgent'
const props = withDefaults(
defineProps<{
config: ComposerConfig
sending?: boolean
disabled?: boolean
}>(),
{ sending: false, disabled: false },
)
const emit = defineEmits<{ send: [message: string] }>()
const message = ref('')
const textarea = ref<HTMLTextAreaElement | null>(null)
function resizeTextarea() {
const element = textarea.value
if (!element) return
element.style.height = 'auto'
element.style.height = `${Math.min(element.scrollHeight, 132)}px`
}
function send() {
const clean = message.value.trim()
if (!clean || props.sending || props.disabled || props.config.disabled) return
emit('send', clean)
message.value = ''
void nextTick(resizeTextarea)
}
function handleKeydown(event: KeyboardEvent) {
if (event.key !== 'Enter' || event.shiftKey || event.isComposing) return
event.preventDefault()
send()
}
</script>
<template>
<div class="composer" :class="`composer--${config.mode}`">
<div v-if="config.mode === 'ui_only'" class="composer-ui-only" role="status">
<span aria-hidden="true"></span>
<p>{{ config.helper_text || '完成上方这一步后,对话会继续。' }}</p>
</div>
<form v-else class="composer-form" @submit.prevent="send">
<label class="visually-hidden" for="resume-agent-message">给简历助手发送消息</label>
<textarea
id="resume-agent-message"
ref="textarea"
v-model="message"
rows="1"
:maxlength="config.max_length || 8000"
:placeholder="config.placeholder || (disabled ? '完成创建后即可继续补充经历' : '继续讲讲你的经历…')"
:disabled="sending || disabled || config.disabled"
@input="resizeTextarea"
@keydown="handleKeydown"
/>
<button
type="submit"
:disabled="!message.trim() || sending || disabled || config.disabled"
aria-label="发送消息"
>
<span v-if="sending" class="composer-spinner" aria-hidden="true" />
<svg v-else viewBox="0 0 24 24" aria-hidden="true">
<path d="m5 12 13-7-4.6 14-2.2-5.2L5 12Z" />
<path d="m11.2 13.8 3.1-3.1" />
</svg>
</button>
<p class="composer-hint">
{{ config.helper_text || 'Enter 发送 · Shift + Enter 换行' }}
</p>
</form>
</div>
</template>
<style scoped>
.composer {
position: sticky;
z-index: 8;
bottom: 0;
padding: 14px 0 20px;
background: linear-gradient(to bottom, rgba(245, 250, 249, 0), rgba(245, 250, 249, 0.94) 24%, #f5faf9 62%);
}
.composer-form {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) 46px;
gap: 9px;
padding: 8px;
border: 1px solid rgba(168, 204, 200, 0.92);
border-radius: 19px;
background: rgba(255, 255, 255, 0.96);
box-shadow: var(--shadow-float);
backdrop-filter: blur(16px);
}
.composer-form textarea {
width: 100%;
min-height: 45px;
max-height: 132px;
padding: 12px 10px 9px;
border: 0;
outline: none;
color: var(--ink);
background: transparent;
font-size: 14px;
line-height: 1.5;
resize: none;
}
.composer-form textarea::placeholder {
color: #90a5a8;
}
.composer-form textarea:disabled {
color: #87999c;
}
.composer-form button {
display: grid;
width: 42px;
height: 42px;
place-items: center;
align-self: end;
border: 0;
border-radius: 14px;
color: #fff;
background: var(--brand-dark);
box-shadow: 0 8px 18px rgba(20, 127, 133, 0.24);
}
.composer-form button:disabled {
color: #d7e3e2;
background: #afc9c7;
box-shadow: none;
}
.composer-form svg {
width: 21px;
height: 21px;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 1.8;
}
.composer-hint {
grid-column: 1 / -1;
margin: -2px 5px 1px;
color: var(--ink-faint);
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
font-size: 9px;
}
.composer-ui-only {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 43px;
padding: 10px 14px;
border: 1px solid rgba(200, 221, 218, 0.85);
border-radius: 15px;
color: var(--ink-faint);
background: rgba(247, 251, 250, 0.9);
font-size: 12px;
backdrop-filter: blur(12px);
pointer-events: none;
}
.composer-ui-only span {
color: var(--brand-dark);
font-size: 16px;
}
.composer-ui-only p {
margin: 0;
}
.composer-spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.35);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
</style>