generated from kgod/ai-review-template
193 lines
4.6 KiB
Vue
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>
|