美化ai chat:1,聊天输入框高度应该根据内容自适应,就像chatgpt的输入框一样
2,占位符文本应该垂直居中 3,我发送的信息的圆角风格因该与输入框的圆角风格保持一致
This commit is contained in:
parent
67c33806b2
commit
02a83b7e61
@ -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%;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user