1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172 |
- <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"
- @input="handleInputChange"
- 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 || !enableWebSearch }" :style="{ width: rightMenuWidth + 'px' }">
- <!-- 添加拖动条 -->
- <div
- class="resize-handle"
- @mousedown="startResize"
- @touchstart="startResize"
- ></div>
-
- <!-- 修改折叠按钮的样式和行为 -->
- <div
- class="collapse-button"
- :class="{ 'menu-expanded': !isRightMenuCollapsed }"
- :style="!isRightMenuCollapsed ? { left: `-20px` } : {}"
- @click="toggleRightMenu"
- >
- <img
- :src="isRightMenuCollapsed ? leftIcon : rightIcon"
- alt="toggle"
- class="collapse-icon"
- />
- </div>
- <div class="right-menu-content">
- <!-- 添加 Tab 切换 -->
- <div class="right-menu-tabs">
- <div
- class="tab-item"
- :class="{ active: activeTab === 'search' }"
- @click="activeTab = 'search'"
- >
- <FileSearchOutlined />
- <span>搜索</span>
- </div>
- <div
- class="tab-item"
- :class="{ active: activeTab === 'preview' }"
- @click="activeTab = 'preview'"
- >
- <FileOutlined />
- <span>文档预览</span>
- </div>
- </div>
- <!-- 搜索面板 -->
- <div v-show="activeTab === 'search'" class="panel-container">
- <div class="search-header">
- <span>搜索结果</span>
- <div class="web-search-toggle">
- <input
- type="checkbox"
- id="searchToggle"
- v-model="enableWebSearch"
- class="toggle-input"
- @change="handleSearchToggle"
- />
- <label for="searchToggle" class="toggle-label">
- {{ enableWebSearch ? '搜索已开启' : '搜索已关闭' }}
- </label>
- </div>
- </div>
- <SearchResults
- v-if="enableWebSearch"
- :searchResults="searchResults"
- :loading="isSearchLoading"
- :total="searchTotal"
- :enableWebSearch="enableWebSearch"
- :message="currentSearchQuery"
- @update:enableWebSearch="handleSearchToggle"
- />
- </div>
- <!-- 文档预览面板 -->
- <div v-show="activeTab === 'preview'" class="panel-container">
- <div class="preview-header">
- <span>文档预览</span>
- </div>
- <DocumentPreview
- :documents="attachedFiles"
- @select="handleDocumentSelect"
- />
- </div>
- </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,
- FileSearchOutlined,
- } 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";
- import dayjs from 'dayjs';
- import DocumentPreview from "../components/DocumentPreview/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 (isRightMenuCollapsed.value) {
- // 收起菜单时的处理
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = '0';
- }
- } else {
- // 展开菜单时的处理
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- 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;
- // 如果启用了网络搜索,更新搜索查询
- if (enableWebSearch.value) {
- currentSearchQuery.value = inputContent.value;
- }
- // 构建消息内容,包含文件信息
- 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, // 保存音频数据
- });
- 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);
- };
- // 添加新的响应式变量
- const rightMenuWidth = ref(600); // 默认宽度
- const minWidth = 0; // 最小宽度
- const maxWidth = 800; // 最大宽度
- // 添加拖动相关方法
- const startResize = (e) => {
- e.preventDefault();
-
- const startX = e.type === 'mousedown' ? e.clientX : e.touches[0].clientX;
- const startWidth = rightMenuWidth.value;
-
- const handleMove = (e) => {
- if (isRightMenuCollapsed.value) return;
-
- const currentX = e.type === 'mousemove' ? e.clientX : e.touches[0].clientX;
- const diff = startX - currentX;
-
- let newWidth = Math.min(Math.max(startWidth + diff, minWidth), maxWidth);
- rightMenuWidth.value = newWidth;
-
- // 更新聊天区域的右边距和按钮位置
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${newWidth}px`;
- }
- };
-
- const handleEnd = () => {
- document.removeEventListener('mousemove', handleMove);
- document.removeEventListener('mouseup', handleEnd);
- document.removeEventListener('touchmove', handleMove);
- document.removeEventListener('touchend', handleEnd);
- };
-
- document.addEventListener('mousemove', handleMove);
- document.addEventListener('mouseup', handleEnd);
- document.addEventListener('touchmove', handleMove);
- document.addEventListener('touchend', handleEnd);
- };
- // 添加搜索开关状态
- const isSearchEnabled = ref(true);
- // 添加切换搜索方法
- const toggleSearch = () => {
- isSearchEnabled.value = !isSearchEnabled.value;
- };
- // 修改 toggleWebSearch 方法
- const toggleWebSearch = () => {
- enableWebSearch.value = !enableWebSearch.value;
- isSearchEnabled.value = enableWebSearch.value;
-
- // 当关闭搜索时,自动折叠右侧菜单
- if (!enableWebSearch.value) {
- isRightMenuCollapsed.value = true;
- currentSearchQuery.value = '';
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = '0';
- }
- } else if (inputContent.value.trim()) {
- isRightMenuCollapsed.value = false;
- currentSearchQuery.value = inputContent.value;
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- };
- // 添加新的响应式变量
- const currentSearchQuery = ref('');
- // 添加输入变化处理方法
- const handleInputChange = () => {
- if (enableWebSearch.value && inputContent.value.trim()) {
- currentSearchQuery.value = inputContent.value;
- }
- };
- // 添加处理搜索开关的方法
- const handleSearchToggle = (value) => {
- enableWebSearch.value = value;
- isSearchEnabled.value = value;
-
- // 当关闭搜索时,自动折叠右侧菜单
- if (!value) {
- isRightMenuCollapsed.value = true;
- // 更新聊天区域的样式
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = '0';
- }
- } else {
- isRightMenuCollapsed.value = false;
- // 展开时更新聊天区域的样式
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- };
- // 添加新的响应式变量
- const activeTab = ref('search'); // 默认显示搜索面板
- // 处理文档选择
- const handleDocumentSelect = (document) => {
- // 处理文档选择逻辑
- console.log('Selected document:', document);
- };
- </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 {
- /* 修改现有样式 */
- position: absolute;
- right: 0;
- height: 100%;
- background: #f7f7f8;
- border-left: 1px solid rgba(0, 0, 0, 0.06);
- transform: translateX(0);
- transition: transform 0.3s ease, width 0.3s ease;
- z-index: 2;
- }
- .right_menu.collapsed {
- transform: translateX(100%);
- width: 0;
- }
- /* 当右侧菜单展开时,给聊天区域添加右边距 */
- .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: 3;
- transition: left 0.3s ease;
- }
- /* 添加展开状态的样式 */
- .collapse-button.menu-expanded {
- border-right: 1px solid rgba(0, 0, 0, 0.06);
- border-left: none;
- border-radius: 0 4px 4px 0;
- }
- .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%;
- }
- .collapse-button {
- left: -20px !important;
- }
- .collapse-button.menu-expanded {
- left: auto !important;
- right: 0;
- }
- }
- /* 添加移动端手势支持 */
- @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;
- }
- /* 添加拖动条样式 */
- .resize-handle {
- position: absolute;
- left: -5px;
- top: 0;
- width: 10px;
- height: 100%;
- cursor: col-resize;
- background: transparent;
- z-index: 3;
- }
- .resize-handle:hover {
- background: rgba(0, 0, 0, 0.1);
- }
- /* 确保拖动时不会选中文本 */
- .resize-handle:active {
- user-select: none;
- }
- /* 移动端样式调整 */
- @media screen and (max-width: 768px) {
- .resize-handle {
- display: none;
- }
-
- .right_menu {
- width: 100% !important;
- }
- }
- .search-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px;
- border-bottom: 1px solid #f0f0f0;
- }
- .search-header span {
- font-size: 16px;
- font-weight: bold;
- }
- /* 添加开关样式 */
- .web-search-toggle {
- display: flex;
- align-items: center;
- }
- .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;
- }
- /* 添加 Tab 样式 */
- .right-menu-tabs {
- display: flex;
- border-bottom: 1px solid #f0f0f0;
- background: #fff;
- }
- .tab-item {
- flex: 1;
- padding: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- cursor: pointer;
- transition: all 0.3s;
- border-bottom: 2px solid transparent;
- color: rgba(0, 0, 0, 0.65);
- }
- .tab-item:hover {
- color: #1677ff;
- }
- .tab-item.active {
- color: #1677ff;
- border-bottom-color: #1677ff;
- background: #f6f9fe;
- }
- .panel-container {
- height: calc(100% - 45px); /* 减去 tab 的高度 */
- overflow-y: auto;
- }
- /* 修改右侧菜单内容样式 */
- .right-menu-content {
- height: 100%;
- display: flex;
- flex-direction: column;
- }
- /* 预览面板样式 */
- .preview-header {
- padding: 16px;
- border-bottom: 1px solid #f0f0f0;
- font-size: 16px;
- font-weight: bold;
- }
- </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>
|