美化ai chat:1,聊天输入框高度应该根据内容自适应,就像chatgpt的输入框一样

2,占位符文本应该垂直居中
3,我发送的信息的圆角风格因该与输入框的圆角风格保持一致
This commit is contained in:
jingrow 2026-05-24 23:26:51 +08:00
parent 67c33806b2
commit 02a83b7e61
3 changed files with 51 additions and 75 deletions

View File

@ -7,26 +7,11 @@ const props = defineProps<{
message: AiChatMessage
}>()
const isUser = computed(() => props.message.role === 'user')
const isSystem = computed(() => props.message.role === 'system')
const isTool = computed(() => props.message.role === 'tool')
</script>
<template>
<div class="message-row" :class="[message.role]">
<!-- Avatar -->
<div class="message-avatar" v-if="!isSystem">
<div class="avatar-icon" :class="isUser ? 'user-avatar' : 'ai-avatar'">
<svg v-if="isUser" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
<svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</svg>
</div>
</div>
<!-- Bubble -->
<div class="message-bubble" :class="[message.role]">
<div v-if="isTool" class="tool-indicator">
@ -57,75 +42,59 @@ function renderContent(content: string): string {
<style scoped>
.message-row {
display: flex;
gap: 12px;
margin-bottom: 20px;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.message-row.user {
flex-direction: row-reverse;
align-items: flex-end;
}
.message-row.system {
justify-content: center;
}
.avatar-icon {
width: 32px;
height: 32px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.user-avatar {
background: var(--n-primary-color, #6366f1);
color: #fff;
}
.ai-avatar {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: #fff;
}
.message-bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 12px;
max-width: 75%;
font-size: 14px;
line-height: 1.7;
word-break: break-word;
}
/* 用户:浅灰背景 + 统一大圆角(与输入框一致) */
.message-bubble.user {
background: var(--n-primary-color, #6366f1);
color: #fff;
border-bottom-right-radius: 4px;
padding: 10px 16px;
background: #f3f4f6;
color: #1f2937;
border-radius: 24px;
}
/* AI纯文本无背景无边框 */
.message-bubble.assistant {
background: var(--n-color-embedded, #f5f5f5);
color: var(--n-text-color, #333);
border-bottom-left-radius: 4px;
padding: 10px 0;
background: transparent;
color: #374151;
}
.message-bubble.system {
background: var(--n-color-embedded-modal, #fff8e1);
color: var(--n-text-color, #333);
padding: 8px 16px;
background: #fffbeb;
color: #92400e;
font-size: 13px;
font-style: italic;
border-radius: 10px;
max-width: 90%;
text-align: center;
}
.message-bubble.tool {
background: var(--n-color-embedded, #f0f0f0);
color: var(--n-text-color-2, #666);
padding: 8px 12px;
background: #f9fafb;
color: #6b7280;
font-size: 13px;
font-family: monospace;
border-radius: 8px;
max-width: 80%;
}

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { watch, ref } from 'vue'
import { watch, ref, nextTick } from 'vue'
import { NSpin } from 'naive-ui'
import { t } from '@/shared/i18n'
import { useChat } from '@/shared/composables/useChat'
@ -22,6 +22,14 @@ const {
} = useChat()
const inputText = ref('')
const textareaRef = ref<HTMLTextAreaElement | null>(null)
function autoResize() {
const el = textareaRef.value
if (!el) return
el.style.height = 'auto'
el.style.height = el.scrollHeight + 'px'
}
// chatName
watch(() => props.chatName, (name) => {
@ -46,6 +54,9 @@ async function handleSend() {
const content = inputText.value.trim()
if (!content || sending.value || !props.chatName) return
inputText.value = ''
if (textareaRef.value) {
textareaRef.value.style.height = 'auto'
}
emit('send', props.chatName, content)
const result = await send(props.chatName, content)
if (result.success && result.title) {
@ -91,10 +102,12 @@ function onKeydown(e: KeyboardEvent) {
</svg>
</button>
<textarea
ref="textareaRef"
v-model="inputText"
:placeholder="t('Type a message...')"
class="pill-textarea"
rows="1"
@input="autoResize"
@keydown="onKeydown"
:disabled="sending"
></textarea>
@ -195,7 +208,7 @@ function onKeydown(e: KeyboardEvent) {
.pill-input {
display: flex;
align-items: flex-end;
align-items: center;
gap: 6px;
width: 100%;
background: var(--n-color-embedded, #f5f5f5);
@ -238,7 +251,7 @@ function onKeydown(e: KeyboardEvent) {
line-height: 1.5;
background: transparent;
color: var(--n-text-color, #333);
max-height: 120px;
max-height: 300px;
min-height: 24px;
font-family: inherit;
}

View File

@ -165,6 +165,14 @@ function formatTime(dateStr: string | null) {
// ===== =====
const inputText = ref('')
const sending = ref(false)
const welcomeTextareaRef = ref<HTMLTextAreaElement | null>(null)
function autoResizeWelcome() {
const el = welcomeTextareaRef.value
if (!el) return
el.style.height = 'auto'
el.style.height = el.scrollHeight + 'px'
}
async function handleSend() {
const content = inputText.value.trim()
@ -179,6 +187,9 @@ async function handleSend() {
activeChatName.value = createResult.name
activeChatTitle.value = createResult.title || ''
inputText.value = ''
if (welcomeTextareaRef.value) {
welcomeTextareaRef.value.style.height = 'auto'
}
await loadChats()
}
} catch (e) {
@ -309,10 +320,12 @@ onMounted(() => {
<Icon icon="tabler:paperclip" :size="18" />
</button>
<textarea
ref="welcomeTextareaRef"
v-model="inputText"
:placeholder="t('Ask anything')"
class="pill-textarea"
rows="1"
@input="autoResizeWelcome"
@keydown="handleWelcomeKeydown"
:disabled="sending"
></textarea>
@ -329,9 +342,6 @@ onMounted(() => {
<!-- 聊天视图 -->
<template v-else>
<div class="chat-header">
<div class="chat-header-title">{{ activeChatTitle || t('Untitled') }}</div>
</div>
<ChatPanel
:chat-name="activeChatName"
entity="File"
@ -653,7 +663,7 @@ onMounted(() => {
/* ===== 药丸输入框 ===== */
.pill-input {
display: flex;
align-items: flex-end;
align-items: center;
gap: 6px;
width: 100%;
background: #f5f5f5;
@ -728,20 +738,4 @@ onMounted(() => {
.pill-send:not(:disabled):hover {
opacity: 0.85;
}
/* ===== 聊天标题栏 ===== */
.chat-header {
padding: 12px 20px;
border-bottom: 1px solid #f0f0f0;
flex-shrink: 0;
}
.chat-header-title {
font-size: 14px;
font-weight: 600;
color: #1f2937;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>