message 组件开发
This commit is contained in:
parent
2f3a5eb968
commit
6a1fed8513
@ -104,18 +104,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="input-box" :class="{ 'wide-screen': meta.wideScreen }">
|
||||
<div class="input-area">
|
||||
<a-textarea
|
||||
class="user-input"
|
||||
v-model:value="conv.inputText"
|
||||
@keydown="handleKeyDown"
|
||||
placeholder="输入问题……"
|
||||
:auto-size="{ minRows: 2, maxRows: 10 }"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-options">
|
||||
<div class="options__left">
|
||||
<div class="message-input-wrapper">
|
||||
<MessageInputComponent
|
||||
v-model="conv.inputText"
|
||||
:is-loading="isStreaming"
|
||||
:send-button-disabled="!conv.inputText && !isStreaming"
|
||||
:auto-size="{ minRows: 2, maxRows: 10 }"
|
||||
:custom-classes="{ 'wide-screen': meta.wideScreen }"
|
||||
@send="sendMessage"
|
||||
@keydown="handleKeyDown"
|
||||
>
|
||||
<template #options-left>
|
||||
<div
|
||||
:class="{'switch': true, 'opt-item': true, 'active': meta.use_web}"
|
||||
v-if="configStore.config.enable_web_search"
|
||||
@ -151,15 +150,10 @@
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div class="options__right">
|
||||
<a-button size="large" @click="sendMessage" :disabled="(!conv.inputText && !isStreaming)" type="link">
|
||||
<template #icon> <ArrowUpOutlined v-if="!isStreaming" /> <LoadingOutlined v-else/> </template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MessageInputComponent>
|
||||
<p class="note">请注意辨别内容的可靠性 By {{ configStore.config?.model_provider }}: {{ configStore.config?.model_name }}</p>
|
||||
</div>
|
||||
<p class="note">请注意辨别内容的可靠性 By {{ configStore.config?.model_provider }}: {{ configStore.config?.model_name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -197,6 +191,7 @@ import { message } from 'ant-design-vue'
|
||||
import RefsComponent from '@/components/RefsComponent.vue'
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/github.css';
|
||||
import MessageInputComponent from '@/components/MessageInputComponent.vue'
|
||||
|
||||
const props = defineProps({
|
||||
conv: Object,
|
||||
@ -618,7 +613,7 @@ watch(
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
background: white;
|
||||
background: var(--main-light-6);
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
flex: 5 5 200px;
|
||||
@ -629,7 +624,8 @@ watch(
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background-color: white;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -872,124 +868,29 @@ watch(
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 4px 2rem 0 2rem;
|
||||
background: white;
|
||||
|
||||
.input-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.message-input-wrapper {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 1rem;
|
||||
background: var(--gray-50);
|
||||
transition: background, border 0.3s, box-shadow 0.3s, max-width 0.3s ease;
|
||||
background-color: white;
|
||||
|
||||
&.wide-screen {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border: 2px solid var(--main-500);
|
||||
background: white;
|
||||
.note {
|
||||
width: 100%;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
color: #ccc;
|
||||
margin: 4px 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.input-options {
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
|
||||
.options__left,
|
||||
.options__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.options__left {
|
||||
flex: 1;
|
||||
|
||||
.opt-item {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--gray-300);
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: var(--gray-700);
|
||||
|
||||
&.active {
|
||||
color: var(--main-600);
|
||||
border: 1px solid var(--main-500);
|
||||
background-color: var(--main-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
textarea.user-input {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
padding: 0.5rem 0.5rem;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
margin: 0 0;
|
||||
color: #111111;
|
||||
font-size: 16px;
|
||||
font-variation-settings: 'wght' 400, 'opsz' 10.5;
|
||||
outline: none;
|
||||
resize: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.ant-btn-icon-only {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
background-color: var(--main-color);
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
transition: color 0.3s;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--main-800);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--gray-400);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.note {
|
||||
width: 100%;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
padding: 0rem;
|
||||
color: #ccc;
|
||||
margin: 4px 0;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ant-dropdown-link {
|
||||
color: var(--gray-900);
|
||||
cursor: pointer;
|
||||
|
||||
247
web/src/components/MessageInputComponent.vue
Normal file
247
web/src/components/MessageInputComponent.vue
Normal file
@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<div class="input-box" :class="customClasses">
|
||||
<div class="input-area">
|
||||
<a-textarea
|
||||
class="user-input"
|
||||
v-model:value="inputValue"
|
||||
@keydown="handleKeyPress"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:auto-size="autoSize"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-options">
|
||||
<div class="options__left">
|
||||
<slot name="options-left"></slot>
|
||||
</div>
|
||||
<div class="options__right">
|
||||
<a-button @click="handleSend" :disabled="sendButtonDisabled" type="link">
|
||||
<template #icon>
|
||||
<component :is="getIcon" />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, toRefs } from 'vue';
|
||||
import {
|
||||
SendOutlined,
|
||||
ArrowUpOutlined,
|
||||
LoadingOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '输入问题...'
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
sendButtonDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
autoSize: {
|
||||
type: Object,
|
||||
default: () => ({ minRows: 2, maxRows: 6 })
|
||||
},
|
||||
sendIcon: {
|
||||
type: String,
|
||||
default: 'ArrowUpOutlined'
|
||||
},
|
||||
customClasses: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'send', 'keydown']);
|
||||
|
||||
// 图标映射
|
||||
const iconComponents = {
|
||||
'SendOutlined': SendOutlined,
|
||||
'ArrowUpOutlined': ArrowUpOutlined,
|
||||
'LoadingOutlined': LoadingOutlined
|
||||
};
|
||||
|
||||
// 根据传入的图标名动态获取组件
|
||||
const getIcon = computed(() => {
|
||||
if (props.isLoading) {
|
||||
return LoadingOutlined;
|
||||
}
|
||||
return iconComponents[props.sendIcon] || ArrowUpOutlined;
|
||||
});
|
||||
|
||||
// 创建本地引用以进行双向绑定
|
||||
const inputValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
});
|
||||
|
||||
// 处理键盘事件
|
||||
const handleKeyPress = (e) => {
|
||||
emit('keydown', e);
|
||||
};
|
||||
|
||||
// 处理发送按钮点击
|
||||
const handleSend = () => {
|
||||
emit('send');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 0.8rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--main-500);
|
||||
background: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&.wide-screen {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-input {
|
||||
flex: 1;
|
||||
min-height: 44px;
|
||||
padding: 0.5rem 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
margin: 0;
|
||||
color: #222222;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
line-height: 1.6;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #888888;
|
||||
}
|
||||
}
|
||||
|
||||
.input-options {
|
||||
display: flex;
|
||||
padding: 8px 0 0;
|
||||
margin-top: 6px;
|
||||
border-top: 1px solid var(--gray-100);
|
||||
|
||||
.options__left,
|
||||
.options__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.options__right {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.options__left {
|
||||
flex: 1;
|
||||
|
||||
:deep(.opt-item) {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--gray-300);
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: var(--gray-700);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--main-10);
|
||||
color: var(--main-600);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--main-600);
|
||||
border: 1px solid var(--main-500);
|
||||
background-color: var(--main-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.ant-btn-icon-only {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
background-color: var(--main-500);
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
color: white;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--main-600);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--gray-400);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.input-box {
|
||||
border-radius: 15px;
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -90,26 +90,18 @@
|
||||
</div>
|
||||
|
||||
<div class="bottom">
|
||||
<div class="input-box">
|
||||
<div class="input-area">
|
||||
<a-textarea
|
||||
class="user-input"
|
||||
v-model:value="userInput"
|
||||
@keydown="handleKeyDown"
|
||||
placeholder="输入问题..."
|
||||
:disabled="!currentAgent || isProcessing"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-options">
|
||||
<div class="options__right">
|
||||
<a-button size="large" @click="sendMessage" :disabled="!userInput || !currentAgent || isProcessing" type="link">
|
||||
<template #icon> <SendOutlined v-if="!isProcessing" /> <LoadingOutlined v-else/> </template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-input-wrapper">
|
||||
<MessageInputComponent
|
||||
v-model="userInput"
|
||||
:is-loading="isProcessing"
|
||||
:disabled="!currentAgent || isProcessing"
|
||||
:send-button-disabled="!userInput || !currentAgent || isProcessing"
|
||||
:placeholder="'输入问题...'"
|
||||
@send="sendMessage"
|
||||
@keydown="handleKeyDown"
|
||||
/>
|
||||
<p class="note">请注意辨别内容的可靠性</p>
|
||||
</div>
|
||||
<p class="note">请注意辨别内容的可靠性</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -167,6 +159,7 @@ import { markedHighlight } from 'marked-highlight';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
import 'highlight.js/styles/github.css';
|
||||
import hljs from 'highlight.js';
|
||||
import MessageInputComponent from '@/components/MessageInputComponent.vue'
|
||||
|
||||
// ==================== 初始化配置 ====================
|
||||
|
||||
@ -1232,89 +1225,20 @@ const toggleToolCall = (toolCallId) => {
|
||||
padding: 4px 2rem 0 2rem;
|
||||
background: white;
|
||||
|
||||
.input-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.message-input-wrapper {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 1rem;
|
||||
background: var(--gray-50);
|
||||
transition: background, border 0.3s, box-shadow 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border: 2px solid var(--main-500);
|
||||
background: white;
|
||||
.note {
|
||||
width: 100%;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
color: #ccc;
|
||||
margin: 4px 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.input-options {
|
||||
display: flex;
|
||||
padding: 4px 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.user-input {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
padding: 0.5rem 0.5rem;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
margin: 0;
|
||||
color: var(--gray-900);
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.ant-btn-icon-only {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
background-color: var(--main-color);
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
transition: color 0.3s;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--main-800);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--gray-400);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.note {
|
||||
width: 100%;
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
padding: 0rem;
|
||||
color: #ccc;
|
||||
margin: 4px 0;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1481,15 +1405,6 @@ const toggleToolCall = (toolCallId) => {
|
||||
|
||||
.bottom {
|
||||
padding: 0.5rem 0.5rem;
|
||||
|
||||
.input-box {
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.note {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user