123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820 |
- <template>
- <div class="layout">
- <div class="menu" :class="{ collapsed: isMenuCollapsed }">
- <!-- 添加收缩按钮 -->
- <div
- class="collapse-left-button"
- :class="{ 'menu-collapsed': isMenuCollapsed }"
- @click="toggleMenu"
- >
- <img
- :src="isMenuCollapsed ? rightIcon : leftIcon"
- alt="toggle"
- class="collapse-icon"
- />
- </div>
- <!-- Logo -->
- <div class="logo">
- <img
- src="https://ecsaas.raycos.com.cn/web/uploads/20230713/3690c0badc2c7be4552a253f72d6d701.png"
- :draggable="false"
- alt="logo"
- class="logo-img"
- />
- <span>Raycos Tech</span>
- </div>
- <!-- New Conversation 按钮 -->
- <button class="new-conversation-btn" @click="addNewConversation">
- <img class="tab-icon" src="../assets/svg/plus.svg" alt="" />
- <span>New Conversation</span>
- </button>
- <!-- 会话列表 -->
- <div class="conversation-list">
- <div
- v-for="conversation in conversations"
- :key="conversation.id"
- :class="[
- 'conversation-item',
- { active: currentConversationId === conversation.id },
- ]"
- @click="switchConversation(conversation.id)"
- >
- {{ conversation.title }}
- </div>
- </div>
- <!-- 添加模型配置和语音配置的容器 -->
- <div class="config-container">
- <KnowledgeConfig />
- <ModelConfig />
- <VoiceConfig />
- </div>
- </div>
- <div class="chat">
- <!-- 欢迎消息 -->
- <div v-if="messages.length === 0" class="welcome-section">
- <!-- Do you want 部分 -->
- <div class="suggestion-section">
- <h3>嗨,我是你的智能助手</h3>
- <div class="cards-container">
- <div class="suggestion-card">
- <div class="card-header">
- <img
- src="../assets/svg/hot-for-ux.svg"
- alt="hot topics"
- class="tab-icon"
- />
- <h4>推荐问题</h4>
- </div>
- <p class="card-subtitle">为您精选的热门问题</p>
- <div class="suggestion-items">
- <!-- Combine both QA pairs and guides -->
- <div
- v-for="(item, index) in [
- ...currentQaPairs,
- ...currentDesignGuides,
- ]"
- :key="index"
- class="suggestion-item"
- @click="handleQaClick(item)"
- >
- {{ item.question }}
- </div>
- </div>
- </div>
- <!-- Hot Topics 卡片 --><!-- Design Guide 卡片 -->
- <!-- <div class="suggestion-card">
- <div class="card-header">
- <img
- src="../assets/svg/hot-for-ux.svg"
- alt="hot topics"
- class="tab-icon"
- />
- <h4>知识库相关问题:</h4>
- </div>
- <p class="card-subtitle">我们可以这样向知识库提问</p>
- <div class="suggestion-items">
- <div
- v-for="(qa, index) in currentQaPairs"
- :key="index"
- class="suggestion-item"
- @click="handleQaClick(qa)"
- >
- {{ qa.question }}
- </div>
- </div>
- </div>
-
- <div class="suggestion-card">
- <div class="card-header">
- <img
- src="../assets/svg/read.svg"
- alt="hot topics"
- class="tab-icon"
- />
- <h4>网上热搜问答</h4>
- </div>
- <p class="card-subtitle">搜索网上的热门回答</p>
- <div class="suggestion-items">
- <div
- v-for="(guide, index) in currentDesignGuides"
- :key="index"
- class="suggestion-item"
- @click="handleQaClick(guide)"
- >
- {{ guide.question }}
- </div>
- </div>
- </div> -->
- </div>
- </div>
- </div>
- <!-- 修改消息列表部分 -->
- <div v-else class="message-list" ref="messageList">
- <div
- v-for="message in messages"
- :key="message.id"
- :class="[
- 'message-item',
- message.role === 'user' ? 'user' : 'assistant',
- ]"
- >
- <div class="message-bubble">
- <div class="message-content">
- <template v-if="message.role === 'user' && message.files">
- <div
- v-for="file in message.files"
- :key="file.name"
- class="file-message"
- >
- <!-- 图片文件显示 -->
- <template v-if="isImageFile(file)">
- <div class="image-preview">
- <img :src="file.url" class="preview-image" />
- </div>
- </template>
- <!-- 视频文件显示 -->
- <template v-else-if="isVideoFile(file)">
- <div class="video-preview">
- <video
- controls
- class="preview-video"
- :src="file.url"
- preload="metadata"
- >
- Your browser does not support the video tag.
- </video>
- </div>
- </template>
- <!-- 文档文件显示 -->
- <template v-else>
- <div class="file-info-display">
- <img :src="getFileIcon(file)" class="file-type-icon" />
- <div class="file-details">
- <span class="file-name" :title="file.name">{{
- file.name
- }}</span>
- <span class="file-size">{{ file.size }}</span>
- </div>
- </div>
- </template>
- </div>
- </template>
- <div
- class="message-text"
- v-if="message.role === 'assistant'"
- v-html="renderMarkdown(message.displayContent)"
- ></div>
- <div v-else>{{ message.content }}</div>
- <!-- 音频控制按钮 -->
- <div
- v-if="message.role === 'assistant' && message.audioData"
- class="audio-controls"
- >
- <button class="audio-btn" @click="handleAudioClick(message)">
- <!-- 只在当前消息正在播放时显示波形动画 -->
- <div
- v-if="currentPlayingId === message.id"
- class="wave-animation"
- >
- <span class="wave-bar"></span>
- <span class="wave-bar"></span>
- <span class="wave-bar"></span>
- <span class="wave-bar"></span>
- </div>
- <!-- 其他情况显示播放图标 -->
- <img v-else :src="playIcon" alt="播放" class="audio-icon" />
- </button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 输入框 -->
- <div class="input-container">
- <!-- 标签栏 -->
- <div class="tabs-wrapper">
- <div class="tab-item active">
- <img
- src="../assets/svg/hot-for-ux.svg"
- alt="hot topics"
- class="tab-icon"
- />
- <span>Hot Topics</span>
- </div>
- <div class="tab-item">
- <img
- src="../assets/svg/read.svg"
- alt="hot topics"
- class="tab-icon"
- />
- <span>Design Guide</span>
- </div>
- <!-- 添加互联网搜索开关 -->
- <div class="web-search-toggle">
- <input
- type="checkbox"
- id="webSearchToggle"
- v-model="enableWebSearch"
- :disabled="attachedFiles.length > 0"
- class="toggle-input"
- />
- <label
- for="webSearchToggle"
- class="toggle-label"
- :class="{ disabled: attachedFiles.length > 0 }"
- >
- 启用互联网搜索
- <!-- <span v-if="attachedFiles.length > 0" class="disabled-hint">
- (文件上传时不可用)
- </span> -->
- </label>
- </div>
- </div>
- <!-- 添加附件面板 -->
- <div v-if="showAttachments" class="attachments-panel">
- <div class="panel-content">
- <div class="attachments-header">
- <span>Attachments</span>
- <button class="close-btn" @click="toggleAttachments">
- <img src="../assets/svg/close.svg" alt="" />
- </button>
- </div>
- <div
- v-if="attachedFiles.length === 0"
- style="display: flex; justify-content: center"
- >
- <div
- class="upload-area"
- :class="{ 'has-files': attachedFiles.length > 0 }"
- @click="triggerFileInput"
- @drop.prevent="handleFileDrop"
- @dragover.prevent
- @dragenter.prevent
- >
- <!-- 移除条件判断,让整个区域始终可点击 -->
- <template v-if="isUploading">
- <div class="upload-loading">
- <div class="loading-spinner"></div>
- <div class="upload-text">Uploading...</div>
- </div>
- </template>
- <template v-else>
- <img
- src="../assets/svg/upload.svg"
- alt="upload"
- class="upload-icon"
- />
- <div class="upload-text">Upload files</div>
- <div class="upload-hint">
- Click or drag files to this area to upload
- </div>
- </template>
- <input
- type="file"
- ref="fileInput"
- style="display: none"
- @change="handleFileUpload"
- multiple
- />
- </div>
- </div>
- <!-- 上传内容列表 -->
- <div v-if="attachedFiles.length !== 0">
- <div class="file-list">
- <div
- v-for="file in attachedFiles"
- :key="file.name"
- class="file-item"
- :class="{ 'is-image': isImageFile(file) }"
- >
- <div :class="isImageFile(file) ? 'file-wrapper' : ''">
- <!-- 添加文件 loading 状态 -->
- <div v-if="file.isUploading" class="file-loading">
- <div class="loading-spinner"></div>
- </div>
- <!-- 其他文件显示逻辑保持不变 -->
- <img
- v-else-if="isImageFile(file)"
- :src="file.url"
- alt="file"
- class="file-icon"
- />
- <video
- v-else-if="isVideoFile(file)"
- :src="file.url"
- class="file-icon"
- preload="metadata"
- >
- Your browser does not support the video tag.
- </video>
- <img
- v-else
- :src="getFileIcon(file)"
- alt="file"
- :class="
- isImageFile(file) || isVideoFile(file)
- ? 'file-icon'
- : ''
- "
- />
- <button
- class="remove-btn"
- @click.stop="removeFile(file)"
- v-if="!file.isUploading"
- >
- ×
- </button>
- </div>
- <div class="file-info" v-if="!isImageFile(file)">
- <span class="file-name">{{ file.name }}</span>
- <span class="file-size">{{ file.sizeFormatted }}</span>
- </div>
- </div>
- <button class="add-more-btn" @click.stop="triggerFileInput">
- <img src="../assets/svg/plus.svg" alt="add" />
- </button>
- </div>
- <input
- type="file"
- ref="fileInput"
- style="display: none"
- @change="handleFileUpload"
- multiple
- />
- </div>
- <!-- <div v-if="attachedFiles.length" class="attached-files">
- <div
- v-for="file in attachedFiles"
- :key="file.name"
- class="file-item"
- >
- <FileOutlined />
- <span>{{ file.name }}</span>
- <button class="remove-btn" @click="removeFile(file)">
- </button>
- </div>
- </div> -->
- </div>
- </div>
- <!-- 输入框部分 -->
- <div class="input-wrapper">
- <button class="attachment-btn" @click="toggleAttachments">
- <img
- src="../assets/svg/attachment.svg"
- alt="attachment"
- class="tab-icon"
- />
- </button>
- <input
- type="text"
- class="message-input"
- v-model="inputContent"
- @keyup.enter="sendMessage"
- placeholder="Type a message..."
- />
- <button
- class="send-btn"
- @click="sendMessage"
- :disabled="!inputContent && attachedFiles.length === 0"
- >
- <img src="../assets/svg/up.svg" alt="send" />
- </button>
- </div>
- </div>
- </div>
- <div class="right_menu" :class="{ collapsed: isRightMenuCollapsed }">
- <div class="collapse-button" @click="toggleRightMenu">
- <img
- :src="isRightMenuCollapsed ? leftIcon : rightIcon"
- alt="toggle"
- class="collapse-icon"
- />
- </div>
- <div class="right-menu-content">
- <SearchResults
- :searchResults="searchResults"
- :loading="isSearchLoading"
- :total="searchTotal"
- />
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, nextTick, onUnmounted, onMounted } from "vue";
- import {
- PlusOutlined,
- FireOutlined,
- ReadOutlined,
- HeartOutlined,
- SmileOutlined,
- CommentOutlined,
- CloudUploadOutlined,
- PaperClipOutlined,
- ShareAltOutlined,
- EllipsisOutlined,
- CloseOutlined,
- FileOutlined,
- ArrowUpOutlined,
- UploadOutlined,
- } from "@ant-design/icons-vue";
- import leftIcon from "../assets/svg/left.svg";
- import rightIcon from "../assets/svg/right.svg";
- import plusIcon from "../assets/svg/plus.svg";
- import hotForUxIcon from "../assets/svg/hot-for-ux.svg";
- import readIcon from "../assets/svg/read.svg";
- import attachmentIcon from "../assets/svg/attachment.svg";
- import uploadIcon from "../assets/svg/upload.svg";
- import closeIcon from "../assets/svg/close.svg";
- import upIcon from "../assets/svg/up.svg";
- import wordIcon from "../assets/svg/word.svg";
- import pauseIcon from "../assets/svg/pause.svg";
- import playIcon from "../assets/svg/play.svg";
- import axios from "axios";
- import { useStore } from "vuex";
- import MarkdownIt from "markdown-it";
- import ModelConfig from "../components/ModelConfig/index.vue";
- import SearchResults from "../components/SearchResults/index.vue";
- import VoiceConfig from "../components/VoiceConfig/index.vue";
- import KnowledgeConfig from "../components/KnowledgeConfig/index.vue";
- import ExportButton from "../components/ExportButton/index.vue";
- // 初始化 markdown-it
- const md = new MarkdownIt({
- html: true,
- linkify: true,
- typographer: true,
- });
- // 响应式状态
- const headerOpen = ref(false);
- const inputContent = ref("");
- const showAttachments = ref(false);
- const isRightMenuCollapsed = ref(false);
- const fileInput = ref(null);
- const attachedFiles = ref([]);
- const messages = ref([]);
- const isLoading = ref(false);
- const currentConversationId = ref(1);
- const conversationCounter = ref(1);
- const messageList = ref(null);
- const store = useStore();
- // 会话数据
- const conversations = ref([
- {
- id: 1,
- title: "当前会话",
- messages: [],
- },
- ]);
- // 添加打字效果相关的状态
- const typingSpeed = 50; // 打字速度(毫秒/字符)
- const isTyping = ref(false);
- // 添加音频播放相关的响应式变量
- const audioPlayer = ref(null);
- const isPlaying = ref(false);
- // 添加当前播放消息的 ID
- const currentPlayingId = ref(null);
- // 添加新的响应式变量
- const isUploading = ref(false);
- // 添加新的响应式状态
- const documentSummary = ref(null);
- const isLoadingSummary = ref(false);
- const currentQaPairs = ref([]);
- const currentDesignGuides = ref([]);
- let rotationInterval = null;
- // 添加新的响应式状态
- const searchResults = ref([]);
- const isSearchLoading = ref(false);
- const searchTotal = ref(0);
- // 添加互联网搜索开关状态
- const enableWebSearch = ref(false);
- // 添加新的响应式变量
- const isMenuCollapsed = ref(false);
- // 添加移动端检测
- const isMobile = ref(false);
- // 检测设备类型
- const checkMobile = () => {
- isMobile.value = window.innerWidth <= 768;
- };
- // 监听窗口大小变化
- onMounted(() => {
- checkMobile();
- window.addEventListener('resize', checkMobile);
- });
- onUnmounted(() => {
- window.removeEventListener('resize', checkMobile);
- });
- // 添加遮罩层点击处理
- const handleOverlayClick = () => {
- if (isMobile.value) {
- isMenuCollapsed.value = true;
- isRightMenuCollapsed.value = true;
- document.body.style.overflow = 'auto';
- }
- };
- // 修改菜单切换逻辑
- const toggleMenu = () => {
- isMenuCollapsed.value = !isMenuCollapsed.value;
- if (isMobile.value) {
- document.body.style.overflow = isMenuCollapsed.value ? 'auto' : 'hidden';
- if (!isMenuCollapsed.value) {
- isRightMenuCollapsed.value = true; // 确保右侧菜单关闭
- }
- }
- };
- // 修改右侧菜单切换逻辑
- const toggleRightMenu = () => {
- isRightMenuCollapsed.value = !isRightMenuCollapsed.value;
- if (isMobile.value) {
- document.body.style.overflow = isRightMenuCollapsed.value ? 'auto' : 'hidden';
- if (!isRightMenuCollapsed.value) {
- isMenuCollapsed.value = true; // 确保左侧菜单关闭
- }
- }
- };
- // 修改获取文档摘要的方法
- const fetchDocumentSummary = async () => {
- isLoadingSummary.value = true;
- try {
- const response = await axios.get(
- `${import.meta.env.VITE_BASE_AI_API}/api/document/summary`
- );
- console.log("API Response:", response.data);
- if (response.data.code === 200) {
- documentSummary.value = response.data.data.items;
- console.log("Document Summary:", documentSummary.value);
- startRotation();
- }
- } catch (error) {
- console.error("获取文档摘要失败:", error);
- } finally {
- isLoadingSummary.value = false;
- }
- };
- // 修改轮换显示逻辑
- const startRotation = () => {
- if (rotationInterval) {
- clearInterval(rotationInterval);
- }
- const updateItems = () => {
- if (!documentSummary.value?.length) {
- console.log("No document summary data available");
- return;
- }
- // 处理第一项数据(Hot Topics)
- try {
- const hotTopicsData = documentSummary.value[0];
- if (hotTopicsData?.questions) {
- const parsedQaPairs =
- /* JSON.parse( */ hotTopicsData.questions; /* .replace(/'/g, '"')); */
- const formattedQaPairs = parsedQaPairs.map((question) => ({
- question,
- }));
- // 随机选择3个问题
- const shuffled = [...formattedQaPairs].sort(() => 0.5 - Math.random());
- currentQaPairs.value = shuffled.slice(0, 3);
- }
- } catch (error) {
- console.error("解析 Hot Topics 失败:", error);
- }
- // 处理第二项数据(Design Guide)
- try {
- const designGuideData = documentSummary.value[1];
- if (designGuideData?.questions) {
- const parsedGuides =
- /* JSON.parse( */ designGuideData.questions; /* .replace(/'/g, '"')); */
- const formattedGuides = parsedGuides.map((question) => ({ question }));
- // 随机选择3个指南
- const shuffled = [...formattedGuides].sort(() => 0.5 - Math.random());
- currentDesignGuides.value = shuffled.slice(0, 3);
- }
- } catch (error) {
- console.error("解析 Design Guide 失败:", error);
- }
- };
- // 初始更新
- updateItems();
- // 每5秒更新一次
- rotationInterval = setInterval(updateItems, 5000);
- };
- // 处理QA点击事件
- const handleQaClick = (qa) => {
- console.log("QA clicked:", qa); // 添加日志
- if (qa?.question) {
- inputContent.value = qa.question;
- sendMessage();
- }
- };
- // 在组件卸载时清理定时器
- onUnmounted(() => {
- if (rotationInterval) {
- clearInterval(rotationInterval);
- }
- });
- // 在组件挂载时获取摘要
- onMounted(() => {
- fetchDocumentSummary();
- });
- // 方法
- const toggleAttachments = () => {
- showAttachments.value = !showAttachments.value;
- };
- const removeFile = (file) => {
- attachedFiles.value = attachedFiles.value.filter((f) => f !== file);
- };
- const triggerFileInput = () => {
- // 如果正在上传,不触发新的上传
- if (isUploading.value) return;
- fileInput.value?.click();
- };
- const uploadFile = async (file) => {
- try {
- const formData = new FormData();
- formData.append("file", file);
- const response = await axios.post(
- `${import.meta.env.VITE_BASE_AI_API}/upload/file`,
- formData,
- {
- headers: {
- "Content-Type": "multipart/form-data",
- },
- }
- );
- if (response.data.status === 200) {
- return response.data.data.fileUrl; // 返回文件上传后的URL
- } else {
- throw new Error(response.data.message || "文件上传失败");
- }
- } catch (error) {
- console.error("文件上传失败:", error);
- throw error;
- }
- };
- const handleFileUpload = async (event) => {
- const files = Array.from(event.target.files);
- if (files.length === 0) return;
- // 添加文件大小检查
- const MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10MB in bytes
- const invalidFiles = files.filter((file) => {
- if (isVideoFile(file) && file.size > MAX_VIDEO_SIZE) {
- return true;
- }
- return false;
- });
- if (invalidFiles.length > 0) {
- // 如果有超出大小限制的视频文件,显示错误提示
- alert("Video files must not exceed 10MB");
- event.target.value = ""; // 清空input
- return;
- }
- isUploading.value = true;
- try {
- for (const file of files) {
- // 创建一个带有上传状态的文件对象
- const fileWithStatus = {
- name: file.name,
- size: file.size,
- sizeFormatted: formatFileSize(file.size),
- type: file.type,
- isUploading: true,
- };
- // 先添加到文件列表中
- attachedFiles.value.push(fileWithStatus);
- try {
- const url = await uploadFile(file);
- // 直接修改数组中的对象
- const index = attachedFiles.value.findIndex(
- (f) => f.name === file.name
- );
- if (index !== -1) {
- // 使用数组替换方式更新,确保响应式更新
- attachedFiles.value.splice(index, 1, {
- ...fileWithStatus,
- url,
- isUploading: false,
- });
- }
- } catch (error) {
- // 如果上传失败,从列表中移除该文件
- attachedFiles.value = attachedFiles.value.filter(
- (f) => f.name !== file.name
- );
- console.error("单个文件上传失败:", error);
- }
- }
- } catch (error) {
- console.error("文件上传失败:", error);
- } finally {
- isUploading.value = false;
- event.target.value = ""; // 清空input以允许重复上传相同文件
- }
- if (files.length > 0 && enableWebSearch.value) {
- enableWebSearch.value = false; // 如果有文件上传,自动关闭互联网搜索
- }
- };
- const formatFileSize = (bytes) => {
- if (!bytes || isNaN(bytes)) 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];
- };
- const getFileIcon = (file) => {
- // 如果文件已经有 URL 且是图片类型,返回 URL
- if (file.url && isImageFile(file)) {
- return file.url;
- }
- // 其他所有情况返回文档图标
- return wordIcon;
- };
- const isImageFile = (file) => {
- const imageTypes = ["jpg", "jpeg", "png", "gif", "webp"];
- const ext = file.name?.split(".")?.pop()?.toLowerCase() || "";
- return imageTypes.includes(ext);
- };
- const isVideoFile = (file) => {
- const videoTypes = ["mp4", "webm", "ogg"];
- const ext = file.name?.split(".")?.pop()?.toLowerCase() || "";
- return videoTypes.includes(ext);
- };
- const getFileType = (file) => {
- const ext = file.name.split(".").pop().toLowerCase();
- const imageTypes = ["jpg", "jpeg", "png", "gif", "webp"];
- const videoTypes = ["mp4", "webm", "ogg"];
- if (imageTypes.includes(ext)) return "image";
- if (videoTypes.includes(ext)) return "video";
- return "document";
- };
- // 添加滚动到底部的方法
- const scrollToBottom = () => {
- if (messageList.value) {
- nextTick(() => {
- messageList.value.scrollTop = messageList.value.scrollHeight;
- });
- }
- };
- // 添加 session_id 的响应式引用
- const session_id = ref("");
- // 修改发送消息方法
- const sendMessage = async () => {
- if (
- (!inputContent.value.trim() && attachedFiles.value.length === 0) ||
- isTyping.value
- )
- return;
- // 构建消息内容,包含文件信息
- let messageContent = inputContent.value.trim();
- // 收集文件URLs
- const imageUrls = [];
- const videoUrls = [];
- const documentUrls = [];
- attachedFiles.value.forEach((file) => {
- const fileType = getFileType(file);
- if (fileType === "image") {
- imageUrls.push(file.url);
- } else if (fileType === "video") {
- videoUrls.push(file.url);
- } else {
- documentUrls.push(file.url);
- }
- });
- // 添加用户消息
- const userMessage = {
- id: Date.now(),
- role: "user",
- content: messageContent,
- displayContent: messageContent,
- files: attachedFiles.value.map((file) => ({
- name: file.name,
- size: file.sizeFormatted, // 使用格式化后的大小
- type: file.type,
- url: file.url,
- })),
- };
- messages.value.push(userMessage);
- scrollToBottom();
- // 异步执行搜索,但不等待结果
- sendMessageS(messageContent).catch((error) => {
- console.error("Web search failed:", error);
- });
- // 保存当前输入内容并清空输入框和附件
- const currentInput = inputContent.value;
- showAttachments.value = false;
- inputContent.value = "";
- const currentFiles = [...attachedFiles.value];
- attachedFiles.value = [];
- // 添加临时AI消息
- const tempAiMessage = {
- id: Date.now() + 1,
- role: "assistant",
- content: "Thinking...",
- displayContent: "Thinking...",
- isLoading: true,
- };
- messages.value.push(tempAiMessage);
- scrollToBottom();
- try {
- // 根据开关状态选择不同的 API 端点
- const apiEndpoint = enableWebSearch.value
- ? `${import.meta.env.VITE_BASE_AI_API}/api/chat/web-search-llm/`
- : `${import.meta.env.VITE_BASE_AI_API}/chatbot/multimodal`;
- const response = await axios.post(apiEndpoint, {
- message: currentInput,
- chat_config_id: "2",
- user_id: "13365429324",
- session_id: session_id.value,
- source: "pc",
- image_urls: imageUrls,
- video_urls: videoUrls,
- documents: documentUrls,
- });
- // 更新 session_id
- if (response.data.data.session_id) {
- session_id.value = response.data.data.session_id;
- }
- // 移除时消息并添加AI响应
- messages.value = messages.value.filter(
- (msg) => msg.id !== tempAiMessage.id
- );
- const aiMessage = reactive({
- id: Date.now() + 2,
- role: "assistant",
- content: response.data.data.answer,
- displayContent: "",
- audioData: response.data.data.audio_info?.audio || null, // 保存音频数据
- });
- enableWebSearch.value=false
- messages.value.push(aiMessage);
- // 如果有音频数据,自动播放
- if (aiMessage.audioData) {
- playAudioMessage(aiMessage.audioData);
- }
- await typeMessage(aiMessage);
- } catch (error) {
- console.error("发送消息失败:", error);
- messages.value = messages.value.filter(
- (msg) => msg.id !== tempAiMessage.id
- );
- messages.value.push({
- id: Date.now() + 2,
- role: "assistant",
- content: "Sorry, there was an error processing your request.",
- displayContent: "Sorry, there was an error processing your request.",
- });
- } finally {
- isTyping.value = false;
- await nextTick();
- scrollToBottom();
- }
- };
- // 修改 sendMessageS 方法
- const sendMessageS = async (query) => {
- isSearchLoading.value = true;
- try {
- const response = await axios.post(
- `${import.meta.env.VITE_BASE_AI_API}/api/web-search-results/`,
- {
- query: query,
- num_results: 20,
- page: 1,
- page_size: 20,
- engine: "bing",
- }
- );
- if (response.data && response.data.code === 200) {
- searchResults.value = response.data.data.results || [];
- searchTotal.value = response.data.data.total_results || 0;
- }
- } catch (error) {
- console.error("Web search failed:", error);
- searchResults.value = [];
- searchTotal.value = 0;
- } finally {
- isSearchLoading.value = false;
- }
- };
- // 修改打字效果方法
- const typeMessage = async (message) => {
- if (!message || !message.content) {
- console.error("Invalid message or content is missing");
- return;
- }
- return new Promise((resolve) => {
- const delay = 30; // 打字延迟时间
- let i = 0;
- message.displayContent = ""; // 确保开始时是空的
- const typeChar = () => {
- if (i < message.content.length) {
- message.displayContent = message.content.substring(0, i + 1);
- i++;
- nextTick(() => {
- scrollToBottom();
- setTimeout(typeChar, delay);
- });
- } else {
- resolve();
- }
- };
- typeChar();
- });
- };
- // 模拟API请求
- const mockApiRequest = async (message) => {
- await new Promise((resolve) => setTimeout(resolve, 1000));
- return `This is a response to: ${message}`;
- };
- // 会话管理
- const addNewConversation = () => {
- conversationCounter.value++;
- const newConversation = {
- id: Date.now(),
- title: `New Conversation ${conversationCounter.value}`,
- messages: [],
- };
- conversations.value.push(newConversation);
- session_id.value = ""; // 重置 session_id
- switchConversation(newConversation.id);
- };
- const switchConversation = (conversationId) => {
- currentConversationId.value = conversationId;
- const conversation = conversations.value.find((c) => c.id === conversationId);
- messages.value = conversation ? conversation.messages : [];
- session_id.value = ""; // 重置 session_id
- };
- const handleFileDrop = async (event) => {
- if (isUploading.value) return;
- const files = Array.from(event.dataTransfer.files);
- if (files.length === 0) return;
- // 添加文件大小检查
- const MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10MB in bytes
- const invalidFiles = files.filter((file) => {
- if (isVideoFile(file) && file.size > MAX_VIDEO_SIZE) {
- return true;
- }
- return false;
- });
- isUploading.value = true;
- try {
- for (const file of files) {
- const fileWithStatus = {
- name: file.name,
- size: file.size,
- sizeFormatted: formatFileSize(file.size),
- type: file.type,
- isUploading: true,
- };
- attachedFiles.value.push(fileWithStatus);
- try {
- const url = await uploadFile(file);
- // 直接修改数组中的对象
- const index = attachedFiles.value.findIndex(
- (f) => f.name === file.name
- );
- if (index !== -1) {
- // 使用数组替换方式更新,确保响应式更新
- attachedFiles.value.splice(index, 1, {
- ...fileWithStatus,
- url,
- isUploading: false,
- });
- }
- } catch (error) {
- attachedFiles.value = attachedFiles.value.filter(
- (f) => f.name !== file.name
- );
- console.error("单个文件上传失败:", error);
- }
- }
- } catch (error) {
- console.error("文件上传失败:", error);
- } finally {
- isUploading.value = false;
- }
- if (files.length > 0 && enableWebSearch.value) {
- enableWebSearch.value = false; // 如果有文件上传,自动关闭互联网搜索
- }
- };
- // 添加音频点击处理方法
- const handleAudioClick = (message) => {
- if (currentPlayingId.value === message.id) {
- stopAudio();
- } else {
- playAudioMessage(message.audioData, message.id);
- }
- };
- // 修改音频播放方法
- const playAudioMessage = async (audioBase64, messageId) => {
- try {
- // 停止当前播放的音频
- stopAudio();
- // 创建新的音频元素
- const audio = new Audio(audioBase64);
- audioPlayer.value = audio;
- currentPlayingId.value = messageId;
- // 添加事件监听
- audio.addEventListener("ended", () => {
- stopAudio();
- });
- audio.addEventListener("error", (e) => {
- console.error("音频播放错误:", e);
- stopAudio();
- });
- // 播放音频
- await audio.play();
- isPlaying.value = true;
- } catch (error) {
- console.error("音频播放失败:", error);
- stopAudio();
- }
- };
- // 修改停止播放方法
- const stopAudio = () => {
- if (audioPlayer.value) {
- audioPlayer.value.pause();
- audioPlayer.value = null;
- }
- isPlaying.value = false;
- currentPlayingId.value = null;
- };
- // 在组件卸载时清理
- onUnmounted(() => {
- stopAudio();
- });
- // 添加 renderMarkdown 方法
- const renderMarkdown = (content) => {
- if (!content) return "";
- return md.render(content);
- };
- </script>
- <style scoped>
- .layout {
- display: flex;
- width: 100%;
- height: 100vh;
- overflow: hidden;
- position: relative;
- }
- .menu {
- background: #f7f7f8;
- width: 260px;
- height: 100%;
- display: flex;
- flex-direction: column;
- border-right: 1px solid rgba(0, 0, 0, 0.06);
- position: absolute;
- left: 0;
- transform: translateX(0);
- transition: transform 0.3s ease;
- z-index: 2;
- }
- .menu.collapsed {
- transform: translateX(-260px);
- width: 260px;
- }
- .conversations {
- padding: 0 12px;
- flex: 1;
- overflow-y: auto;
- }
- .chat {
- flex: 1;
- padding: 24px;
- display: flex;
- flex-direction: column;
- gap: 24px;
- overflow-y: auto;
- overflow-x: hidden;
- margin-left: 0;
- margin-right: 0;
- transition: margin 0.3s ease;
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- }
- .menu:not(.collapsed) + .chat {
- left: 260px;
- }
- .chat {
- right: 0;
- transition: left 0.3s ease, right 0.3s ease;
- }
- .layout:has(.right_menu:not(.collapsed)) .chat {
- right: 600px;
- }
- .right_menu:not(.collapsed) ~ .chat {
- right: 600px;
- }
- .messages {
- flex: 1;
- overflow-y: auto;
- padding-right: 16px;
- }
- .sender {
- box-shadow: none;
- border: 1px solid #e5e6eb;
- border-radius: 8px;
- padding: 8px;
- }
- .logo {
- height: 64px;
- display: flex;
- align-items: center;
- padding: 0 20px;
- }
- .logo-img {
- width: 24px;
- height: 24px;
- }
- .logo span {
- margin-left: 12px;
- font-size: 16px;
- font-weight: 600;
- color: rgba(0, 0, 0, 0.88);
- }
- .new-conversation-btn {
- margin: 0 12px 12px;
- height: 40px;
- background: rgba(235, 245, 255, 0.7);
- border: none;
- border-radius: 8px;
- color: #1677ff;
- font-size: 14px;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- cursor: pointer;
- transition: background-color 0.2s;
- }
- .new-conversation-btn:hover {
- background: rgba(235, 245, 255, 0.9);
- }
- .conversation-list {
- flex: 1;
- padding: 0 8px;
- overflow-y: auto;
- }
- .conversation-item {
- padding: 12px;
- margin: 4px 0;
- border-radius: 8px;
- color: rgba(0, 0, 0, 0.88);
- font-size: 14px;
- cursor: pointer;
- transition: background-color 0.2s;
- }
- .conversation-item:hover {
- background: rgba(0, 0, 0, 0.04);
- }
- .conversation-item.active {
- background: #fff;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
- }
- .conversation-list::-webkit-scrollbar {
- width: 4px;
- }
- .conversation-list::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);
- border-radius: 2px;
- }
- .conversation-list::-webkit-scrollbar-track {
- background: transparent;
- }
- :deep(.welcome-message) {
- background: #fff;
- border-radius: 8px;
- padding: 16px;
- display: flex;
- align-items: flex-start;
- gap: 16px;
- }
- :deep(.welcome-message .title) {
- font-size: 16px;
- font-weight: 500;
- color: rgba(0, 0, 0, 0.88);
- margin-bottom: 4px;
- }
- :deep(.welcome-message .description) {
- color: rgba(0, 0, 0, 0.45);
- font-size: 14px;
- }
- :deep(.prompt-card) {
- background: #f7f8fa;
- border-radius: 8px;
- padding: 16px;
- margin-bottom: 12px;
- }
- :deep(.prompt-card-title) {
- font-size: 16px;
- font-weight: 500;
- margin-bottom: 8px;
- display: flex;
- align-items: center;
- gap: 8px;
- }
- :deep(.prompt-option) {
- background: #f5f5f5;
- padding: 8px 12px;
- border-radius: 4px;
- margin-top: 8px;
- cursor: pointer;
- transition: background 0.3s;
- }
- :deep(.prompt-option:hover) {
- background: #e8e8e8;
- }
- .input-container {
- width: 50%;
- margin: auto auto 0;
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .tabs-wrapper {
- display: flex;
- gap: 8px;
- margin-bottom: 4px;
- }
- .tab-item {
- display: flex;
- align-items: center;
- gap: 6px;
- padding: 4px 12px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- color: rgba(0, 0, 0, 0.88);
- background: transparent;
- transition: background-color 0.2s;
- }
- .tab-item:hover {
- background: rgba(0, 0, 0, 0.04);
- }
- .tab-item.active {
- background: #f5f5f5;
- }
- .tab-item .anticon {
- font-size: 14px;
- }
- .input-wrapper {
- display: flex;
- align-items: center;
- padding: 8px 12px;
- border: 1px solid #e5e6eb;
- border-radius: 8px;
- background: white;
- }
- .message-input {
- flex: 1;
- border: none;
- outline: none;
- padding: 8px;
- font-size: 14px;
- background: transparent;
- }
- .message-input::placeholder {
- color: rgba(0, 0, 0, 0.45);
- }
- .attachment-btn,
- .send-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border: none;
- background: transparent;
- cursor: pointer;
- color: rgba(0, 0, 0, 0.45);
- border-radius: 4px;
- }
- .attachment-btn:hover,
- .send-btn:hover {
- background: rgba(0, 0, 0, 0.04);
- color: rgba(0, 0, 0, 0.88);
- }
- .attachments-panel {
- background: white;
- border-radius: 12px;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
- overflow: hidden;
- }
- .panel-content {
- background: white;
- }
- .attachments-header {
- padding: 12px 16px;
- border-bottom: 1px solid #f0f0f0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .attachments-header span {
- font-size: 14px;
- font-weight: 500;
- color: rgba(0, 0, 0, 0.88);
- }
- .close-btn {
- border: none;
- background: transparent;
- cursor: pointer;
- padding: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: rgba(0, 0, 0, 0.45);
- }
- .upload-area {
- padding: 24px;
- border-radius: 8px;
- text-align: center;
- cursor: pointer;
- transition: all 0.3s;
- }
- .upload-area.has-files {
- padding: 12px;
- background: #fff;
- }
- .upload-icon {
- width: 32px;
- height: 32px;
- margin-bottom: 8px;
- }
- .upload-text {
- font-size: 14px;
- color: rgba(0, 0, 0, 0.88);
- margin-bottom: 4px;
- }
- .upload-hint {
- font-size: 12px;
- color: rgba(0, 0, 0, 0.45);
- }
- .file-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- padding: 10px;
- }
- .file-item {
- position: relative;
- width: 260px;
- padding: 8px 12px;
- margin: 4px 0;
- background: #f5f5f5;
- border-radius: 8px;
- }
- .file-item.is-image {
- width: 68px;
- padding: 0;
- margin: 4px 0;
- }
- .file-wrapper {
- position: relative;
- width: 100%;
- height: 100%;
- }
- .file-icon {
- width: 68px;
- height: 68px;
- object-fit: cover;
- border-radius: 4px;
- }
- .file-wrapper video.file-icon {
- background: #000;
- }
- .remove-btn {
- position: absolute;
- top: -8px;
- right: -8px;
- width: 16px;
- height: 16px;
- padding: 0;
- border: none;
- background: #545454;
- border-radius: 50%;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 14px;
- line-height: 1;
- color: #fff;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- }
- .remove-btn:hover {
- opacity: 0.8;
- }
- .file-info {
- margin-top: 8px;
- }
- .file-name {
- display: block;
- max-width: 150px;
- color: rgba(0, 0, 0, 0.88);
- font-size: 14px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .file-info .file-name {
- max-width: 150px;
- }
- .file-name:hover {
- cursor: pointer;
- }
- .file-size {
- display: block;
- color: rgba(0, 0, 0, 0.45);
- font-size: 12px;
- margin-top: 4px;
- }
- .add-more-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 68px;
- height: 68px;
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- background: transparent;
- cursor: pointer;
- transition: all 0.3s;
- margin: 4px 0;
- }
- .add-more-btn:hover {
- border-color: #1677ff;
- background: #f0f7ff;
- }
- /* 欢迎区域样式 */
- .welcome-section {
- max-width: 800px;
- margin: 0 auto;
- width: 50%;
- }
- .welcome-header {
- display: flex;
- align-items: flex-start;
- gap: 16px;
- margin-bottom: 24px;
- }
- .avatar {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- }
- .welcome-info {
- flex: 1;
- }
- .welcome-info h2 {
- font-size: 20px;
- font-weight: 500;
- color: rgba(0, 0, 0, 0.88);
- margin: 0 0 4px 0;
- }
- .welcome-info p {
- font-size: 14px;
- color: rgba(0, 0, 0, 0.45);
- margin: 0;
- }
- .action-buttons {
- display: flex;
- gap: 8px;
- }
- .icon-btn {
- padding: 8px;
- border: none;
- background: transparent;
- cursor: pointer;
- color: rgba(0, 0, 0, 0.45);
- }
- /* 建议区域样式 */
- .suggestion-section {
- margin-top: 32px;
- }
- .suggestion-section h3 {
- font-size: 14px;
- color: rgba(0, 0, 0, 0.45);
- margin-bottom: 16px;
- }
- .cards-container {
- display: flex;
- gap: 16px;
- }
- .suggestion-card {
- flex: 1;
- border-radius: 8px;
- padding: 16px;
- background: #ffffff; /* 替换 var(--ant-color-bg-container) */
- border-radius: 8px; /* 替换 var(--ant-border-radius-lg) */
- transition: border 0.3s, background 0.3s; /* 替换 var(--ant-motion-duration-slow) */
- border: 1px solid #f0f0f0; /* 替换合的 border 属性 */
- }
- :root {
- --ant-color-bg-container: #ffffff;
- --ant-border-radius-lg: 8px;
- --ant-motion-duration-slow: 0.3s;
- --ant-line-width: 1px;
- --ant-line-type: solid;
- --ant-color-border-secondary: #f0f0f0;
- }
- .card-header {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 8px;
- }
- .card-header h4 {
- font-size: 16px;
- font-weight: 500;
- color: rgba(0, 0, 0, 0.88);
- margin: 0;
- }
- .icon.red {
- color: #ff4d4f;
- }
- .icon.blue {
- color: #1890ff;
- }
- .card-subtitle {
- font-size: 14px;
- color: rgba(0, 0, 0, 0.45);
- margin-bottom: 16px;
- }
- .suggestion-items {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .suggestion-item {
- padding: 8px 12px;
- background: #f5f5f5;
- border-radius: 4px;
- font-size: 12px;
- color: rgba(0, 0, 0, 0.88);
- display: flex;
- align-items: center;
- gap: 8px;
- cursor: pointer;
- transition: opacity 0.3s ease;
- }
- .suggestion-item:hover {
- background: #e8e8e8;
- }
- .tab-icon {
- width: 16px;
- }
- .attached-files {
- padding: 16px;
- }
- .file-item {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 8px;
- border-radius: 4px;
- }
- .file-item:hover {
- background: #f5f5f5;
- }
- .remove-btn {
- margin-left: auto;
- padding: 4px;
- border: none;
- background: transparent;
- cursor: pointer;
- color: rgba(0, 0, 0, 0.45);
- }
- .remove-btn:hover {
- color: rgba(0, 0, 0, 0.88);
- }
- .send-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .upload-area {
- /* border: 2px dashed #d9d9d9; */
- border-radius: 8px;
- padding: 20px;
- text-align: center;
- cursor: pointer;
- transition: border-color 0.3s;
- }
- /* .upload-area:hover,
- .upload-area.drag-over {
- border-color: #1677ff;
- } */
- .file-item {
- display: flex;
- align-items: center;
- width: 236px;
- height: 68px;
- padding: 0;
- margin: 4px 0;
- background: #f5f5f5;
- border-radius: 4px;
- }
- .file-item .remove-btn {
- margin-left: auto;
- padding: 4px 8px;
- background: transparent;
- border: none;
- cursor: pointer;
- }
- /* 聊天内容显示框 */
- .message-list {
- width: 60%;
- overflow-y: auto;
- overflow-x: hidden;
- padding: 20px;
- display: flex;
- flex-direction: column;
- gap: 16px;
- margin: 0 auto;
- }
- .message-item {
- display: flex;
- margin-bottom: 12px;
- }
- /* 用户消息靠右显示 */
- .user {
- display: flex;
- justify-content: flex-end;
- }
- .message-bubble {
- max-width: 80%;
- padding: 12px 16px;
- border-radius: 12px;
- background: #fff;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- }
- /* 用户消息的气泡样式 */
- .user .message-bubble {
- background: #fff;
- color: rgba(0, 0, 0, 0.88);
- }
- /* AI消息的气泡样式 */
- .assistant .message-bubble {
- background: #f7f7f8;
- color: rgba(0, 0, 0, 0.88);
- }
- .message-content {
- font-size: 14px;
- line-height: 1.5;
- white-space: pre-wrap;
- word-break: break-word;
- }
- .right_menu {
- width: 600px;
- height: 100%;
- background: #f7f7f8;
- border-left: 1px solid rgba(0, 0, 0, 0.06);
- position: absolute; /* 改为绝对定位 */
- right: 0;
- transform: translateX(0);
- transition: transform 0.3s ease;
- z-index: 2;
- }
- .right_menu.collapsed {
- transform: translateX(100%); /* 使用 transform 代替 width */
- width: 600px; /* 保持固定宽度 */
- }
- /* 当右侧菜单展开时,给聊天区域添加右边距 */
- .right_menu:not(.collapsed) + .chat {
- margin-right: 600px;
- }
- .collapse-left-button{
- position: absolute;
- left: 260px;
- top: 50%;
- transform: translateY(-50%);
- width: 20px;
- height: 40px;
- background: #fff;
- border: 1px solid rgba(0, 0, 0, 0.06);
- border-right: none;
- border-radius: 4px 0 0 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- z-index: 2;
- transition: left 0.3s ease;
- }
- .collapse-left-button.menu-collapsed {
- left: 260px;
- /* border-right: 1px solid rgba(0, 0, 0, 0.06);
- border-left: none;
- border-radius: 0 4px 4px 0; */
- }
- .collapse-button {
- position: absolute;
- left: -20px;
- top: 50%;
- transform: translateY(-50%);
- width: 20px;
- height: 40px;
- background: #fff;
- border: 1px solid rgba(0, 0, 0, 0.06);
- border-right: none;
- border-radius: 4px 0 0 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- z-index: 1;
- }
- .collapse-button:hover {
- background: #f5f5f5;
- }
- .collapse-icon {
- width: 12px;
- height: 12px;
- }
- .right-menu-content {
- height: 100%;
- overflow-y: auto;
- }
- .right_menu.collapsed .right-menu-content {
- display: none;
- }
- /* 添加打字效果的光标样式 */
- /* .assistant .message-content {
- position: relative;
- }
- .assistant .message-content::after {
- content: '|';
- position: absolute;
- margin-left: 2px;
- animation: cursor-blink 1s infinite;
- }
- @keyframes cursor-blink {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0; }
- }*/
- .file-message {
- background: #f7f7f8;
- border-radius: 8px;
- padding: 12px;
- margin-bottom: 8px;
- }
- .file-info-display {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 8px;
- }
- .file-type-icon {
- width: 24px;
- height: 24px;
- object-fit: contain;
- }
- .file-details {
- display: flex;
- flex-direction: column;
- }
- /* .file-name {
- font-size: 14px;
- color: rgba(0, 0, 0, 0.88);
- margin-bottom: 4px;
- } */
- .file-size {
- font-size: 12px;
- color: rgba(0, 0, 0, 0.45);
- }
- .file-action {
- font-size: 12px;
- color: rgba(0, 0, 0, 0.45);
- padding-left: 36px;
- }
- /* 修改用户消息气泡样式,确保文件信息正确显示 */
- .user .message-bubble {
- background: #fff;
- color: rgba(0, 0, 0, 0.88);
- }
- .user .file-message {
- background: #f7f7f8;
- }
- /* 图片预览样式 */
- .image-preview {
- position: relative;
- width: 200px;
- border-radius: 8px;
- overflow: hidden;
- background: #fff;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- }
- .preview-image {
- width: 100%;
- height: auto;
- display: block;
- }
- .image-info {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- padding: 8px;
- background: rgba(255, 255, 255, 0.9);
- font-size: 12px;
- color: rgba(0, 0, 0, 0.45);
- text-align: center;
- border-top: 1px solid rgba(0, 0, 0, 0.06);
- }
- /* 文档显示样式保持不变 */
- .file-info-display {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 8px;
- }
- .file-type-icon {
- width: 24px;
- height: 24px;
- object-fit: contain;
- }
- .file-details {
- display: flex;
- flex-direction: column;
- }
- /* .file-name {
- font-size: 14px;
- color: rgba(0, 0, 0, 0.88);
- margin-bottom: 4px;
- } */
- .file-size {
- font-size: 12px;
- color: rgba(0, 0, 0, 0.45);
- }
- .file-action {
- font-size: 12px;
- color: rgba(0, 0, 0, 0.45);
- padding-left: 36px;
- }
- /* 添加音频控制相关样式 */
- .audio-controls {
- margin-top: 8px;
- }
- .audio-btn {
- background: transparent;
- border: none;
- padding: 4px 8px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- transition: background-color 0.2s;
- min-width: 32px;
- height: 32px;
- }
- .audio-btn:hover {
- background: rgba(0, 0, 0, 0.04);
- }
- .audio-icon {
- width: 20px;
- height: 20px;
- }
- /* 音频波形动画样式 */
- .wave-animation {
- display: flex;
- align-items: center;
- height: 20px;
- gap: 2px;
- }
- .wave-bar {
- display: inline-block;
- width: 3px;
- height: 100%;
- background: #1677ff;
- border-radius: 1px;
- animation: wave 1s ease-in-out infinite;
- }
- .wave-bar:nth-child(1) {
- animation-delay: 0s;
- }
- .wave-bar:nth-child(2) {
- animation-delay: 0.2s;
- }
- .wave-bar:nth-child(3) {
- animation-delay: 0.4s;
- }
- .wave-bar:nth-child(4) {
- animation-delay: 0.6s;
- }
- @keyframes wave {
- 0%,
- 100% {
- height: 6px;
- }
- 50% {
- height: 18px;
- }
- }
- .audio-btn {
- background: transparent;
- border: none;
- padding: 4px 8px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- transition: background-color 0.2s;
- min-width: 32px;
- height: 32px;
- }
- .audio-btn:hover {
- background: rgba(0, 0, 0, 0.04);
- }
- .audio-icon {
- width: 20px;
- height: 20px;
- }
- .message-text {
- margin: 0;
- white-space: normal; /* 确保文本会换行 */
- word-wrap: break-word; /* 长单词或 URL 会被断开以适应容器宽度 */
- overflow: auto;
- font-size: 12px;
- line-height: 20px;
- }
- .message-text ::v-deep p {
- font-size: 12px !important;
- margin: 5px 0 10px;
- padding: 0;
- font-size: inherit;
- line-height: 22px;
- font-weight: inherit;
- }
- .message-text ::v-deep ol {
- padding-bottom: 10px;
- }
- .message-text ::v-deep .source-section h3 {
- margin: 20px 0 3px;
- color: #333;
- font-size: 16px;
- }
- .message-text ::v-deep ol li {
- padding-top: 3px;
- }
- .message-text ::v-deep ol li a {
- font-size: 14px;
- color: #1296db;
- }
- .video-preview {
- position: relative;
- width: 300px;
- border-radius: 8px;
- overflow: hidden;
- background: #fff;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- margin-bottom: 8px;
- }
- .preview-video {
- width: 100%;
- height: auto;
- display: block;
- max-height: 300px;
- }
- /* 添加 loading 相关样式 */
- .upload-loading {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 12px;
- }
- .loading-spinner {
- width: 24px;
- height: 24px;
- border: 2px solid #f3f3f3;
- border-top: 2px solid #1677ff;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- .file-loading {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(255, 255, 255, 0.8);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1;
- }
- @keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
- }
- /* 上传区域在上传时的样式 */
- .upload-area {
- position: relative;
- cursor: pointer; /* 始终保持指针样式 */
- }
- .upload-area.uploading {
- opacity: 0.7;
- }
- /* 添加配置容器的样式 */
- .config-container {
- margin-top: auto;
- border-top: 1px solid rgba(0, 0, 0, 0.06);
- }
- .web-search-toggle {
- display: flex;
- align-items: center;
- margin-left: auto;
- padding: 0 12px;
- }
- .toggle-input {
- position: relative;
- width: 36px;
- height: 20px;
- margin-right: 8px;
- appearance: none;
- background: #ccc;
- border-radius: 10px;
- cursor: pointer;
- transition: background 0.3s;
- }
- .toggle-input:checked {
- background: #1677ff;
- }
- .toggle-input::before {
- content: "";
- position: absolute;
- top: 2px;
- left: 2px;
- width: 16px;
- height: 16px;
- background: white;
- border-radius: 50%;
- transition: transform 0.3s;
- }
- .toggle-input:checked::before {
- transform: translateX(16px);
- }
- .toggle-label {
- font-size: 14px;
- color: rgba(0, 0, 0, 0.88);
- cursor: pointer;
- }
- .toggle-input:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .toggle-label.disabled {
- color: rgba(0, 0, 0, 0.45);
- cursor: not-allowed;
- }
- .disabled-hint {
- font-size: 12px;
- color: rgba(0, 0, 0, 0.45);
- margin-left: 4px;
- }
- /* 添加菜单收缩相关样式 */
- .menu {
- /* 现有样式保持不变 */
- transition: width 0.3s ease;
- position: relative;
- }
- .menu.collapsed {
- width: 0;
- padding: 0;
- }
- /* 添加收缩按钮样式 */
- .collapse-button {
- position: absolute;
- right: -20px;
- top: 50%;
- transform: translateY(-50%);
- width: 20px;
- height: 40px;
- background: #fff;
- border: 1px solid rgba(0, 0, 0, 0.06);
- border-left: none;
- border-radius: 0 4px 4px 0;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- z-index: 1;
- }
- .collapse-button:hover {
- background: #f5f5f5;
- }
- .collapse-icon {
- width: 12px;
- height: 12px;
- }
- /* 添加响应式布局样式 */
- @media screen and (max-width: 768px) {
- .layout {
- /* 移动端布局调整 */
- flex-direction: column;
- }
- .menu {
- /* 移动端菜单调整 */
- position: fixed;
- width: 100%;
- height: 100%;
- z-index: 1000;
- transform: translateX(-100%);
- }
- .menu.collapsed {
- transform: translateX(-100%);
- }
- .collapse-left-button {
- /* 移动端收缩按钮调整 */
- display: none;
- }
- .chat {
- /* 移动端聊天区域调整 */
- width: 100%;
- margin: 0;
- padding: 16px;
- left: 0 !important;
- right: 0 !important;
- }
- .message-list {
- /* 移动端消息列表调整 */
- width: 100%;
- padding: 10px;
- }
- .input-container {
- /* 移动端输入区域调整 */
- width: 100%;
- padding: 10px;
- }
- .cards-container {
- /* 移动端卡片容器调整 */
- flex-direction: column;
- }
- .suggestion-card {
- /* 移动端建议卡片调整 */
- width: 100%;
- }
- .welcome-section {
- /* 移动端欢迎区域调整 */
- width: 100%;
- padding: 16px;
- }
- .right_menu {
- /* 移动端右侧菜单调整 */
- position: fixed;
- width: 100%;
- height: 100%;
- z-index: 1000;
- }
- .right_menu.collapsed {
- transform: translateX(100%);
- }
- /* 移动端文件预览调整 */
- .image-preview,
- .video-preview {
- width: 100%;
- max-width: 300px;
- }
- /* 移动端文件列表调整 */
- .file-list {
- justify-content: center;
- }
- .file-item {
- width: 100%;
- max-width: 300px;
- }
- /* 移动端标签栏调整 */
- .tabs-wrapper {
- flex-wrap: wrap;
- gap: 4px;
- }
- .web-search-toggle {
- width: 100%;
- justify-content: flex-start;
- margin-top: 8px;
- }
- /* 移动端消息气泡调整 */
- .message-bubble {
- max-width: 90%;
- }
- }
- /* 添加移动端手势支持 */
- @media (hover: none) and (pointer: coarse) {
- .menu {
- /* 支持触摸滑动 */
- touch-action: pan-y;
- }
- .message-list {
- /* 支持触摸滚动 */
- -webkit-overflow-scrolling: touch;
- }
- }
- /* 优化移动端字体大小 */
- @media screen and (max-width: 480px) {
- .message-content {
- font-size: 14px;
- }
- .suggestion-item {
- font-size: 13px;
- }
- .file-name {
- font-size: 13px;
- }
- }
- /* 修改现有的响应式样式 */
- @media screen and (max-width: 768px) {
- .layout {
- overflow-x: hidden;
- }
- .chat {
- /* 调整聊天区域以保持一致的宽度 */
- padding: 24px 16px;
- margin: 0;
- width: 100%;
- }
- .message-list {
- /* 保持消息列表的宽度一致 */
- width: 100%;
- max-width: 800px;
- margin: 0 auto;
- padding: 0 10px;
- }
- .input-container {
- /* 保持输入区域的宽度一致 */
- width: 100%;
- max-width: 800px;
- margin: 0 auto;
- padding: 0 10px;
- }
- .welcome-section {
- /* 保持欢迎区域的宽度一致 */
- width: 100%;
- max-width: 800px;
- margin: 0 auto;
- padding: 0 16px;
- }
- .cards-container {
- /* 保持卡片布局 */
- flex-direction: row;
- flex-wrap: wrap;
- gap: 16px;
- }
- .suggestion-card {
- /* 调整卡片大小 */
- flex: 1 1 300px;
- min-width: 300px;
- }
- .message-bubble {
- /* 保持消息气泡的样式 */
- max-width: 80%;
- padding: 12px 16px;
- }
- /* 调整文件预览区域 */
- .image-preview,
- .video-preview {
- width: 200px;
- margin: 0 auto;
- }
- /* 调整文件列表布局 */
- .file-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: flex-start;
- }
- .file-item {
- width: calc(50% - 8px);
- min-width: 200px;
- }
- /* 调整标签栏布局 */
- .tabs-wrapper {
- flex-wrap: nowrap;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- padding-bottom: 8px;
- }
- /* 调整搜索开关位置 */
- .web-search-toggle {
- position: relative;
- width: auto;
- margin-left: auto;
- }
- /* 调整菜单样式 */
- .menu {
- position: fixed;
- left: 0;
- top: 0;
- bottom: 0;
- width: 260px;
- transform: translateX(-100%);
- transition: transform 0.3s ease;
- z-index: 1000;
- }
- .menu.collapsed {
- transform: translateX(-100%);
- }
- .menu:not(.collapsed) {
- transform: translateX(0);
- }
- /* 调整右侧菜单样式 */
- .right_menu {
- position: fixed;
- right: 0;
- top: 0;
- bottom: 0;
- width: 300px;
- transform: translateX(100%);
- transition: transform 0.3s ease;
- z-index: 1000;
- }
- .right_menu.collapsed {
- transform: translateX(100%);
- }
- .right_menu:not(.collapsed) {
- transform: translateX(0);
- }
- /* 添加遮罩层样式 */
- .menu-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- z-index: 999;
- display: none;
- }
- .menu:not(.collapsed) ~ .menu-overlay,
- .right_menu:not(.collapsed) ~ .menu-overlay {
- display: block;
- }
- }
- /* 添加更细致的断点控制 */
- @media screen and (max-width: 480px) {
- .message-content {
- font-size: 14px;
- line-height: 1.5;
- }
- .suggestion-item {
- font-size: 14px;
- padding: 10px;
- }
- .input-wrapper {
- padding: 8px;
- }
- .message-input {
- font-size: 14px;
- }
- }
- /* 确保滚动流畅 */
- * {
- -webkit-overflow-scrolling: touch;
- }
- /* 防止页面横向滚动 */
- body {
- overflow-x: hidden;
- width: 100%;
- }
- /* 优化触摸体验 */
- button,
- .suggestion-item,
- .conversation-item {
- touch-action: manipulation;
- }
- </style>
- <style lang="less" scoped>
- ::v-deep .message-content {
- /* 确保 scoped 样式能够渗透到动态生成的内容 */
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- margin-top: 16px;
- margin-bottom: 8px;
- font-weight: 600;
- }
- p {
- margin-bottom: 8px;
- }
- code {
- background: #f5f5f5;
- padding: 2px 4px;
- border-radius: 4px;
- font-family: monospace;
- }
- pre {
- background: #f5f5f5;
- padding: 16px;
- border-radius: 8px;
- overflow-x: auto;
- }
- ul,
- ol {
- padding-left: 24px;
- margin-bottom: 8px;
- }
- blockquote {
- border-left: 4px solid #ddd;
- padding-left: 16px;
- margin: 8px 0;
- color: rgba(0, 0, 0, 0.65);
- }
- a {
- color: #1677ff;
- text-decoration: none;
- }
- a:hover {
- text-decoration: underline;
- }
- table {
- border-collapse: collapse;
- width: 100%;
- margin-bottom: 16px;
- }
- th,
- td {
- border: 1px solid #ddd;
- padding: 8px;
- text-align: left;
- }
- th {
- background: #f5f5f5;
- }
- }
- .message-actions {
- display: flex;
- justify-content: flex-end;
- margin-top: 8px;
- padding-top: 8px;
- border-top: 1px solid rgba(0, 0, 0, 0.06);
- }
- .message-actions :deep(.export-button) {
- opacity: 0.6;
- transition: opacity 0.2s;
- }
- .message-actions :deep(.export-button:hover) {
- opacity: 1;
- }
- </style>
|