<template>
|
<div class="chat-container">
|
<div class="messages">
|
<div v-for="(msg, index) in messages" :key="index"
|
:class="['message-bubble', msg.role, { 'with-think': msg.think }]">
|
<!-- 思考内容区域 -->
|
<div v-if="msg.think" class="think-section">
|
<div class="think-header" @click="toggleThink(index)">
|
<span>思考过程</span>
|
<i :class="[
|
'arrow',
|
msg.isThinkExpanded
|
? 'el-icon-arrow-down'
|
: 'el-icon-arrow-right',
|
]"></i>
|
</div>
|
<transition name="slide">
|
<div v-show="msg.isThinkExpanded" class="think-content">
|
{{ msg.think }}
|
</div>
|
</transition>
|
</div>
|
|
<!-- 正常消息内容 -->
|
<div class="content">
|
<template v-if="msg.role === 'assistant'">
|
<span v-if="isGenerating && index === messages.length - 1">
|
<span v-if="!msg.think" class="generating-indicator">正在思考...</span>
|
<!-- <span class="cursor"></span> -->
|
</span>
|
{{ msg.content }}
|
</template>
|
<template v-else>
|
{{ msg.content }}
|
</template>
|
</div>
|
</div>
|
</div>
|
|
<div class="preview-area" v-if="uploadedImages.length > 0">
|
<div v-for="(img, index) in uploadedImages" :key="index" class="image-preview">
|
<img :src="'data:image/png;base64,' + img.base64" class="thumbnail" />
|
<span class="image-name">{{ img.name }}</span>
|
<span class="image-size">{{ formatSize(img.size) }}</span>
|
<i class="el-icon-close remove-icon" @click="removeImage(index)"></i>
|
</div>
|
</div>
|
<div class="input-area">
|
<div class="input-area2">
|
<textarea v-model="inputText" @keydown.enter.prevent="handleEnter" :disabled="isGenerating"
|
placeholder="请输入你的问题" class="input-textarea" rows="1" @input="autoResize"></textarea>
|
</div>
|
|
<div class="action-buttons">
|
<el-upload action="" :auto-upload="false" :show-file-list="false" :on-change="handleImageUpload"
|
accept="image/*" class="action-img" :disabled="true">
|
<i class="el-icon-picture upload-icon"></i>
|
</el-upload>
|
<div class="action-img">
|
<i class="el-icon-microphone microphone-icon"></i>
|
</div>
|
<button @click="startStream" :disabled="isGenerating" class="send-button">
|
<i class="el-icon-position send-icon"></i>
|
</button>
|
</div>
|
</div>
|
<div class="tip-text">内容由AI大模型生成,请仔细甄别</div>
|
</div>
|
</template>
|
|
<script>
|
import AiRetrieval from "@/api/AiRetrievalView";
|
export default {
|
props: ["currentChatId"],
|
watch: {
|
currentChatId: {
|
immediate: true,
|
handler(newVal) {
|
if (newVal) this.loadChat(); // 监听chatId变化
|
},
|
},
|
},
|
name: "AiRetrieval",
|
data() {
|
return {
|
chatUrl: "",
|
severUrl:"",
|
params: "",
|
chatId: null,
|
messages: [],
|
inputText: "",
|
isGenerating: false,
|
controller: null,
|
uploadedImages: [],
|
};
|
},
|
mounted() {
|
this.fetchCameraInfo();
|
},
|
methods: {
|
async fetchCameraInfo() {
|
const response = await fetch("/config.json");
|
if (!response.ok) throw new Error(`请求失败: ${response.status}`);
|
const responseData = await response.json();
|
this.chatUrl = responseData.chatUrl;
|
this.severUrl = responseData.severUrl;
|
// console.info("chatUrl:"+this.chatUrl);
|
},
|
toggleThink(index) {
|
this.$set(
|
this.messages[index],
|
"isThinkExpanded",
|
!this.messages[index].isThinkExpanded
|
);
|
},
|
handleItemSend(params) {
|
this.$emit("list-selected", params); // 触发自定义事件 刷新右侧内容区域
|
},
|
async loadChat() {
|
if (!this.currentChatId) return;
|
|
try {
|
// console.info("新对话"+this.currentChatId);
|
const res = await AiRetrieval.getDetail({
|
chatId: this.currentChatId,
|
});
|
// console.info(res.data);
|
|
this.messages = [];
|
this.chatId = res.data[0].chatId;
|
// console.info(+this.chatId )
|
for (let i = 0; i < res.data.length; i++) {
|
this.messages.push({
|
role: "user",
|
content: res.data[i].question,
|
});
|
this.messages.push({
|
role: "assistant",
|
content: res.data[i].content,
|
think: res.data[i].think,
|
});
|
}
|
// this.messages = res.data.data; // 假设接口返回消息列表
|
this.$nextTick(() => {
|
const container = this.$el.querySelector(".messages");
|
container.scrollTop = container.scrollHeight;
|
});
|
} catch (error) {
|
console.error("加载聊天失败:", error);
|
}
|
},
|
|
// async saveToDatabase(chatData) {
|
// if (this.currentChatId) { // 更新逻辑
|
// await AiRetrieval.updateChat(this.currentChatId, chatData);
|
// } else { // 新建逻辑
|
// const res = await AiRetrieval.createChat(chatData);
|
// this.currentChatId = res.data.chatId;
|
// }
|
// },
|
reset() {
|
// console.info("清空对话记录");
|
// 清空对话记录
|
this.messages = [];
|
// 重置输入框
|
this.inputMessage = "";
|
// 如果有其他需要重置的状态
|
this.searchResults = [];
|
this.currentPage = 1;
|
// 可以根据需要添加其他重置逻辑
|
},
|
// 新增自动调整高度方法
|
autoResize(e) {
|
const textarea = e.target;
|
textarea.style.height = "auto"; // 重置高度
|
const maxHeight = 6 * 24; // 6行*24px行高 + 8px*2内边距
|
textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`;
|
},
|
handleImageUpload(file) {
|
const reader = new FileReader();
|
reader.onload = (e) => {
|
const base64Data = e.target.result.split(",")[1];
|
this.uploadedImages.push({
|
name: file.name,
|
size: file.size,
|
base64: base64Data,
|
});
|
};
|
reader.readAsDataURL(file.raw);
|
},
|
removeImage(index) {
|
this.uploadedImages.splice(index, 1);
|
},
|
formatSize(bytes) {
|
if (bytes === 0) return "0 B";
|
const k = 1024;
|
const sizes = ["B", "KB", "MB", "GB"];
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
},
|
handleEnter(e) {
|
if (!e.shiftKey && !this.isGenerating) {
|
this.startStream();
|
}
|
},
|
async startStream() {
|
if (!this.inputText.trim() || this.isGenerating) return;
|
|
this.messages.push({ role: "user", content: this.inputText });
|
const userMessage = this.inputText;
|
this.inputText = "";
|
this.messages.push({
|
role: "assistant",
|
content: "",
|
think: "",
|
isThinkExpanded: true,
|
});
|
// this.$nextTick(() => {
|
// const container = this.$el.querySelector(".messages");
|
// container.scrollTop = container.scrollHeight;
|
// });
|
this.isGenerating = true;
|
|
try {
|
this.controller = new AbortController();
|
|
let ids = [];
|
let idsStr = "";
|
//筛选最终结果
|
// const response = await AiRetrieval.getChatDetail(
|
// {
|
// message:userMessage
|
// }
|
// );
|
const response = await fetch(this.severUrl +"/v1/record/chat", {
|
method: "POST",
|
headers: {
|
"Content-Type": "application/json",
|
},
|
body: JSON.stringify({
|
message: userMessage
|
}),
|
});
|
|
if (!response.ok) throw new Error(`请求失败: ${response.status}`);
|
|
const reader = response.body.getReader();
|
const decoder = new TextDecoder();
|
let boo = true;
|
// 修改后的fetch处理逻辑
|
while (true) {
|
const { done, value } = await reader.read();
|
if (done) break;
|
|
const chunk = decoder.decode(value, { stream: true });
|
const lines = chunk.split("\n").filter((line) => line.trim());
|
|
// 保存最新消息的引用
|
const lastMessage = this.messages[this.messages.length - 1];
|
|
for (const line of lines) {
|
try {
|
const json = JSON.parse(line.replace("data:", "").trim());
|
if (json.data === true) break;
|
// 直接替换而不是追加
|
if (json.data) {
|
|
// 使用正则表达式匹配思考内容
|
const thoughtMatch = json.data.match(
|
/<think>(.*?)(?=\n<\/think>|$)/s
|
);
|
if (thoughtMatch) {
|
// 更新消息内容 带think
|
lastMessage.think = thoughtMatch[1].trim();
|
let con = json.data //第一部分:
|
.match(/<\/think>([\s\S]*)/)[1]
|
.trim();
|
// console.info('con='+con)
|
const rescou = con
|
.match(/第一部分:(.*?)(?=\n第二部分:|$)/s)[1]
|
.trim();
|
|
// console.info('rescou='+rescou)
|
if (rescou) {
|
lastMessage.content = rescou;
|
idsStr = con.match(/第二部分:([\s\S]*)/)[1].trim();
|
// console.info("idsStr=" + idsStr);
|
boo = false;
|
}
|
} else {
|
// 更新消息内容
|
lastMessage.think = json.data
|
.match(/<think>([\s\S]*)/)[1]
|
.trim();
|
}
|
}
|
// console.info("boo:" + boo)
|
} catch (e) {
|
console.info("")
|
}
|
}
|
}
|
|
// console.info("boo:"+boo)
|
if (boo) {
|
this.messages[this.messages.length - 1].content = "匹配到的数据为0条";
|
} else {
|
ids = idsStr.split(",");
|
}
|
// this.handleItemSend(lists);//发送数据展示到页面
|
this.handleItemSend(ids);
|
// // 保存到数据库的逻辑
|
// console.info("保存旧数据"+this.chatId)
|
if (this.messages.length >= 2) {
|
// console.info(this.messages)
|
const userMsg = this.messages[this.messages.length - 2];
|
const assistantMsg = this.messages[this.messages.length - 1];
|
this.saveToDatabase({
|
question: userMsg.content,
|
content: assistantMsg.content,
|
think: assistantMsg.think,
|
chatId: this.chatId,
|
// images: this.uploadedImages
|
});
|
}
|
} catch (error) {
|
if (error.name !== "AbortError") {
|
console.error("生成失败:", error);
|
this.messages[this.messages.length - 1].content += "(生成失败)";
|
}
|
} finally {
|
this.isGenerating = false;
|
this.controller = null;
|
}
|
},
|
// async startStream() {
|
// if (!this.inputText.trim() || this.isGenerating) return;
|
|
// this.messages.push({ role: "user", content: this.inputText });
|
// const userMessage = this.inputText;
|
// this.inputText = "";
|
// this.messages.push({
|
// role: "assistant",
|
// content: "",
|
// think: "",
|
// isThinkExpanded: true,
|
// });
|
// // this.$nextTick(() => {
|
// // const container = this.$el.querySelector(".messages");
|
// // container.scrollTop = container.scrollHeight;
|
// // });
|
// this.isGenerating = true;
|
|
// try {
|
// this.controller = new AbortController();
|
|
// //优化用户问题
|
// const resstar =
|
// "milvus数据库中存储了id、摄像机抓拍图片、图片的文本描述,图片文本描述是通过qwen模型把图片中包含的内容形成了文本内容。用户会输入从milvus数据库中查找图片的需求,请帮我把用户的问题改写一下,以便在milvus数据库中查找到更加准确的且符合用户期望的数据。不要输出改写说明 /nothink";
|
// const startRes = await fetch(this.chatUrl + "/chat", {
|
// method: "POST",
|
// headers: {
|
// "Content-Type": "application/json",
|
// },
|
// body: JSON.stringify({
|
// prompt: resstar,
|
// llm_name: "qwen3:8b",//qwen2.5vl
|
// messages: [
|
// ...this.messages.slice(0, -1),
|
// { role: "user", content: userMessage },
|
// ],
|
// stream: false,
|
// gen_conf: {
|
// temperature: 0.1,
|
// max_tokens: 1000
|
// }
|
// // image:this.uploadedImages[0]?.base64 || ''
|
// }),
|
// });
|
// // console.info("chat地址:"+"/api-AI" + "/chat")
|
// if (!startRes.ok) throw new Error(`请求失败: ${startRes.status}`);
|
// const responseData = await startRes.json();
|
|
|
// let res1 = responseData.data.split('\n\n')
|
// let rescou = res1.length>1?res1[2]:""
|
// //let rescou = responseData.data.match(/!(.*?)(?=\n!|$)/s)[1].trim();
|
// console.info(rescou);
|
// if (!rescou) {
|
// rescou = userMessage;
|
// }
|
// //检索向量数据库
|
// const res = await fetch(this.chatUrl + "/search", {
|
// method: "POST",
|
// headers: {
|
// "Content-Type": "application/json",
|
// },
|
// body: JSON.stringify({
|
// collection_name: "smartrag",
|
// query_text: rescou,
|
// search_mode: "hybrid",
|
// limit: 20,
|
// weight_dense: 0.7,
|
// weight_sparse: 0.3,
|
// filter_expr: "",
|
// output_fields: [
|
// "id",
|
// "image_path",
|
// "create_timestamp",
|
// "task_name",
|
// "event_level_name",
|
// "rtsp_address",
|
// "video_point_id",
|
// "zh_desc_class",
|
// "video_path",
|
// "detect_time",
|
// ],
|
// }),
|
// });
|
|
// // console.info(res.json())
|
// const text = await res.text();
|
// const data = JSONbig({ storeAsString: true }).parse(text);
|
// console.info(data);
|
// let lists = [];
|
// let ids = [];
|
// let idsStr = "";
|
// for (let i = 0; i < data.results.length; i++) {
|
// // console.info(data.results[i].entity.id)
|
// this.params +=
|
// "信息id:" +
|
// data.results[i].entity.id +
|
// ",内容:" +
|
// data.results[i].entity.video_path +
|
// "摄像头在" +
|
// data.results[i].entity.detect_time +
|
// "时间拍摄的图片" +
|
// data.results[i].entity.zh_desc_class +
|
// "\n";
|
// lists.push(data.results[i].entity);
|
// }
|
|
// // const mes =
|
// // "用户询问【" +
|
// // rescou +
|
// // "】。\n请在以下信息中进行匹配,信息为\n" +
|
// // this.params +
|
// // "。\n回答:【XXX信息id】";
|
|
|
// const mes =
|
// "用户询问【" +
|
// rescou +
|
// "】。\n请在以下信息中进行匹配,信息为\n" +
|
// this.params +
|
// "。\n不进行推理和解译,必须返回两部分结果,且结果中必须带有第一部分或第二部分四个字,第一部分为总结匹配到多少条,第二部分的格式必须是 第二部分:XXX,XXX其中XXX为数据库id";
|
// this.params = "";
|
// if (!res.ok) throw new Error(`请求失败: ${res.status}`);
|
|
// let contentMes = mes.replace(/\s+/g, '')
|
|
// //筛选最终结果
|
// const response = await fetch(this.chatUrl + "/chat", {
|
// method: "POST",
|
// headers: {
|
// "Content-Type": "application/json",
|
// },
|
// body: JSON.stringify({
|
// prompt: "",
|
// llm_name: "qwen3:8b",//qwen2.5vl
|
// messages: [
|
// ...this.messages.slice(0, -1),
|
// { role: "user", content: contentMes },
|
// ],
|
// stream: true,
|
// gen_conf: {
|
// temperature: 0.7,
|
// max_tokens: 4000,
|
// },
|
// // image:this.uploadedImages[0]?.base64 || ''
|
// }),
|
// });
|
|
// if (!response.ok) throw new Error(`请求失败: ${response.status}`);
|
|
// const reader = response.body.getReader();
|
// const decoder = new TextDecoder();
|
// let boo = true;
|
// // 修改后的fetch处理逻辑
|
// while (true) {
|
// const { done, value } = await reader.read();
|
// if (done) break;
|
|
// const chunk = decoder.decode(value, { stream: true });
|
// const lines = chunk.split("\n").filter((line) => line.trim());
|
|
// // 保存最新消息的引用
|
// const lastMessage = this.messages[this.messages.length - 1];
|
|
// for (const line of lines) {
|
// try {
|
// const json = JSON.parse(line.replace("data:", "").trim());
|
// if (json.data === true) break;
|
// // 直接替换而不是追加
|
// if (json.data) {
|
|
// // 使用正则表达式匹配思考内容
|
// const thoughtMatch = json.data.match(
|
// /<think>(.*?)(?=\n<\/think>|$)/s
|
// );
|
// if (thoughtMatch) {
|
// // 更新消息内容 带think
|
// lastMessage.think = thoughtMatch[1].trim();
|
// let con = json.data //第一部分:
|
// .match(/<\/think>([\s\S]*)/)[1]
|
// .trim();
|
// // console.info('con='+con)
|
// const rescou = con
|
// .match(/第一部分:(.*?)(?=\n第二部分:|$)/s)[1]
|
// .trim();
|
|
// // console.info('rescou='+rescou)
|
// if (rescou) {
|
// lastMessage.content = rescou;
|
// idsStr = con.match(/第二部分:([\s\S]*)/)[1].trim();
|
// // console.info("idsStr=" + idsStr);
|
// boo = false;
|
// }
|
// } else {
|
// // 更新消息内容
|
// lastMessage.think = json.data
|
// .match(/<think>([\s\S]*)/)[1]
|
// .trim();
|
// }
|
// }
|
// } catch (e) {
|
// // console.error("解析错误:", e);
|
// }
|
// }
|
// // this.$nextTick(() => {
|
// // const container = this.$el.querySelector(".messages");
|
// // container.scrollTop = container.scrollHeight;
|
// // });
|
// }
|
|
// if (boo) {
|
// this.messages[this.messages.length - 1].content = "匹配到的数据为0条";
|
// lists = [];
|
// } else {
|
// ids = idsStr.split(",");
|
// }
|
// // this.handleItemSend(lists);//发送数据展示到页面
|
// this.handleItemSend(ids);
|
// // // 保存到数据库的逻辑
|
// // console.info("保存旧数据"+this.chatId)
|
// if (this.messages.length >= 2) {
|
// // console.info(this.messages)
|
// const userMsg = this.messages[this.messages.length - 2];
|
// const assistantMsg = this.messages[this.messages.length - 1];
|
// this.saveToDatabase({
|
// question: userMsg.content,
|
// content: assistantMsg.content,
|
// think: assistantMsg.think,
|
// chatId: this.chatId,
|
// // images: this.uploadedImages
|
// });
|
// }
|
// } catch (error) {
|
// if (error.name !== "AbortError") {
|
// console.error("生成失败:", error);
|
// this.messages[this.messages.length - 1].content += "(生成失败)";
|
// }
|
// } finally {
|
// this.isGenerating = false;
|
// this.controller = null;
|
// }
|
// },
|
async saveToDatabase(chatData) {
|
// 实际调用API接口
|
try {
|
const response = await AiRetrieval.createChat(chatData);
|
// console.info(response.data);
|
this.chatId = response.data || null;
|
} catch (error) {
|
console.error("API Error:", error);
|
}
|
},
|
cancelGeneration() {
|
if (this.controller) this.controller.abort();
|
},
|
},
|
};
|
</script>
|
|
<style scoped>
|
/* 新增思考内容样式 */
|
.message-bubble.assistant.with-think {
|
border-radius: 12px;
|
}
|
|
.think-section {
|
background: #f8f9fa;
|
padding: 12px;
|
border-bottom: 1px solid #e9ecef;
|
text-align: left;
|
}
|
|
.think-header {
|
font-size: 12px;
|
color: #6c757d;
|
margin-bottom: 8px;
|
display: flex;
|
align-items: center;
|
font-weight: 500;
|
}
|
|
.think-header::before {
|
content: "💡";
|
margin-right: 6px;
|
}
|
|
.think-content {
|
font-size: 10px;
|
color: #495057;
|
line-height: 1.6;
|
white-space: pre-wrap;
|
}
|
|
.generating-indicator {
|
color: #6c757d;
|
font-size: 0.9em;
|
margin-right: 8px;
|
}
|
|
/* 调整原有样式 */
|
.message-bubble.assistant .content {
|
background: #f5f6f8;
|
padding: 12px 15px;
|
border-radius: 0 0 12px 12px;
|
text-align: left;
|
}
|
|
/* 新增图片相关样式 */
|
.preview-area {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 8px;
|
padding: 8px;
|
background: #f5f5f5;
|
border-radius: 4px;
|
margin-bottom: 8px;
|
}
|
|
.image-preview {
|
position: relative;
|
display: flex;
|
align-items: center;
|
gap: 4px;
|
padding: 4px;
|
background: white;
|
border-radius: 4px;
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
}
|
|
.thumbnail {
|
width: 30px;
|
height: 30px;
|
object-fit: cover;
|
border-radius: 2px;
|
}
|
|
.image-name {
|
font-size: 12px;
|
max-width: 100px;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.image-size {
|
font-size: 10px;
|
color: #666;
|
}
|
|
.remove-icon {
|
position: absolute;
|
top: -8px;
|
right: -8px;
|
font-size: 12px;
|
background: white;
|
border-radius: 50%;
|
cursor: pointer;
|
}
|
|
.action-img {
|
width: 20px;
|
height: 20px;
|
border-radius: 13px;
|
padding: 6px;
|
background-color: #f2f8ff;
|
}
|
|
.upload-icon {
|
font-size: 20px;
|
color: #7fade6;
|
cursor: pointer;
|
}
|
|
.chat-container {
|
position: relative;
|
width: 100%;
|
max-width: 500px;
|
height: 600px;
|
margin: 0 auto;
|
/* background: #f1f1f1; */
|
border-radius: 12px;
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
display: flex;
|
flex-direction: column;
|
}
|
|
.close-button {
|
position: absolute;
|
right: 15px;
|
top: 15px;
|
font-size: 20px;
|
color: #999;
|
cursor: pointer;
|
z-index: 1;
|
transition: color 0.2s;
|
}
|
|
.close-button:hover {
|
color: #666;
|
}
|
|
.messages {
|
flex: 1;
|
padding: 20px;
|
overflow-y: auto;
|
display: flex;
|
flex-direction: column;
|
gap: 15px;
|
}
|
|
.message-bubble {
|
max-width: 80%;
|
position: relative;
|
font-size: 14px;
|
line-height: 1.5;
|
}
|
|
.message-bubble.user {
|
align-self: flex-end;
|
}
|
|
.message-bubble.assistant {
|
align-self: flex-start;
|
}
|
|
.content {
|
padding: 12px 15px;
|
border-radius: 5px;
|
word-break: break-word;
|
}
|
|
.user .content {
|
background: #cadcff;
|
color: #2e2f31;
|
border-radius: 12px 12px 0 12px;
|
}
|
|
.assistant .content {
|
background: white;
|
color: #2e2f31;
|
border-radius: 12px 12px 12px 0;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
}
|
|
.input-area {
|
display: flex;
|
flex-direction: column;
|
/* 改为垂直布局 */
|
background: #fff;
|
border: 2px solid #1890ff;
|
border-radius: 10px;
|
padding-top: 8px;
|
gap: 8px;
|
/* 添加间距 */
|
min-height: 80px;
|
max-height: 200px;
|
align-items: center;
|
/* position: relative; */
|
}
|
|
.input-area2 {
|
width: 100%;
|
display: flex;
|
background: #fff;
|
min-height: 60px;
|
max-height: 180px;
|
/* border: 2px solid #1890ff; */
|
}
|
|
.input-textarea {
|
/* background: #0c0c0c; */
|
width: 100%;
|
border: none;
|
resize: none;
|
line-height: 24px;
|
/* 精确行高 */
|
font-size: 14px;
|
padding: 8px 15px;
|
min-height: 24px !important;
|
/* 强制单行高度 */
|
max-height: 152px !important;
|
/* 强制6行高度 */
|
overflow-y: auto;
|
box-sizing: content-box;
|
/* 防止padding影响计算 */
|
}
|
|
.input-textarea:focus {
|
outline: none;
|
box-shadow: none;
|
}
|
|
.wechat-input {
|
flex: 1;
|
min-height: 40px;
|
max-height: 144px;
|
/* 6行 x 24px行高 */
|
padding: 8px 0;
|
line-height: 1.5;
|
border: 0;
|
background: transparent;
|
font-size: 16px;
|
color: #2c3e50;
|
resize: none;
|
overflow-y: auto;
|
}
|
|
.wechat-input:focus {
|
border-color: #07c160;
|
background: white;
|
box-shadow: 0 4px 12px rgba(7, 193, 96, 0.1);
|
/* 添加聚焦阴影 */
|
}
|
|
.wechat-input::placeholder {
|
color: #a8c5e6;
|
/* 浅蓝色占位符 */
|
}
|
|
.microphone-icon {
|
font-size: 20px;
|
color: #7fade6;
|
cursor: pointer;
|
}
|
|
/* 操作按钮区域 */
|
.action-buttons {
|
width: 100%;
|
display: flex;
|
justify-content: flex-end;
|
/* 右对齐按钮 */
|
gap: 8px;
|
padding: 0 0 5px 0;
|
background: transparent;
|
/* border: 2px solid #1890ff; */
|
}
|
|
/* 发送按钮改造 */
|
.send-button {
|
width: 45px;
|
height: 25px;
|
border-radius: 10px;
|
background: #1890ff;
|
border: none;
|
padding: 0;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
transition: all 0.3s ease;
|
margin: 2px 6px;
|
}
|
|
.send-icon {
|
color: white;
|
font-size: 16px;
|
/* transform: rotate(45deg); */
|
}
|
|
.send-button:disabled {
|
background: #b6d7ff;
|
cursor: not-allowed;
|
}
|
|
/* 提示文本 */
|
.tip-text {
|
text-align: center;
|
font-size: 12px;
|
color: #95a5c6;
|
margin: 12px 0 10px 0;
|
letter-spacing: 0.5px;
|
}
|
|
/* 输入框禁用状态 */
|
.wechat-input:disabled {
|
background: #f0f0f0;
|
opacity: 0.8;
|
}
|
|
.send-button:not(:disabled):hover {
|
background: #2482ff;
|
}
|
|
.cursor {
|
display: inline-block;
|
width: 8px;
|
height: 16px;
|
background: #666;
|
margin-left: 2px;
|
animation: blink 1s infinite;
|
}
|
|
@keyframes blink {
|
|
0%,
|
100% {
|
opacity: 1;
|
}
|
|
50% {
|
opacity: 0;
|
}
|
}
|
</style>
|