123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718 |
- <template>
- <div class="layout" :class="{ 'dark-theme': isDarkTheme }">
- <!-- 添加密码修改弹窗组件 -->
- <ChangePasswordDialog
- v-model:visible="showPasswordDialog"
- />
-
- <div class="header">
- <div class="user-profile-bar">
- <div class="logo">
- <img
- src="../assets/logoc.png"
- alt="logo"
- class="logo-img"
- />
- <!-- <span>Raycos Tech</span> -->
- </div>
- <div class="user-info-bar">
- <!-- 添加主题切换按钮 -->
- <div class="theme-switch" @click="toggleTheme" :title="isDarkTheme ? '切换到浅色模式' : '切换到深色模式'">
- <transition name="theme-icon" mode="out-in">
- <img
- v-if="isDarkTheme"
- :key="isDarkTheme"
- src="../assets/sun2.png"
- alt="切换到浅色模式"
- class="theme-icon"
- />
- <img v-else class="theme-icon" src="../assets/night2.png" alt="切换到深色模式">
- </transition>
- </div>
- <div class="user-info" v-if="!isMobile" @click="toggleDropdown">
- <div class="user-name">商户名称:{{ userInfo.merchant_name || '未登录用户' }}</div>
- <div class="user-name">当前用户:{{ userInfo.username || '未登录用户' }}</div>
- <!-- <div class="user-role">{{ userInfo.role || '未设置角色' }}</div> -->
- </div>
- <div class="avatar" @click="toggleDropdown">
- <img src="../assets/avter.png" alt="用户头像" />
- </div>
- </div>
- <!-- Logo :draggable="false"-->
-
- <!-- 下拉菜单 -->
- <div class="dropdown-menu" v-show="showDropdown">
- <div class="dropdown-item" @click="handleModifyPassword">
- <i class="fas fa-key"></i>
- 修改密码
- </div>
- <div class="dropdown-item" @click="handleCreateInvitation">
- <i class="fas fa-user-plus"></i>
- 创建邀请
- </div>
- <div class="dropdown-item" @click="handleLogout">
- <i class="fas fa-sign-out-alt"></i>
- 退出登录
- </div>
- </div>
- </div>
- </div>
- <div
- class="collapse-left-button"
- :class="{ 'menu-collapsed': isMenuCollapsed }"
- @click="toggleMenu"
- >
- <img
- :src="isMenuCollapsed ? rightIcon : leftIcon"
- alt="toggle"
- class="collapse-icon"
- />
- </div>
- <div class="menu" :class="{ collapsed: isMenuCollapsed }">
- <!-- 添加收缩按钮 -->
-
-
- <!-- 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-if="currentThinkingSteps && currentThinkingSteps.length > 0" class="thinking-steps-stream">
- <div v-for="(step, index) in currentThinkingSteps"
- :key="index"
- class="thinking-step-stream"
- :class="[step.type, {'fade-in': true}]">
- {{ step.content }}
- <div v-if="index === currentThinkingSteps.length - 1" class="typing-indicator">
- <span></span>
- <span></span>
- <span></span>
- </div>
- </div>
- </div>
-
- <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">
- <!-- 添加thinking steps显示 -->
- <div v-if="message.thinkingSteps && message.thinkingSteps.length > 0" class="thinking-steps">
- <div v-for="(step, index) in message.thinkingSteps"
- :key="index"
- class="thinking-step"
- :class="step.type">
- {{ step.content }}
- </div>
- </div>
- <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'"
- 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="search-toggles" :class="{ 'compact': isCompact }">
- <div class="left_item" style="display: flex;">
- <div class="search-toggle-item">
- <div class="toggle-content">
- <GlobalOutlined />
- <span class="toggle-text" >互联网</span><!-- v-show="!isCompact" -->
- <div class="toggle-switch">
- <input
- type="checkbox"
- id="webSearchToggle"
- v-model="enableWebSearch"
- @change="handleWebSearchToggle"
- :disabled="attachedFiles.length > 0"
- class="toggle-input"
- />
- <label
- for="webSearchToggle"
- class="toggle-label"
- :class="{ disabled: attachedFiles.length > 0 }"
- >
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- </div>
-
- <div class="search-toggle-item">
- <div class="toggle-content">
- <DatabaseOutlined />
- <span class="toggle-text" >知识库</span><!-- v-show="!isCompact" -->
- <div class="toggle-switch">
- <input
- type="checkbox"
- id="knowledgeSearchToggle"
- v-model="enableKnowledgeSearch"
- @change="handleKnowledgeToggle"
- class="toggle-input"
- />
- <label
- for="knowledgeSearchToggle"
- class="toggle-label"
- >
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- </div>
- <div class="search-toggle-item">
- <div class="toggle-content">
- <DatabaseOutlined />
- <span class="toggle-text" >推理模式</span><!-- v-show="!isCompact" -->
- <div class="toggle-switch">
- <input
- type="checkbox"
- id="inferenceModelId"
- v-model="inferenceModel"
-
- class="toggle-input"
- />
- <label
- for="inferenceModelId"
- class="toggle-label"
- >
- <span class="toggle-slider"></span>
- </label>
- </div>
- </div>
- </div>
- </div>
-
- <div class="search-toggle-item">
- <div class="toggle-content">
- <!-- <SelectOutlined /> -->
- <!-- <span class="toggle-text">模型选择</span> -->
- <select
- class="model-select"
- :value="selectedModel"
- @change="handleModelChange($event.target.value)"
- :disabled="isLoadingModels"
- >
- <option v-if="isLoadingModels" value="">加载中...</option>
- <option v-else-if="models.length === 0" value="">无可用模型</option>
- <option
- v-for="model in models"
- :key="model.id"
- :value="model.id"
- >
- {{ model.name }}
- </option>
- </select>
- </div>
- </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..."
- /><!-- @input="handleInputChange" :disabled="(!inputContent && attachedFiles.length === 0)"-->
- <button
- class="send-btn"
- @click="sendMessage"
-
- >
- <template v-if="isTyping">
- <span class="loading-btn">
- <span class="loading-square"></span>
- </span>
- </template>
- <template v-else>
- <img src="../assets/svg/up.svg" alt="send" />
- </template>
- </button>
- </div>
- </div>
- </div>
- <div
- v-if="!isMobile"
- class="right_menu"
- :class="{
- collapsed: !isRightMenuVisible
- }"
- :style="{ width: rightMenuWidth + 'px' }"
- >
- <div v-if="inferenceModel">
- <div style="height: 91vh;overflow: auto;" class="file-list" v-html="formatted_data">
-
- </div>
- </div>
- <!-- 修改收缩按钮的位置和样式 -->
- <!-- <div
- class="collapse-button"
- :class="{
- 'menu-expanded': !isRightMenuCollapsed,
- 'menu-collapsed': isRightMenuCollapsed
- }"
- @click="toggleRightMenu"
- >
- <img
- :src="isRightMenuCollapsed ? leftIcon : rightIcon"
- alt="toggle"
- class="collapse-icon"
- />
- </div> -->
- <!-- 添加拖动条 -->
- <div
- class="resize-handle"
- @mousedown="startResize"
- @touchstart="startResize"
- ></div>
-
- <div class="right-menu-content">
- <!-- 添加 Tab 切换 -->
-
- <!-- 搜索面板 -->
- <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"
- />
- <label for="searchToggle" class="toggle-label">
- {{ enableWebSearch ? '搜索已开启' : '搜索已关闭' }}
- </label>
- </div>
- </div> -->
- <!-- v-if="enableWebSearch" -->
- <SearchResults
- :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>
- <div class="document-preview">
- <div class="preview-container">
- <!-- 文档列表 -->
- <!-- <div class="document-list">
- <div v-if="attachedFiles.length === 0" class="empty-state">
- <FileOutlined class="empty-icon" />
- <p>暂无文档</p>
- </div>
- <div v-else v-for="doc in attachedFiles" :key="doc.name" class="document-item"
- :class="{ 'active': currentDoc?.name === doc.name }"
- @click="handleDocClick(doc)">
- <div class="document-info">
- <div class="doc-icon">
- <img
- v-if="isImageFile(doc)"
- :src="doc.url"
- class="preview-thumbnail"
- alt="preview"
- />
- <video
- v-else-if="isVideoFile(doc)"
- :src="doc.url"
- class="preview-thumbnail"
- preload="metadata"
- />
- <img
- v-else
- :src="getFileIcon(doc)"
- alt="file"
- class="file-icon"
- />
- </div>
- <div class="info">
- <div class="name" :title="doc.name">{{ doc.name }}</div>
- <div class="size">{{ doc.sizeFormatted || formatFileSize(doc.size) }}</div>
- </div>
- </div>
- <div class="actions">
- <button class="action-btn" @click.stop="handleDownload(doc)" title="下载">
- <DownloadOutlined />
- </button>
- </div>
- </div>
- </div> -->
- <!-- 直接预览区域 -->
- <div v-if="currentDoc" class="direct-preview">
- <div class="preview-header">
- <span>{{ currentDoc.name }}</span>
- <button class="close-btn" @click="closePreview">
- <CloseOutlined />
- </button>
- </div>
- <div class="preview-body">
- <!-- 图片预览 -->
- <img
- v-if="isImageFile(currentDoc)"
- :src="currentDoc.url"
- class="preview-image"
- alt="preview"
- />
- <!-- 视频预览 -->
- <video
- v-else-if="isVideoFile(currentDoc)"
- :src="currentDoc.url"
- controls
- class="preview-video"
- />
- <!-- Word文档预览 -->
- <div v-else-if="isWordFile(currentDoc)">
- <vue-office-docx
- :src="currentDoc.url"
- class="doc-preview"
- @rendered="handleRendered"
- @error="handleError"
- />
- </div>
- <!-- Excel文档预览 -->
- <div v-else-if="isExcelFile(currentDoc)">
- <vue-office-excel
- :options="excelOptions"
- @rendered="handleRendered"
- @error="handleExcelError"
- :src="currentDoc.url"
- class="doc-preview"
- />
- </div>
- <!-- PDF预览-->
- <iframe v-else-if="isPdfFile(currentDoc)" :src="currentDoc.url" style="width: 100%; height: 100%;" frameborder="0"></iframe>
- <!-- 其他文件预览 -->
- <div v-else class="file-preview">
- <FileOutlined class="large-icon" />
- <p>该文件类型暂不支持预览</p>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 添加知识库结果面板 -->
- <div v-show="activeTab === 'knowledge'" class="panel-container">
- <!-- <div class="search-header">
- <span>知识库搜索结果</span>
- </div> -->
- <!-- v-if="enableKnowledgeSearch" -->
- <KnowledgeResults
- :searchResults="knowledgeResults"
- :loading="isKnowledgeLoading"
- :total="knowledgeTotal"
- :message="currentSearchQuery"
- />
- </div>
- </div>
-
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, nextTick, onUnmounted, onMounted, computed, watch } from "vue";
- import {
- PlusOutlined,
- FireOutlined,
- ReadOutlined,
- HeartOutlined,
- SmileOutlined,
- CommentOutlined,
- CloudUploadOutlined,
- PaperClipOutlined,
- ShareAltOutlined,
- EllipsisOutlined,
- CloseOutlined,
- FileOutlined,
- ArrowUpOutlined,
- UploadOutlined,
- FileSearchOutlined,
- GlobalOutlined,
- DatabaseOutlined,
- FolderOutlined,
- SelectOutlined,
- DownloadOutlined,
- } 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";
- import KnowledgeResults from "../components/KnowledgeResults/index.vue";
- import ChangePasswordDialog from '../components/ChangePasswordDialog.vue'
- import { useRouter } from 'vue-router'
- import { message } from 'ant-design-vue';
- import VueOfficeDocx from '@vue-office/docx';
- import VueOfficeExcel from '@vue-office/excel';
- /* import '@vue-office/docx/lib/index.css';
- import '@vue-office/excel/lib/index.css'; */
- // 初始化 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 router = useRouter()
- // 会话数据
- const conversations = ref([
- {
- id: 1,
- title: "当前会话",
- messages: [],
- },
- ]);
- // 添加打字效果相关的状态
- const typingSpeed = 50; // 打字速度(毫秒/字符)
- const isTyping = ref(false);
- const typingTimeout = ref(null); // 添加打字超时引用
- // 添加音频播放相关的响应式变量
- const audioPlayer = ref(null);
- const isPlaying = ref(false);
- // 添加当前播放消息的 ID
- const currentPlayingId = ref(null);
- const currentAudioSource = 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 enableKnowledgeSearch = ref(false);
- const knowledgeResults = ref([]);
- const knowledgeTotal = ref(0);
- const isKnowledgeLoading = ref(false);
- const formatted_data = ref('')
- // 推理模式
- const inferenceModel = ref(false);
- // 检测设备类型
- const checkMobile = () => {
- isMobile.value = window.innerWidth <= 768;
- if (isMobile.value) {
- isRightMenuCollapsed.value = true; // 在移动设备下自动收起右侧菜单
- isMenuCollapsed.value = true;
- }else{
- isRightMenuCollapsed.value = false;
- isMenuCollapsed.value = false;
- }
- };
- // 添加窗口大小改变事件监听
- onMounted(() => {
- checkMobile(); // 初始检查
- window.addEventListener('resize', checkMobile);
- });
- const showDropdown = ref(false);
- const toggleDropdown = () => {
- console.log(showDropdown.value);
- showDropdown.value = !showDropdown.value;
- };
- // 添加遮罩层点击处理
- 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; // 确保右侧菜单关闭
- }
- }
-
- // 更新聊天区域的位置
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.left = isMenuCollapsed.value ? '0' : '260px';
- }
- };
- // 修改右侧菜单切换逻辑
- const toggleRightMenu = () => {
- isRightMenuCollapsed.value = false;
-
- if (isRightMenuCollapsed.value) {
- // 收起菜单时的处理
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = '0';
- }
- } else {
- enableWebSearch.value = true;
- // 展开菜单时的处理
- 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 expandRightMenu = () => {
- isRightMenuCollapsed.value = false;
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- };
- // 修改获取文档摘要的方法
- const fetchDocumentSummary = async () => {
- isLoadingSummary.value = true;
- try {
- const response = await axios.get(
- `${import.meta.env.VITE_BASE_AI_API}/api/document/summary?limit=2`,
- {
- headers: {
- 'Authorization': `JWT ${localStorage.getItem('token')}`
- }
- }
- );
- console.log("API Response:", response.data);
- if (response.data.status === 200) {
- documentSummary.value = response.data.data;
- 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("没有可用的文档摘要数据");
- return;
- }
- // 处理第一篇文档的问题(如:热门话题)
- try {
- const firstDocData = documentSummary.value[0];
- if (firstDocData?.questions) {
- // 直接使用questions数组中的问题
- const formattedQaPairs = firstDocData.questions.map((question) => ({
- question,
- }));
- // 随机抽取3个问题显示
- const shuffled = [...formattedQaPairs].sort(() => 0.5 - Math.random());
- currentQaPairs.value = shuffled.slice(0, 3);
- }
- } catch (error) {
- console.error("处理第一篇文档问题失败:", error);
- }
- // 处理第二篇文档的问题(如:设计指南)
- try {
- const secondDocData = documentSummary.value[1];
- if (secondDocData?.questions) {
- // 直接使用questions数组中的问题
- const formattedGuides = secondDocData.questions.map((question) => ({
- question,
- }));
- // 随机抽取3个问题显示
- const shuffled = [...formattedGuides].sort(() => 0.5 - Math.random());
- currentDesignGuides.value = shuffled.slice(0, 3);
- }
- } catch (error) {
- console.error("处理第二篇文档问题失败:", 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);
- }
- });
- // 方法
- const toggleAttachments = () => {
- showAttachments.value = !showAttachments.value;
- };
- const removeFile = (file) => {
- attachedFiles.value = attachedFiles.value.filter((f) => f !== file);
- if(attachedFiles.value.length === 0){
- rightMenuWidth.value = 0;
- if(document.querySelector('.chat')){
- document.querySelector('.chat').style.right = `0px`;
- }
- }else{
- rightMenuWidth.value = 450;
- if(document.querySelector('.chat')){
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- console.log(attachedFiles.value.length);
- };
- 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 === 2000) {
- 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;
- rightMenuWidth.value = 450;
- 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 (attachedFiles.value.length > 0) {
- // 切换到预览标签页
- activeTab.value = 'preview';
- // 展开右侧菜单
- isRightMenuCollapsed.value = false;
- // 如果在移动端,调整布局
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- }
- 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 abortController = ref(null);
- // 修改发送消息方法
- const sendMessage = async () => {
- // 检查登录状态
- const token = localStorage.getItem('token');
- if (!token) {
- router.push('/login');
- return;
- }
- if(isTyping.value){
- isTyping.value = false;
- // 如果存在正在进行的请求,取消它
- if (abortController.value) {
- abortController.value.abort();
- abortController.value = null;
- }
- // 清除打字效果的定时器
- if (typingTimeout.value) {
- clearTimeout(typingTimeout.value);
- typingTimeout.value = null;
- }
- return;
- }
- if (
- (!inputContent.value.trim() && attachedFiles.value.length === 0) ||
- isTyping.value
- )
- return;
- isTyping.value = true;
-
- // 保存当前输入内容
- const currentMessage = inputContent.value.trim();
- // 立即清空输入框
- inputContent.value = '';
-
- // 根据开启的搜索类型执行相应的搜索
- if (enableWebSearch.value) {
- currentSearchQuery.value = currentMessage;
- // 执行互联网搜索
- sendMessageS(currentMessage).catch((error) => {
- console.error("Web search failed:", error);
- });
- } else if (enableKnowledgeSearch.value) {
- currentSearchQuery.value = currentMessage;
- // 执行知识库搜索
- handleKnowledgeSearch({
- query: currentMessage,
- page: 1,
- pageSize: 20
- });
- }
- // 构建消息内容,包含文件信息
- let messageContent = currentMessage;
- // 收集文件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: currentMessage,
- displayContent: currentMessage,
- files: attachedFiles.value.map((file) => ({
- name: file.name,
- size: file.sizeFormatted,
- type: file.type,
- url: file.url,
- })),
- };
- messages.value.push(userMessage);
- // 添加临时AI消息
- const tempAiMessage = {
- id: Date.now() + 1,
- role: "assistant",
- content: "思考中...",
- displayContent: "思考中...",
- isLoading: true,
- };
- messages.value.push(tempAiMessage);
- try {
- // 创建新的 AbortController
- abortController.value = new AbortController();
- if(!inferenceModel.value){
- // 构建请求参数
- /* const requestData = {
- message: currentMessage,
- chat_config_id: "2",
- user_id: "13365429324",
- session_id: session_id.value || "",
- source: "pc",
- image_urls: attachedFiles.value.filter(f => isImageFile(f)).map(f => f.url),
- video_urls: attachedFiles.value.filter(f => isVideoFile(f)).map(f => f.url),
- documents: attachedFiles.value.filter(f => !isImageFile(f) && !isVideoFile(f)).map(f => f.url),
- merchant_id: JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id||1,
- model_type: selectedModelInfo.value.type,
- model_name: selectedModelInfo.value.name
- }; */
- const apiEndpoint = enableWebSearch.value
- ? `${import.meta.env.VITE_BASE_AI_API}/api/chat/web-search-llm/`
- : `${import.meta.env.VITE_BASE_AI_API}/api/chat/online/multimodal`;
- const response = await axios.post(apiEndpoint, {
- message: currentMessage,
- chat_config_id: "2",
- user_id: "13365429324",
- session_id: session_id.value,
- source: "pc",
- image_urls: imageUrls,
- video_urls: videoUrls,
- documents: documentUrls,
- merchant_id:JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id,
- model_type: selectedModelInfo.value.type,
- model_name: selectedModelInfo.value.name
- },
- {
- headers: {
- 'Authorization': `JWT ${localStorage.getItem('token')}`
- },
- signal: abortController.value.signal
- }
- );
- // 移除临时消息
- messages.value = messages.value.filter(
- (msg) => msg.id !== tempAiMessage.id
- );
- // 统一处理响应数据
- const responseData = enableWebSearch.value ? response.data.data : response.data;
- const messageContent = enableWebSearch.value ? responseData.answer : responseData.choices[0].message.content;
- const audioInfo = enableWebSearch.value ? responseData.audio_info?.audio : responseData.audio_info?.audio;
- const sessionId = enableWebSearch.value ? responseData.session_id : responseData.session_id;
- // 更新 session_id
- if (sessionId) {
- session_id.value = sessionId;
- }
- // 创建新的AI消息
- const aiMessage = reactive({
- id: Date.now() + 2,
- role: "assistant",
- content: messageContent,
- displayContent: "",
- audioData: audioInfo || null,
- });
- messages.value.push(aiMessage);
- // 如果有音频数据,自动播放
- if (aiMessage.audioData && !isPlaying.value) {
- await playAudioMessage(aiMessage.audioData, aiMessage.id);
- }
- // 显示打字效果
- await typeMessage(aiMessage);
- }else{
- // 构建请求参数
- const requestData = {
- query: currentMessage,
- /* user_id: "13365429324",
- session_id: session_id.value || "", */
- };
- // 发送初始请求获取SSE URL
- const response = await fetch(`${import.meta.env.VITE_BASE_AI_API}/api/agents/database/`, {
- method: 'POST',
- headers: {
- 'Authorization': `JWT ${token}`,
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(requestData)
- });
- if (!response.ok) {
- throw new Error('请求失败');
- }
- // 获取SSE URL
- const responseData = await response.json();
- console.log(responseData)//.result.summary
- // 移除临时消息
- messages.value = messages.value.filter(
- (msg) => msg.id !== tempAiMessage.id
- );
- formatted_data.value = responseData.result.html
- const messageContent = responseData.result.summary
- // 创建新的AI消息
- const aiMessage = reactive({
- id: Date.now() + 2,
- role: "assistant",
- content: messageContent,
- displayContent: "",
- audioData: null,
- });
- messages.value.push(aiMessage);
- // 显示打字效果
- 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: "发送消息失败,请重试",
- displayContent: "发送消息失败,请重试",
- });
- } finally {
- isTyping.value = false;
- abortController.value = null;
- }
- };
- // 修改 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: "tavily",
- }
- );
- console.log(response)
- if (response.data && response.data.code === 200) {
- searchResults.value = response.data.data.result || [];
- console.log(searchResults.value)
- searchTotal.value = response.data.data.total || 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 minDelay = 20; // 最小打字延迟
- const maxDelay = 50; // 最大打字延迟
- let i = 0;
- message.displayContent = ""; // 确保开始时是空的
- const typeChar = () => {
- if (i < message.content.length && isTyping.value) {
- // 处理特殊字符和标点符号
- let chunk = "";
- const char = message.content[i];
-
- // 如果是中文字符,整个字符一起显示
- if (/[\u4e00-\u9fa5]/.test(char)) {
- chunk = char;
- }
- // 如果是标点符号,与前面的字符一起显示
- else if (/[.,!?;,。!?;]/.test(char)) {
- chunk = char;
- }
- // 如果是空格,与下一个单词一起显示
- else if (char === " " && i + 1 < message.content.length) {
- let j = i + 1;
- while (j < message.content.length && !/[\s\n]/.test(message.content[j])) {
- j++;
- }
- chunk = message.content.substring(i, j);
- }
- // 其他情况,单个字符显示
- else {
- chunk = char;
- }
- message.displayContent = message.content.substring(0, i) + chunk;
- i += chunk.length;
- // 根据字符类型动态调整延迟
- const delay = chunk.length > 1 ? maxDelay : minDelay;
- typingTimeout.value = setTimeout(typeChar, delay);
- } else {
- if (!isTyping.value) {
- // 如果打字被取消,直接显示完整内容
- message.displayContent = message.content;
- }
- 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 (attachedFiles.value.length > 0) {
- // 切换到预览标签页
- activeTab.value = 'preview';
- // 展开右侧菜单
- isRightMenuCollapsed.value = false;
- // 如果在移动端,调整布局
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- }
- if (files.length > 0 && enableWebSearch.value) {
- enableWebSearch.value = false; // 如果有文件上传,自动关闭互联网搜索
- }
- };
- // 添加音频点击处理方法
- const handleAudioClick = (message) => {
- if(message.audioData){
- if (currentPlayingId.value === message.id) {
- stopAudio();
- } else {
- playAudioMessage(message.audioData, message.id);
- }
- }else{
- // 用户消息的文本转语音处理
- if (currentPlayingId.value === message.id) {
- stopAudio();
- } else {
- playUserMessage(message.content, message.id);
- }
- }
- };
- // 添加播放用户消息的方法
- const playUserMessage = async (text, messageId) => {
- try {
- // 如果当前有其他音频在播放,先停止
- if (currentPlayingId.value && currentPlayingId.value !== messageId) {
- stopAudio();
- }
- // 如果已经在播放这条消息,直接返回(这种情况应该不会发生,因为会被上面的 handleAudioClick 处理)
- if (currentPlayingId.value === messageId) {
- return;
- }
- const response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/api/voices/play_text`, {
- text: text
- }, {
- headers: {
- 'Authorization': `JWT ${localStorage.getItem('token')}`
- },
- });
-
- if(response.data.code === 200){
- // 创建音频元素
- const audio = new Audio(`${import.meta.env.VITE_BASE_AI_API}${response.data.audio_url}`);
-
- // 保存当前播放的音频和ID
- currentAudioSource.value = audio;
- currentPlayingId.value = messageId;
- isPlaying.value = true;
-
- // 播放结束时清理
- audio.onended = () => {
- currentPlayingId.value = null;
- currentAudioSource.value = null;
- isPlaying.value = false;
- };
- // 播放错误时清理
- audio.onerror = (e) => {
- console.error('播放文本失败:', e);
- message.error('播放失败,请稍后重试');
- currentPlayingId.value = null;
- currentAudioSource.value = null;
- isPlaying.value = false;
- };
-
- // 开始播放
- await audio.play();
- }
- } catch (error) {
- console.error('播放文本失败:', error);
- message.error('播放失败,请稍后重试');
- currentPlayingId.value = null;
- currentAudioSource.value = null;
- isPlaying.value = false;
- }
- };
- // 修改停止播放方法
- const stopAudio = () => {
- if (audioPlayer.value) {
- audioPlayer.value.pause();
- audioPlayer.value.currentTime = 0;
- audioPlayer.value = null;
- }
- if (currentAudioSource.value) {
- currentAudioSource.value.pause();
- currentAudioSource.value.currentTime = 0;
- currentAudioSource.value = null;
- }
- isPlaying.value = false;
- currentPlayingId.value = null;
- };
- // 在组件卸载时清理
- onUnmounted(() => {
- stopAudio();
- });
- // 添加 renderMarkdown 方法
- const renderMarkdown = (content) => {
- if (!content) return "";
-
- /* // 处理代码块
- content = content.replace(/```([\s\S]*?)```/g, (match, code) => {
- return `<pre class="code-block"><code>${code}</code></pre>`;
- });
-
- // 处理行内代码
- content = content.replace(/`([^`]+)`/g, '<code>$1</code>');
-
- // 处理标题
- content = content.replace(/^(#{1,6})\s+(.*)$/gm, (match, hashes, title) => {
- const level = hashes.length;
- return `<h${level}>${title}</h${level}>`;
- }); */
-
- return md.render(content);
- };
- // 添加新的响应式变量
- const rightMenuWidth = ref(0); // 默认宽度
- 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 && activeTab.value === 'search') {
- isRightMenuCollapsed.value = true;
- currentSearchQuery.value = '';
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = '0';
- }
- } else if (enableWebSearch.value && 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 (inputContent.value.trim()) {
- currentSearchQuery.value = inputContent.value;
-
- // 根据当前选中的搜索类型执行相应的搜索
- if (enableWebSearch.value) {
- /* sendMessageS(inputContent.value).catch((error) => {
- console.error("Web search failed:", error);
- }); */
- } else if (enableKnowledgeSearch.value) {
- handleKnowledgeSearch({
- query: inputContent.value,
- page: 1,
- pageSize: 20
- });
- }
- }
- };
- // 添加处理搜索开关的方法
- const handleSearchToggle = (value) => {
- console.log(enableWebSearch);
- enableWebSearch.value = value;
- if (value) {
- // 开启搜索时,确保右侧面板展开
- expandRightMenu();
- activeTab.value = 'search';
- }
- };
- // 添加新的响应式变量
- const activeTab = ref('search'); // 默认显示搜索面板
- // 处理文档选择
- const handleDocumentSelect = (document) => {
- // 处理文档选择逻辑
- console.log('Selected document:', document);
- };
- // 添加收缩状态控制
- const isCollapsed = ref(false);
- // 监听屏幕宽度变化
- const updateCollapsedState = () => {
- isCollapsed.value = window.innerWidth <= 480;
- };
- onUnmounted(() => {
- window.removeEventListener('resize', updateCollapsedState);
- });
- // 添加收缩状态控制
- const isCompact = ref(false);
- // 监听屏幕宽度变化
- const updateCompactState = () => {
- isCompact.value = window.innerWidth <= 768;
- };
- onUnmounted(() => {
- window.removeEventListener('resize', updateCompactState);
- });
- // 添加计算属性
- const isRightMenuVisible = computed(() => {
- // 移除搜索开关对菜单显示的影响
- checkMobile();
- return !isRightMenuCollapsed.value;
- /* return (!isRightMenuCollapsed.value && (
- (enableWebSearch.value && activeTab.value === 'search') ||
- (enableKnowledgeSearch.value && activeTab.value === 'knowledge') ||
- (activeTab.value === 'preview' && attachedFiles.value.length > 0)
- )); */
- });
- /* // 推理模式
- const handleInferenceModel = () => {
- inferenceModel.value = !inferenceModel.value;
- }; */
- watch(inferenceModel, (newValue) => {
- console.log('推理模式状态:', newValue);
- if (newValue) {
- // 打开右侧窗口
- rightMenuWidth.value = 450;
- isRightMenuCollapsed.value = false;
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
-
- } else {
- // 关闭右侧窗口
- rightMenuWidth.value = 0;
- isRightMenuCollapsed.value = true;
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- });
- // 修改知识库搜索开关处理方法
- const handleKnowledgeToggle = (event) => {
- if (event.target.checked) {
- rightMenuWidth.value = 450;
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- // 开启知识库搜索时关闭互联网搜索
- enableWebSearch.value = false;
- // 切换到知识库标签页
- activeTab.value = 'knowledge';
-
- // 如果有输入内容,执行知识库搜索
- if (inputContent.value.trim()) {
- handleKnowledgeSearch({
- query: inputContent.value,
- page: 1,
- pageSize: 20
- });
- }
- }else{
- rightMenuWidth.value = 0;
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- };
- // 修改知识库搜索方法
- const handleKnowledgeSearch = async (params) => {
- if (!params.query.trim() || !enableKnowledgeSearch.value) return;
- isKnowledgeLoading.value = true;
- try {
- const response = await axios.post(
- `${import.meta.env.VITE_BASE_AI_API}/chataids/mysql_fuzzy_search/`,
- {
- // collection_names: ["25"], // 固定使用 "25" 作为 collection name
- keywords: params.query, // 使用查询关键词
- documents: [], // 保持空数组
- document_ids: [], // 保持空数组
- limit: 5 // 固定返回5条结果
- },
- {
- headers: {
- 'Authorization': `JWT ${localStorage.getItem('token')}`
- }
- }
- );
- console.log(response);
- if (response.data && response.data.status === 200) {
- knowledgeResults.value = response.data.data.data || [];
- knowledgeTotal.value = response.data.data.data.total || 0;
- }
- } catch (error) {
- console.error('Knowledge search failed:', error);
- knowledgeResults.value = [];
- knowledgeTotal.value = 0;
- } finally {
- isKnowledgeLoading.value = false;
- }
- }
- // 修改处理互联网搜索开关的方法
- const handleWebSearchToggle = (event) => {
- if (event.target.checked) {
- rightMenuWidth.value = 450;
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- // 开启互联网搜索时关闭知识库搜索
- enableKnowledgeSearch.value = false;
- // 切换到搜索标签页
- activeTab.value = 'search';
-
- // 如果有输入内容,执行互联网搜索
- if (inputContent.value.trim()) {
- currentSearchQuery.value = inputContent.value;
- /* sendMessageS(inputContent.value).catch((error) => {
- console.error("Web search failed:", error);
- }); */
- }
- }else{
- rightMenuWidth.value = 0;
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
- }
- }
- };
- // 添加新的响应式变量
- const API_BASE_URL = import.meta.env.VITE_BASE_AI_API;
- const models = ref([]);
- const selectedModel = ref(null);
- const isLoadingModels = ref(false);
- // 添加计算属性获取选中的模型信息
- const selectedModelInfo = computed(() => {
- if (!selectedModel.value || !models.value.length) return { type: '', name: '' };
- const model = models.value.find(m => m.id === selectedModel.value);
- return {
- type: model?.type || '',
- name: model?.name || ''
- };
- });
- // 添加获取模型列表的方法
- const fetchModels = async () => {
- isLoadingModels.value = true;
- try {
- const response = await fetch(`${API_BASE_URL}/api/models?merchant_id=${JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id}`);
- const data = await response.json();
- console.log(data);
- if (data.code === 2000) {
- // 确保每个模型对象都有必要的字段
- models.value = (data.data.models || []).map(model => ({
- id: model.id,
- name: model.name,
- type: model.type || model.model_type || 'default' // 确保有type字段
- }));
- if (models.value.length > 0) {
- selectedModel.value = models.value[0].id; // 默认选择第一个模型
- }
- }
- } catch (error) {
- console.error('Failed to fetch models:', error);
- } finally {
- isLoadingModels.value = false;
- }
- };
- // 添加模型选择改变的处理方法
- const handleModelChange = (modelId) => {
- selectedModel.value = modelId;
- };
- // 用户信息
- const userInfo = ref({
- name: '',
- role: ''
- });
- const getUserInfo = () => {
- const storedUserInfo = localStorage.getItem('userInfo');
- if (storedUserInfo) {
- userInfo.value = JSON.parse(storedUserInfo);
- }
- }
- // 在组件挂载时获取模型列表
- onMounted(() => {
- updateCompactState();
- window.addEventListener('resize', updateCompactState);
- updateCollapsedState();
- window.addEventListener('resize', updateCollapsedState);
- checkMobile();
- window.addEventListener('resize', checkMobile);
- fetchModels();
- fetchDocumentSummary();
- const storedUserInfo = localStorage.getItem('userInfo');
- if (storedUserInfo) {
- try {
- userInfo.value = JSON.parse(storedUserInfo);
- } catch (error) {
- console.error('解析用户信息失败:', error);
- }
- }
-
- });
- const showPasswordDialog = ref(false)
- const handleModifyPassword = () => {
- showPasswordDialog.value = true
- showDropdown.value = false // 关闭下拉菜单
- }
- const handleLogout = async () => {
- try {
- // 调用退出登录接口
- await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/logout`, {}, {
- headers: {
- 'Authorization': `JWT ${localStorage.getItem('token')}`
- }
- });
- // 清除本地存储的信息
- localStorage.removeItem('token');
- localStorage.removeItem('userInfo');
- localStorage.removeItem('permissions');
- localStorage.removeItem('username');
- localStorage.removeItem('mobile');
- // 清除 Vuex store 中的用户信息
- store.commit('app/setUser', {
- user: {},
- permission: [],
- auth: {}
- });
- // 关闭下拉菜单
- showDropdown.value = false;
- // 跳转到登录页面
- router.push('/login');
- } catch (error) {
- console.error('退出登录失败:', error);
- // 即使接口调用失败,也清除本地信息并跳转
- localStorage.clear();
- router.push('/login');
- }
- };
- const handleCreateInvitation = async () => {
- try {
- const response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/user/create_invitation`, {}, {
- headers: {
- 'Authorization': `JWT ${localStorage.getItem('token')}`
- }
- });
- console.log(response);
- if (response.data.code === 200) {
- message.success('创建邀请成功');
- // 从当前窗口location获取基础URL
- const baseUrl = `${window.location.protocol}//${window.location.host}`;
- // 跳转到完整URL
- window.location.href = `${baseUrl}${response.data.data.invitation_link}`;
- } else {
- message.error(response.data.message || '创建邀请失败');
- }
- } catch (error) {
- console.error('创建邀请失败:', error);
- message.error(error.response?.data?.message || '创建邀请失败,请稍后重试');
- }
- showDropdown.value = false; // 关闭下拉菜单
- };
- /* return {
- loginForm,
- formData,
- rules,
- loading,
- rememberMe,
- loginType,
- countdown,
- handleLogin,
- sendVerificationCode,
- switchToMobileLogin,
- switchToAccountLogin,
- switchToWechatLogin,
- wechatLoginModal,
- wechatQrCode,
- getWechatQrCode,
- handleWechatLoginCancel,
- handleCreateInvitation
- }; */
- // These variables are already declared above
- /* const currentAudioSource = ref(null);
- const currentPlayingId = ref(null); */
- // 添加播放音频消息的方法
- const playAudioMessage = async (audioData, messageId) => {
- try {
- // 如果当前有其他音频在播放,先停止
- if (currentPlayingId.value && currentPlayingId.value !== messageId) {
- stopAudio();
- }
- // 如果已经在播放这条消息,直接返回
- if (currentPlayingId.value === messageId) {
- return;
- }
- // 创建新的音频元素
- const audio = new Audio(audioData);
-
- // 保存当前播放的音频和ID
- currentAudioSource.value = audio;
- currentPlayingId.value = messageId;
- isPlaying.value = true;
-
- // 播放结束时清理
- audio.onended = () => {
- currentPlayingId.value = null;
- currentAudioSource.value = null;
- isPlaying.value = false;
- };
- // 播放错误时清理
- audio.onerror = (e) => {
- console.error('播放音频失败:', e);
- message.error('播放失败,请稍后重试');
- currentPlayingId.value = null;
- currentAudioSource.value = null;
- isPlaying.value = false;
- };
-
- // 开始播放
- await audio.play();
- } catch (error) {
- console.error('播放音频失败:', error);
- message.error('播放失败,请稍后重试');
- currentPlayingId.value = null;
- currentAudioSource.value = null;
- isPlaying.value = false;
- }
- };
- // 主题状态
- const isDarkTheme = ref(false);
- // 应用主题
- const applyTheme = (isDark) => {
- document.documentElement.classList.toggle('dark-theme', isDark);
- };
- // 切换主题
- const toggleTheme = () => {
- isDarkTheme.value = !isDarkTheme.value;
- localStorage.setItem('theme', isDarkTheme.value ? 'dark' : 'light');
- applyTheme(isDarkTheme.value);
- };
- // 初始化主题
- onMounted(() => {
- if (document.querySelector('.chat')) {
- document.querySelector('.chat').style.right = `0px`;
- }
- checkMobile();
- // 优先使用存储的主题设置
- const savedTheme = localStorage.getItem('theme');
- if (savedTheme) {
- isDarkTheme.value = savedTheme === 'dark';
- } else {
- // 如果没有存储的主题,则使用系统主题
- isDarkTheme.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
- }
- applyTheme(isDarkTheme.value);
- // 监听系统主题变化
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- mediaQuery.addEventListener('change', (e) => {
- if (!localStorage.getItem('theme')) {
- isDarkTheme.value = e.matches;
- applyTheme(e.matches);
- }
- });
- });
- /* return {
- isDarkTheme,
- toggleTheme,
- // ... existing return values ...
- }; */
- const excelOptions = ref({
- xls: false, // 预览xlsx文件设为false;预览xls文件设为true
- minColLength: 15, // excel最少渲染列数
- minRowLength: 50, // excel最少渲染行数,增加以确保显示更多内容
- widthOffset: 50, // 增加列宽偏移量,使内容显示更完整
- heightOffset: 30, // 增加行高偏移量
- forceRender: true, // 强制渲染
- maxSheetLength: 10, // 最多渲染工作表数量
- showGridLines: true, // 显示网格线
- defaultFontSize: 12, // 默认字体大小
- defaultRowHeight: 24, // 默认行高
- autoFitColumn: true, // 自动适应列宽
- overflowColumns: true, // 允许列内容溢出
- showToolbar: true, // 显示工具栏
- });
- // 添加Excel渲染完成的处理函数
- const handleRendered = () => {
- console.log('Excel文件渲染完成');
- nextTick(() => {
- // 手动触发一次重新计算大小
- window.dispatchEvent(new Event('resize'));
- });
- };
- // 添加Excel错误处理函数的详细信息
- const handleExcelError = (error) => {
- console.error('Excel文件渲染失败:', error);
- message.error('Excel预览失败:' + (error.message || '请尝试下载后查看'));
- };
- const isWordFile = (file) => {
- if (!file) return false;
- const wordTypes = ['doc', 'docx'];
- const ext = file.name?.split('.')?.pop()?.toLowerCase() || '';
- const isWord = wordTypes.includes(ext);
- if (isWord && file.type) {
- return file.type === 'application/msword' ||
- file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
- }
- return isWord;
- };
- const isExcelFile = (file) => {
- if (!file) return false;
- const excelTypes = ['xls', 'xlsx'];
- const ext = file.name?.split('.')?.pop()?.toLowerCase() || '';
- return excelTypes.includes(ext);
- };
- const isPdfFile = (file) => {
- if (!file) return false;
- return file.name?.toLowerCase()?.endsWith('.pdf') || false;
- };
- const currentDoc = ref(null);
- // 监听文档列表变化
- watch(() => attachedFiles.value, (newFiles) => {
- if (newFiles && newFiles.length > 0) {
- currentDoc.value = newFiles[newFiles.length - 1]; // 预览最新上传的文档
- } else {
- currentDoc.value = null;
- }
- }, {
- immediate: true,
- deep: true
- });
- const handleDownload = (doc) => {
- if (doc.url) {
- const link = document.createElement('a');
- link.href = doc.url;
- link.download = doc.name;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- }
- };
- const closePreview = () => {
- currentDoc.value = null;
- };
- // 添加文档点击事件处理
- const handleDocClick = (doc) => {
- currentDoc.value = doc;
- };
- /*
- */
- // 添加流式思考步骤
- const currentThinkingSteps = ref([]);
- // 添加流式思考步骤
- const addThinkingStep = (step) => {
- currentThinkingSteps.value.push({
- type: step.type || 'default',
- content: step.content,
- timestamp: Date.now()
- });
- };
- // 清除当前思考步骤
- const clearThinkingSteps = () => {
- currentThinkingSteps.value = [];
- };
- // 在消息发送完成后将思考步骤添加到消息中
- const finalizeThinkingSteps = (messageId) => {
- const message = messages.value.find(m => m.id === messageId);
- if (message) {
- message.thinkingSteps = [...currentThinkingSteps.value];
- clearThinkingSteps();
- }
- };
- </script>
- <style scoped>
- .layout {
- display: flex;
- width: 100%;
- height: 100vh;
- overflow: hidden;
- position: relative;
- }
- .header {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 64px;
- background: #f7f7f8;
- border-bottom: 1px solid rgba(0, 0, 0, 0.06);
- z-index: 999;
- }
- .user-profile-bar {
- margin: 0 auto;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
-
-
- }
- .user-info-bar{
- display: flex;
- align-items: center;
- justify-content: flex-end;
- width: 100%;
- height: 100%;
- background-color: #f7f7f8;
-
- }
- .avatar {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- overflow: hidden;
- margin-right: 12px;
- }
- .avatar img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .user-info {
- display: flex;
- flex-direction: column;
- margin-right: 15px;
- }
- .user-name {
- font-size: 14px;
- font-weight: 500;
- color: var(--primary-text);
- }
- .dropdown-menu {
- position: absolute;
- top: 100%;
- right: 0;
- background: white;
- border-radius: 8px;
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
- min-width: 160px;
- z-index: 1000;
- margin-top: 4px;
- }
- .dropdown-item {
- padding: 12px 16px;
- display: flex;
- align-items: center;
- color: var(--primary-text);
- transition: background-color 0.2s;
- }
- .dropdown-item:hover {
- background-color: #f5f5f5;
- }
- .dropdown-item i {
- margin-right: 8px;
- font-size: 14px;
- color: #666;
- }
- .menu {
- margin-top: 64px;
- background: #f7f7f8;
- width: 260px;
- height: 93%;
- display: flex;
- flex-direction: column;
- border-right: 1px solid rgba(0, 0, 0, 0.06);
- position: absolute;
- left: 0;
- /* transform: translateX(0); */
- transition: all 0.3s ease;
- z-index: 2;
- }
- .menu.collapsed {
- display: none;
- transform: translateX(-260px);
- width: 0;
- }
- .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: all 0.3s ease;
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- justify-content: space-between;
- background-color: var(--primary-bg);
- }
- .menu:not(.collapsed) + .chat {
- left: 260px;
- }
- .chat {
- right: 0;
- transition: all 0.3s ease;
- }
- .layout:has(.right_menu:not(.collapsed)) .chat {
- right: 450px;
- }
- .right_menu:not(.collapsed) ~ .chat {
- right: 450px;
- }
- .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: 60px;
- }
- .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 {
- background: var(--secondary-bg);
- flex: 1;
- color: var(--primary-text);
- padding: 0 8px;
- overflow-y: auto;
- }
- .conversation-item {
- padding: 12px;
- margin: 4px 0;
- border-radius: 8px;
- color: var(--primary-text);
- font-size: 14px;
- cursor: pointer;
- transition: background-color 0.2s;
- }
- .conversation-item:hover {
- background: rgba(0, 0, 0, 0.04);
- }
- .conversation-item.active {
- background: var(--secondary-bg);
- 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: 61%;
- 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 var(--border-color);
- border-radius: 8px;
- background: var(--primary-bg);
- color: var(--primary-text);
- }
- .message-input {
- flex: 1;
- border: none;
- outline: none;
- padding: 8px;
- font-size: 14px;
- background: transparent;
- color: var(--primary-text);
- }
- .message-input::placeholder {
- color: var(--primary-text);
- }
- .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;
- padding: 0;
- }
- .attachment-btn:hover,
- .send-btn:hover {
- background: rgba(0, 0, 0, 0.04);
- color: var(--primary-text);
- }
- .attachments-panel {
- background: var(--primary-bg);
- border-radius: 12px;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
- overflow: hidden;
- }
- .panel-content {
- background: var(--primary-bg);
- }
- .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: var(--primary-text);
- }
- .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;
- color: var(--primary-text);
- transition: all 0.3s;
- }
- .upload-area.has-files {
- padding: 12px;
- background: var(--primary-bg);
- }
- .upload-icon {
- width: 32px;
- height: 32px;
- margin-bottom: 8px;
- }
- .upload-text {
- font-size: 14px;
- color: var(--primary-text);
- margin-bottom: 4px;
- }
- .upload-hint {
- font-size: 12px;
- color: var(--primary-text);
- }
- .file-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- padding: 10px;
- }
- .file-item {
- position: relative;
- width: 260px;
- padding: 8px 12px;
- margin: 4px 0;
- background: var(--secondary-bg);
- 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: var(--primary-text);
- font-size: 14px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .file-info .file-name {
- max-width: 150px;
- color: var(--primary-text);
- }
- .file-name:hover {
- cursor: pointer;
- }
- .file-size {
- display: block;
- color: var(--primary-text);
- 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%;
- }
- .avatar img{
- width: 100%;
- height: 100%;
- 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: 70px;
- }
- .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 属性 */
- }
- .loading-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- background: #4d7cff;
- border-radius: 50%;
- position: relative;
- }
- .loading-square {
- width: 14px;
- height: 14px;
- background: #fff;
- border-radius: 3px;
- display: block;
- animation: loading-square-blink 1s infinite;
- }
- @keyframes loading-square-blink {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.5; }
- }
- :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: var(--secondary-bg);
- }
- .remove-btn {
- margin-left: auto;
- padding: 4px;
- border: none;
- background: transparent;
- cursor: pointer;
- color: var(--primary-text);
- }
- .remove-btn:hover {
- color: var(--primary-text);
- }
- .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: var(--secondary-bg);
- color: var(--primary-text);
- 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: 30px auto 0;
- }
- .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; */
- margin-top: 64px;
- height: 93%;
- 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;
- position: relative;
- }
- .right_menu.collapsed {
- transform: translateX(100%);
- width: 0;
- }
- /* 当右侧菜单展开时,给聊天区域添加右边距 */
- .right_menu:not(.collapsed) + .chat {
- margin-right: 450px;
- }
- .collapse-left-button{
- position: absolute;
- left: 260px;
- top: 50%;
- transform: translateY(-50%);
- width: 20px;
- height: 40px;
- background: var(--secondary-bg);
- border: 1px solid var(--border-color);
- border-right: none;
- border-radius: 4px 0 0 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- z-index: 2;
- transition: all 0.3s ease;
- }
- .menu-collapsed {
- left: 0px;
- border-right: 1px solid var(--border-color);
- 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;
- }
- .message-text ::v-deep table {
- border-collapse: collapse;
- }
- .message-text ::v-deep table th,
- .message-text ::v-deep table td {
- border: 1px solid #ddd;
- padding: 8px;
- }
- .message-text ::v-deep table th {
- background-color: #f2f2f2;
- }
- .message-text ::v-deep table td {
- background-color: #fff;
- }
- .message-text ::v-deep table tr:nth-child(even) {
- background-color: #f2f2f2;
- }
- .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: var(--primary-text);
- cursor: pointer;
- }
- .toggle-input:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .toggle-label.disabled {
- color: var(--primary-text);
- cursor: not-allowed;
- }
- .disabled-hint {
- font-size: 12px;
- color: var(--primary-text);
- margin-left: 4px;
- }
- /* 添加菜单收缩相关样式 */
- .menu {
- /* 现有样式保持不变 */
- transition: width 0.3s ease;
- position: relative;
- }
- .menu.collapsed {
- width: 0;
- padding: 0;
- }
- /* 添加收缩按钮样式 */
- .collapse-button {
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- width: 20px;
- height: 40px;
- background: var(--secondary-bg);
- border: 1px solid var(--border-color);
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- z-index: 3;
- transition: right 0.3s ease, left 0.3s ease;
- }
- .collapse-button:hover {
- background: var(--secondary-bg);
- }
- .collapse-icon {
- width: 12px;
- height: 12px;
- }
- /* 添加响应式布局样式 */
- @media screen and (max-width: 768px) {
- .layout {
- /* 移动端布局调整 */
- flex-direction: column;
- }
- .menu {
- /* 移动端菜单调整 */
- position: fixed;
- width: 100%;
- height: 93%;
- z-index: 1000;
- transform: translateX(-100%);
- }
- .menu.collapsed {
- transform: translateX(-100%);
- }
- .collapse-left-button {
- z-index: 999;
- /* 移动端收缩按钮调整 */
- /* display: none; */
- }
- .chat {
- /* 移动端聊天区域调整 */
- width: 100%;
- margin: 0;
- padding: 16px;
- left: 0 !important;
- right: 0 !important;
- }
- .message-list {
- /* 移动端消息列表调整 */
- width: 100%;
- padding: 10px;
- }
- .input-container {
- /* 移动端输入区域调整 */
- width: 98%;
- padding: 10px;
- }
- .cards-container {
- /* 移动端卡片容器调整 */
- flex-direction: column;
- }
- .suggestion-card {
- /* 移动端建议卡片调整 */
- width: 100%;
- }
- .welcome-section {
- /* 移动端欢迎区域调整 */
- width: 100%;
- padding: 0;
- }
- .right_menu {
- /* 移动端右侧菜单调整 */
- position: fixed;
- width: 100%;
- height: 100%;
- z-index: 998;
- }
- .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: 16px 0 ;
- margin: 0;
- width: 100%;
- }
- .message-list {
- /* 保持消息列表的宽度一致 */
- width: 100%;
- max-width: 800px;
- margin: 60px auto 0;
- padding: 0 10px;
- }
- .input-container {
- /* 保持输入区域的宽度一致 */
- width: 95%;
- max-width: 800px;
- margin: 0 auto;
- padding: 0;
- }
- .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;
- }
- /* 调整菜单样式 */
- .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: 998;
- } */
- .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:5px;
- }
- .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 var(--border-color);
- }
- .search-header span {
- font-size: 16px;
- font-weight: bold;
- }
- /* 添加开关样式 */
- .web-search-toggle {
- display: flex;
- align-items: center;
- color: var(--primary-text);
- }
- .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: var(--secondary-bg);
- }
- .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 var(--border-color);
- color: var(--primary-text);
- font-size: 16px;
- font-weight: bold;
- }
- .search-toggles {
- display: flex;
- gap: 12px;
- padding: 8px 12px;
- /* background: var(--secondary-bg); */
- border-radius: 8px;
- transition: all 0.3s ease;
- justify-content: space-between;
- &.compact {
- .search-toggle-item {
- padding: 6px;
- min-width: auto;
- width: auto;
- }
- .toggle-content {
- gap: 4px;
- justify-content: center;
- }
- .toggle-text {
- /* display: none; */
- }
- .toggle-switch {
- margin-left: 8px;
- }
- }
- }
- .search-toggle-item {
- position: relative;
- display: flex;
- align-items: center;
- background: var(--primary-bg);
- color: var(--primary-text);
- border-radius: 6px;
- padding: 6px 12px;
- transition: all 0.3s;
- width: 140px;
- @media screen and (max-width: 768px) {
- width: auto;
- padding: 6px 8px;
- }
- &:hover {
- background: rgba(0, 0, 0, 0.02);
- }
- }
- .toggle-content {
- display: flex;
- align-items: center;
- gap: 8px;
- color: rgba(0, 0, 0, 0.65);
- transition: all 0.3s;
- width: 100%;
- .anticon {
- font-size: 16px;
- flex-shrink: 0;
- }
- .toggle-text {
- font-size: 14px;
- transition: all 0.3s;
- white-space: nowrap;
- color: var(--primary-text);
- }
- }
- .toggle-switch {
- position: relative;
- display: flex;
- align-items: center;
- }
- .toggle-input {
- position: absolute;
- opacity: 0;
- width: 0;
- height: 0;
- &:checked + .toggle-label {
- .toggle-slider {
- background: #1677ff;
- &:before {
- transform: translateX(16px);
- }
- }
- }
- &:disabled + .toggle-label {
- cursor: not-allowed;
-
- .toggle-slider {
- background: #d9d9d9;
- &:before {
- background: #f5f5f5;
- }
- }
- }
- }
- .toggle-label {
- display: flex;
- align-items: center;
- cursor: pointer;
- .toggle-slider {
- position: relative;
- width: 36px;
- height: 20px;
- background: #d9d9d9;
- border-radius: 10px;
- transition: all 0.3s;
- &:before {
- content: '';
- position: absolute;
- top: 2px;
- left: 2px;
- width: 16px;
- height: 16px;
- background: white;
- border-radius: 50%;
- transition: transform 0.3s;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- }
- }
- &.disabled {
- cursor: not-allowed;
- }
- }
- /* 移动端适配 */
- @media screen and (max-width: 480px) {
- .search-toggles {
- padding: 6px;
- gap: 6px;
- }
- .search-toggle-item {
- padding: 6px;
- }
- .toggle-content {
- gap: 4px;
- }
- }
- @media screen and (max-width: 768px) {
- .search-toggles {
- &:not(.compact) {
- .search-toggle-item {
- padding: 6px 8px;
- }
- .toggle-content {
- gap: 4px;
- }
- .toggle-text {
- font-size: 12px;
- }
- }
- }
- }
- @media screen and (max-width: 480px) {
- .search-toggles {
- padding: 0px;
- gap: 6px;
- &:not(.compact) {
- .toggle-text {
- /* display: none; */
- }
- .search-toggle-item {
- padding: 6px;
- }
- }
- }
- }
- /* 深色模式支持 */
- @media (prefers-color-scheme: dark) {
- .search-toggle-item {
- background: rgba(255, 255, 255, 0.04);
- &:hover {
- background: rgba(255, 255, 255, 0.08);
- }
- }
- .toggle-content {
- color: rgba(255, 255, 255, 0.65);
- }
- .toggle-label {
- .toggle-slider {
- background: #424242;
- &:before {
- background: #8c8c8c;
- }
- }
- }
- .toggle-input:checked + .toggle-label {
- .toggle-slider {
- background: #1677ff;
- &:before {
- background: white;
- }
- }
- }
- .toggle-input:disabled + .toggle-label {
- .toggle-slider {
- background: #1f1f1f;
- &:before {
- background: #434343;
- }
- }
- }
- }
- /* 修改右侧菜单面板样式 */
- .right_menu {
- position: fixed;
- right: 0;
- top: 0;
- bottom: 0;
- width: 450px;
- background: #fff;
- border-left: 1px solid rgba(0, 0, 0, 0.06);
- display: flex;
- flex-direction: column;
- transition: transform 0.3s ease;
- z-index: 998;
- }
- .right_menu.collapsed {
- transform: translateX(100%);
- }
- .right-menu-content {
- height: 100%;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- /* Tab 样式调整 */
- .right-menu-tabs {
- display: flex;
- background: var(--secondary-bg);
- color: var(--primary-text);
- border-bottom: 1px solid #f0f0f0;
- padding: 0;
- height: 46px;
- flex-shrink: 0;
- }
- .tab-item {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 12px;
- color: var(--primary-text);
- cursor: pointer;
- transition: all 0.3s;
- border-bottom: 2px solid transparent;
- font-size: 14px;
- }
- .tab-item.active {
- color: #1677ff;
- border-bottom-color: #1677ff;
- background: rgba(22, 119, 255, 0.04);
- }
- .tab-item:hover {
- color: #1677ff;
- }
- /* 面板容器样式 */
- .panel-container {
- flex: 1;
- overflow-y: auto;
- background: var(--secondary-bg);
- display: flex;
- flex-direction: column;
- }
- /* 搜索头部样式 */
- .search-header {
- padding: 16px;
- border-bottom: 1px solid var(--border-color);
- display: flex;
- justify-content: space-between;
- align-items: center;
- background: var(--secondary-bg);
- flex-shrink: 0;
- }
- .search-header span {
- font-size: 16px;
- font-weight: 500;
- color: var(--primary-text);
- }
- /* 知识库结果面板样式 */
- .knowledge-results {
- flex: 1;
- padding: 16px 5px;
- overflow-y: auto;
- }
- /* 调整聊天区域的右边距 */
- .chat {
- transition: right 0.3s ease;
- }
- .right_menu:not(.collapsed) ~ .chat {
- right: 450px;
- }
- /* 移动端适配 */
- @media screen and (max-width: 768px) {
- .right_menu {
- width: 93%;
- }
- .right_menu:not(.collapsed) ~ .chat {
- right: 100%;
- }
- .tab-item {
- padding: 8px;
- }
- .tab-item .anticon {
- font-size: 16px;
- }
- .tab-item span {
- font-size: 12px;
- }
- .search-header {
- padding: 12px;
- }
- .search-header span {
- font-size: 14px;
- }
- }
- /* 优化滚动条样式 */
- .panel-container::-webkit-scrollbar {
- width: 4px;
- }
- .panel-container::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.1);
- border-radius: 2px;
- }
- .panel-container::-webkit-scrollbar-track {
- background: transparent;
- }
- /* 添加阴影效果 */
- .right_menu {
- box-shadow: -2px 0 8px rgba(0, 0, 0, 0.06);
- }
- /* 优化过渡动画 */
- .right_menu,
- .chat,
- .tab-item,
- .panel-container {
- transition: all 0.3s ease;
- }
- /* 确保内容不会溢出 */
- .right-menu-content {
- max-height: 100%;
- background-color: var(--secondary-bg) !important;
- }
- .panel-container {
- max-height: calc(100% - 46px); /* 减去 tab 的高度 */
- }
- /* 菜单展开时的按钮样式 */
- .collapse-button.menu-expanded {
- left: -20px;
- border-right: none;
- border-radius: 4px 0 0 4px;
- }
- /* 菜单收起时的按钮样式 */
- .collapse-button.menu-collapsed {
- left: -20px;
- border-left: none;
- border-radius: 0 4px 4px 0;
- }
- /* 确保右侧菜单收起时按钮仍然可见 */
- .right_menu.collapsed .collapse-button {
- right: auto;
- left: -20px;
- }
- /* 添加模型选择框样式 */
- .model-select {
- background: transparent;
- border: 1px solid #d9d9d9;
- border-radius: 4px;
- padding: 4px 8px;
- font-size: 14px;
- color: var(--primary-text);
- cursor: pointer;
- transition: all 0.3s;
- outline: none;
- margin-left: auto;
- min-width: 80px;
- &:hover {
- border-color: #1677ff;
- }
- &:focus {
- border-color: #1677ff;
- box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1);
- }
- &:disabled {
- background: #f5f5f5;
- cursor: not-allowed;
- color: rgba(0, 0, 0, 0.25);
- }
- option {
- background: white;
- color: rgba(0, 0, 0, 0.88);
- }
- }
- @media screen and (max-width: 768px) {
- .model-select {
- width: 80px;
- font-size: 12px;
- padding: 2px 4px;
- }
- }
- /* 全局主题变量 */
- :root {
- /* 浅色主题变量 */
- --primary-bg: #ffffff;
- --secondary-bg: #f5f5f5;
- --primary-text: #333333;
- --secondary-text: #666666;
- --border-color: #e0e0e0;
- --hover-bg: #f0f0f0;
- --card-bg: #ffffff;
- --shadow-color: rgba(0, 0, 0, 0.1);
- --highlight-color: #1890ff;
-
- /* 动画时间 */
- --theme-transition: 0.3s ease;
- }
- /* 深色主题变量 */
- :root.dark-theme {
- --primary-bg: #1a1a1a;
- --secondary-bg: #2d2d2d;
- --primary-text: #ffffff;
- --secondary-text: #b3b3b3;
- --border-color: #404040;
- --hover-bg: #363636;
- --card-bg: #2d2d2d;
- --shadow-color: rgba(0, 0, 0, 0.3);
- --highlight-color: #40a9ff;
- }
- /* 主题过渡动画 */
- .layout * {
- transition: background-color var(--theme-transition),
- color var(--theme-transition),
- border-color var(--theme-transition),
- box-shadow var(--theme-transition);
- }
- /* 主题切换按钮样式 */
- .theme-switch {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 25px;
- height: 25px;
- margin-right: 16px;
- padding: 8px;
- border-radius: 50%;
- cursor: pointer;
- background-color: var(--hover-bg);
- transition: all var(--theme-transition);
- }
- .theme-switch:hover {
- background-color: var(--border-color);
- }
- .theme-icon {
- width: 20px;
- height: 20px;
- color: var(--primary-text);
- }
- /* 主题图标切换动画 */
- .theme-icon-enter-active,
- .theme-icon-leave-active {
- transition: transform 0.3s ease, opacity 0.3s ease;
- }
- .theme-icon-enter-from,
- .theme-icon-leave-to {
- opacity: 0;
- transform: scale(0.8) rotate(-45deg);
- }
- .theme-icon-enter-to,
- .theme-icon-leave-from {
- opacity: 1;
- transform: scale(1) rotate(0);
- }
- /* 应用主题样式到现有组件 */
- :deep(.layout) {
- background-color: var(--primary-bg);
- color: var(--primary-text);
- }
- :deep(.header) {
- background-color: var(--secondary-bg);
- border-bottom: 1px solid var(--border-color);
- }
- :deep(.menu) {
- background-color: var(--secondary-bg);
- border-right: 1px solid var(--border-color);
- }
- :deep(.user-info-bar) {
- background-color: var(--secondary-bg);
- color: var(--primary-text);
- }
- :deep(.dropdown-menu) {
- background-color: var(--card-bg);
- border: 1px solid var(--border-color);
- box-shadow: 0 2px 8px var(--shadow-color);
- }
- :deep(.dropdown-item) {
- color: var(--primary-text);
- }
- :deep(.dropdown-item:hover) {
- background-color: var(--hover-bg);
- }
- :deep(.suggestion-card) {
- background-color: var(--card-bg);
- border: 1px solid var(--border-color);
- }
- :deep(.suggestion-item) {
- color: var(--primary-text);
- }
- :deep(.suggestion-item:hover) {
- background-color: var(--hover-bg);
- }
- :deep(.message-bubble) {
- background-color: var(--card-bg);
- border: 1px solid var(--border-color);
- }
- /* 确保主题样式应用到聊天消息 */
- :deep(.message-item.user .message-bubble) {
- /* background-color: var(--highlight-color); */
- color: var(--primary-text);
- }
- :deep(.message-item.assistant .message-bubble) {
- background-color: var(--card-bg);
- color: var(--primary-text);
- }
- /* 搜索区域样式 */
- :deep(.search-section) {
- background-color: var(--card-bg);
- border: 1px solid var(--border-color);
- padding: 20px;
- border-radius: 8px;
- }
- :deep(.search-input) {
- background-color: var(--primary-bg);
- border: 1px solid var(--border-color);
- color: var(--primary-text);
- }
- :deep(.search-input:hover),
- :deep(.search-input:focus) {
- border-color: var(--highlight-color);
- box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
- }
- /* 提示问题区域样式 */
- :deep(.suggestion-section) {
- background-color: var(--card-bg);
- border-radius: 8px;
- padding: 20px;
- margin-top: 20px;
- }
- :deep(.suggestion-section h3) {
- color: var(--primary-text);
- margin-bottom: 16px;
- }
- :deep(.cards-container) {
- display: grid;
- gap: 16px;
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
- }
- :deep(.suggestion-card) {
- background-color: var(--secondary-bg);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 16px;
- transition: all var(--theme-transition);
- }
- :deep(.suggestion-card:hover) {
- border-color: var(--highlight-color);
- box-shadow: 0 2px 8px var(--shadow-color);
- }
- :deep(.card-header) {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 8px;
- }
- :deep(.card-header h4) {
- color: var(--primary-text);
- margin: 0;
- }
- :deep(.card-subtitle) {
- color: var(--secondary-text);
- margin-bottom: 12px;
- }
- :deep(.suggestion-items) {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- :deep(.suggestion-item) {
- padding: 8px 12px;
- border-radius: 4px;
- background-color: var(--primary-bg);
- color: var(--primary-text);
- cursor: pointer;
- transition: all var(--theme-transition);
- }
- :deep(.suggestion-item:hover) {
- background-color: var(--hover-bg);
- color: var(--highlight-color);
- }
- /* 保存问题区域样式 */
- :deep(.save-question) {
- background-color: var(--card-bg);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 20px;
- margin-top: 20px;
- }
- :deep(.save-question-title) {
- color: var(--primary-text);
- margin-bottom: 16px;
- font-weight: bold;
- }
- :deep(.save-question-input) {
- width: 100%;
- padding: 8px 12px;
- border: 1px solid var(--border-color);
- border-radius: 4px;
- background-color: var(--primary-bg);
- color: var(--primary-text);
- margin-bottom: 8px;
- }
- :deep(.save-question-input:focus) {
- border-color: var(--highlight-color);
- outline: none;
- box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
- }
- /* 搜索结果区域样式 */
- :deep(.search-results) {
- background-color: var(--secondary-bg);
- /* border: 1px solid var(--border-color); */
- border-radius: 8px;
- /* padding: 20px; */
- /* margin-top: 20px; */
- }
- :deep(.search-results-title) {
- color: var(--primary-text);
- margin-bottom: 16px;
- }
- :deep(.no-results) {
- color: var(--secondary-text);
- text-align: center;
- padding: 40px 0;
- }
- /* 输入框和按钮样式 */
- :deep(.ant-input),
- :deep(.ant-input-search-button) {
- background-color: var(--primary-bg);
- border-color: var(--border-color);
- color: var(--primary-text);
- }
- :deep(.ant-input:hover),
- :deep(.ant-input:focus) {
- border-color: var(--highlight-color);
- }
- :deep(.ant-input-search-button) {
- background-color: var(--highlight-color);
- border-color: var(--highlight-color);
- color: #ffffff;
- }
- :deep(.ant-input-search-button:hover) {
- background-color: var(--highlight-color);
- opacity: 0.9;
- }
- /* 标签页样式 */
- :deep(.ant-tabs-tab) {
- color: var(--secondary-text);
- }
- :deep(.ant-tabs-tab-active) {
- color: var(--highlight-color) !important;
- }
- :deep(.ant-tabs-ink-bar) {
- background-color: var(--highlight-color);
- }
- /* 图标样式适配 */
- :deep(.tab-icon) {
- filter: var(--icon-filter);
- }
- /* 其他已有的样式保持不变 */
- </style>
- <style>
- /* 添加图标滤镜变量 */
- :root {
- --icon-filter: none;
- }
- :root.dark-theme {
- --icon-filter: brightness(0) invert(1);
- }
- </style>
- <style lang="less" scoped>
- // ... existing code ...
- /* 文档预览样式 */
- .document-preview {
- height: 100%;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- }
- .preview-container {
- display: flex;
- height: 100%;
- gap: 16px;
- }
- .document-list {
- width: 300px;
- flex-shrink: 0;
- overflow-y: auto;
- padding: 16px;
- border-right: 1px solid var(--border-color);
- }
- .empty-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 48px 0;
- color: var(--primary-text);
- .empty-icon {
- font-size: 48px;
- margin-bottom: 16px;
- }
- p {
- font-size: 14px;
- margin: 0;
- }
- }
- .document-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px;
- border-radius: 8px;
- margin-bottom: 8px;
- background: var(--secondary-bg);
- transition: all 0.3s;
- cursor: pointer;
- &:hover {
- background: var(--hover-bg);
- }
- &.active {
- background: var(--primary-bg);
- border: 1px solid var(--primary-color);
- }
- }
- .document-info {
- display: flex;
- align-items: center;
- gap: 12px;
- flex: 1;
- }
- .doc-icon {
- width: 40px;
- height: 40px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .preview-thumbnail {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: 4px;
- }
- /* .file-icon {
- width: 24px;
- height: 24px;
- } */
- .info {
- flex: 1;
- min-width: 0;
- .name {
- font-size: 14px;
- color: var(--primary-text);
- margin-bottom: 4px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .size {
- font-size: 12px;
- color: var(--secondary-text);
- }
- }
- .actions {
- display: flex;
- gap: 8px;
- }
- .action-btn {
- width: 32px;
- height: 32px;
- border: none;
- background: transparent;
- border-radius: 4px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--primary-text);
- transition: all 0.3s;
- &:hover {
- background: rgba(0, 0, 0, 0.04);
- color: var(--primary-color);
- }
- }
- .direct-preview {
- flex: 1;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- background: var(--secondary-bg);
- border-radius: 8px;
- }
- .preview-header {
- padding: 16px;
- border-bottom: 1px solid var(--border-color);
- display: flex;
- justify-content: space-between;
- align-items: center;
- span {
- font-size: 16px;
- font-weight: 500;
- color: var(--primary-text);
- }
- .close-btn {
- border: none;
- background: transparent;
- cursor: pointer;
- color: var(--primary-text);
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 8px;
- border-radius: 4px;
- transition: all 0.3s;
- &:hover {
- background: rgba(0, 0, 0, 0.04);
- color: var(--primary-color);
- }
- }
- }
- .preview-body {
- flex: 1;
- overflow: auto;
- padding: 24px;
- display: flex;
- align-items: flex-start;
- justify-content: center;
- }
- .preview-image {
- max-width: 100%;
- max-height: 70vh;
- object-fit: contain;
- }
- .preview-video {
- max-width: 100%;
- max-height: 70vh;
- }
- .doc-preview {
- width: 100%;
- height: 100%;
- min-height: 500px;
- }
- :deep(.docx-wrapper) {
- width: 100%;
- height: 100%;
- .docx{
- width: 100% !important;
- }
- }
- :deep(.excel-wrapper) {
- width: 100%;
- height: 100%;
- overflow: auto;
-
- .excel-container {
- min-width: 100%;
- min-height: 100%;
- }
- .excel-toolbar {
- position: sticky;
- top: 0;
- z-index: 1;
- background: #fff;
- padding: 8px;
- border-bottom: 1px solid #e8e8e8;
- }
- table {
- width: 100%;
- border-collapse: collapse;
-
- th, td {
- padding: 8px;
- border: 1px solid #e8e8e8;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
- }
- .file-preview {
- text-align: center;
- color: var(--primary-text);
- .large-icon {
- font-size: 64px;
- margin-bottom: 16px;
- }
- p {
- margin: 0;
- font-size: 14px;
- }
- }
- // 响应式布局
- @media screen and (max-width: 768px) {
- .preview-container {
- flex-direction: column;
- }
- .document-list {
- width: 100%;
- border-right: none;
- border-bottom: 1px solid var(--border-color);
- }
- }
- // ... existing code ...
- </style>
- <style>
- /* 移动端适配样式 */
- @media screen and (max-width: 768px) {
- .suggestion-section{
- padding: 20px 0 !important;
- }
- .layout {
- flex-direction: column;
- }
- .header {
- /* padding: 10px; */
- }
- .user-profile-bar {
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- padding: 0 10px;
- flex-wrap: nowrap;
- }
- .logo {
- flex-shrink: 0;
- max-width: 120px;
- }
- .user-info-bar {
- display: flex;
- align-items: center;
- gap: 8px;
- flex: 1;
- justify-content: flex-end;
- min-width: 0;
- }
- .theme-switch {
- flex-shrink: 0;
- }
- .user-info {
- display: flex;
- flex-direction: column;
- font-size: 12px;
- min-width: 0;
- flex: 1;
- }
- .user-name {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- margin: 0;
- line-height: 1.2;
- }
- .avatar {
- flex-shrink: 0;
- width: 32px;
- height: 32px;
- margin-left: 8px;
- }
- .suggestion-item{
- width: 85%;
- margin: 0 auto;
- }
- @media screen and (max-width: 480px) {
- .user-info-bar {
- gap: 4px;
- }
-
- .logo {
- max-width: 150px;
- }
- .avatar {
- width: 28px;
- height: 28px;
- margin-left: 4px;
- }
- }
- .logo {
- margin-bottom: 10px;
- }
- .logo-img {
- width: 60px;
- }
- .user-info-bar {
- width: 100%;
- justify-content: space-between;
- }
- .menu {
- position: fixed;
- top: 60px;
- left: -100%;
- width: 80%;
- height: calc(100vh - 60px);
- z-index: 1000;
- transition: left 0.3s ease;
- }
- .menu.collapsed {
- left: 0;
- }
- .chat {
- width: 100%;
- margin-left: 0;
- padding: 0;
- }
- .message-list {
- padding: 10px;
- }
- .message-bubble {
- max-width: 90%;
- }
- .config-container {
- padding: 10px;
- }
- .cards-container {
- flex-direction: column;
- }
- .suggestion-card {
- width: 90% !important;
- padding: 16px 2px !important;
- margin: 10px auto !important;
- }
- .collapse-left-button {
- position: fixed;
- top: 70px;
- left: 10px;
- z-index: 1001;
- }
- }
- /* 针对更小屏幕的优化 */
- @media screen and (max-width: 480px) {
- .user-info {
- font-size: 12px;
- }
- .theme-icon {
- width: 20px;
- height: 20px;
- }
- .avatar img {
- width: 30px;
- height: 30px;
- }
- .message-content {
- font-size: 14px;
- }
- .new-conversation-btn {
- padding: 8px;
- font-size: 14px;
- }
- }
- /* 移动端样式调整 */
- @media screen and (max-width: 768px) {
- .layout {
- height: 100vh; /* 使用视口高度 */
- overflow: hidden; /* 防止页面滚动 */
- display: flex;
- flex-direction: column;
- }
- .header {
- flex-shrink: 0; /* 防止header被压缩 */
- }
- .chat {
- flex: 1; /* 占据剩余空间 */
- height: calc(100vh - 60px); /* 减去header高度 */
- overflow-y: auto; /* 内容过多时在chat区域内滚动 */
- -webkit-overflow-scrolling: touch; /* 优化iOS滚动 */
- }
- .message-list {
- height: calc(100% - 120px); /* 减去输入框高度 */
- overflow-y: auto;
- }
- .input-wrapper {
- position: sticky;
- bottom: 0;
- background: var(--primary-bg);
- padding: 10px;
- border-top: 1px solid var(--border-color);
- }
- .resize-handle {
- display: none;
- }
-
- .right_menu {
- width: 100% !important;
- }
- }
- /* PC端样式保持不变 */
- @media screen and (min-width: 769px) {
- .layout {
- height: 100vh;
- display: flex;
- flex-direction: column;
- }
- .chat {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- .message-list {
- flex: 1;
- overflow-y: auto;
- }
- }
- .thinking-steps {
- margin-bottom: 10px;
- font-size: 12px;
- color: #666;
- }
- .thinking-step {
- padding: 4px 8px;
- margin-bottom: 4px;
- border-radius: 4px;
- background: #f5f5f5;
- transition: all 0.3s ease;
- }
- .thinking-step.start {
- background: #e8f5e9;
- }
- .thinking-step.text_analysis {
- background: #e3f2fd;
- }
- .thinking-step.planning {
- background: #fff3e0;
- }
- .thinking-step.execution {
- background: #f3e5f5;
- }
- .thinking-step.result {
- background: #e8eaf6;
- }
- .thinking-step.conclusion {
- background: #e0f7fa;
- font-weight: 500;
- }
- </style>
- <style scoped>
- .thinking-steps-stream {
- position: sticky;
- top: 0;
- z-index: 100;
- background: rgba(255, 255, 255, 0.95);
- padding: 10px;
- margin: 10px 0;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- .thinking-step-stream {
- padding: 8px 12px;
- margin: 4px 0;
- border-radius: 6px;
- background: #f5f5f5;
- font-size: 14px;
- line-height: 1.5;
- opacity: 0;
- transform: translateY(-10px);
-
- &.fade-in {
- animation: fadeInDown 0.3s ease forwards;
- }
- .typing-indicator {
- display: inline-flex;
- align-items: center;
- margin-left: 8px;
-
- span {
- width: 4px;
- height: 4px;
- margin: 0 2px;
- background: #666;
- border-radius: 50%;
- animation: typing 1s infinite ease-in-out;
-
- &:nth-child(1) { animation-delay: 0.2s; }
- &:nth-child(2) { animation-delay: 0.3s; }
- &:nth-child(3) { animation-delay: 0.4s; }
- }
- }
- }
- }
- @keyframes fadeInDown {
- from {
- opacity: 0;
- transform: translateY(-10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
- }
- @keyframes typing {
- 0%, 60%, 100% { transform: translateY(0); }
- 30% { transform: translateY(-4px); }
- }
- </style>
|