| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685 |
- <template>
- <div class="ba-pc-container">
- <!-- 全局提示(按会话并行生成后,发送冲突/后端 409 等都要给用户可见反馈) -->
- <Toast :message="toastMessage" />
- <div class="bg-image" v-show="!currentSession" id="particle-bg" aria-hidden="true"></div>
- <aside class="aside-nav">
- <div class="aside-shell">
- <div class="aside-header">
- <img src="@/assets/qingpu_logo_2.png" class="logo" alt="青浦区营商助手标志" />
- <div class="logo-text">
- <div class="title">青浦区营商助手</div>
- <div class="divider"></div>
- <div class="subtitle">懂营商·懂政策·更懂企业</div>
- </div>
- </div>
- <button type="button" class="cta-button" @click="startNewChat">
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"
- aria-hidden="true">
- <path d="M6 8l-6 0 0-2 6 0 0-6 2 0 0 6 6 0 0 2-6 0 0 6-2 0 0-6 0 0" fill="currentColor" />
- </svg>
- <span>咨询新的话题</span>
- </button>
- <div class="nav-tabs">
- <div class="nav-tabs-header">历史咨询</div>
- <div class="nav-tabs-scroll">
- <div v-if="chatHistory.length === 0" class="nav-empty">
- 还没有历史会话,先发起一次咨询吧。
- </div>
- <button v-for="(session, index) in chatHistory" :key="index" type="button" class="nav-item" :class="{
- active: currentSessionIndex === index,
- 'popover-open': activePopoverIndex === index,
- 'actions-visible': hoveredSessionIndex === index || activePopoverIndex === index
- }" @click="switchSession(index)" @mouseenter="hoveredSessionIndex = index"
- @mouseleave="hoveredSessionIndex = null">
- <div class="nav-icon" aria-hidden="true">
- <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path
- d="M0 16.6667V1.66667C0 1.20833 0.163194 0.815972 0.489583 0.489583C0.815972 0.163194 1.20833 0 1.66667 0H15C15.4583 0 15.8507 0.163194 16.1771 0.489583C16.5035 0.815972 16.6667 1.20833 16.6667 1.66667V11.6667C16.6667 12.125 16.5035 12.5174 16.1771 12.8438C15.8507 13.1701 15.4583 13.3333 15 13.3333H3.33333L0 16.6667ZM2.625 11.6667H15V1.66667H1.66667V12.6042L2.625 11.6667Z"
- fill="currentColor" />
- </svg>
- </div>
- <div class="nav-item-content">{{ session.title || '新对话' }}</div>
- <div class="nav-item-actions"
- :class="{ 'is-visible': hoveredSessionIndex === index || activePopoverIndex === index }"
- @click.stop="openPopover(index, $event)">
- <div class="action-dots">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <circle cx="6" cy="12" r="1.5" fill="#464555" />
- <circle cx="11" cy="12" r="1.5" fill="#464555" />
- <circle cx="16" cy="12" r="1.5" fill="#464555" />
- </svg>
- </div>
- </div>
- <div v-if="activePopoverIndex === index" class="nav-popover" @click.stop.prevent>
- <div class="popover-item" @click.stop.prevent="openRenameModal(index)">
- <svg width="16" height="16" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path
- d="M92.16604 599.04468l-57.6 219.51996c-1.64038 6.24548-1.6793 12.80377-0.11318 19.06829 1.56613 6.26453 4.68673 12.03296 9.07318 16.77173 6.93753 6.51819 16.08099 10.17554 25.60001 10.23999l14.72 0 200.32-53.11999 6.39999 0 551.67999-576.00003c9.20691-9.86269 16.15253-21.6144 20.35279-34.43617 4.20025-12.82178 5.55407-26.40525 3.96722-39.80384-0.14667-29.18983-11.6192-57.18283-32-78.07999l-52.47998-50.56c-9.71039-10.11205-21.32611-18.20222-34.17767-23.80419-12.85157-5.60196-26.68482-8.60493-40.70234-8.83581-11.87939-0.12119-23.66278 2.13824-34.65478 6.64496-10.992 4.50673-20.96997 11.16945-29.34522 19.59504l-550.40001 571.52002-0.64 1.28003z m694.4-424.32006l-54.39996 56.32001-88.96003-93.44 53.12-56.96c4.71924-4.31745 10.88379-6.71187 17.28003-6.71187 6.39624 0 12.56073 2.39442 17.27997 6.71187l53.11999 53.76c5.21851 5.55779 8.18402 12.85744 8.32001 20.48 0.25775 5.88312-1.81152 11.63113-5.76001 16l0 3.83999z m-596.48 433.28002l394.88002-411.52001 89.59998 94.08001-394.88 410.88-89.6-93.44z m-73.59999 167.67999l28.79999-109.44 76.8 81.28003-105.59999 28.15997z m750.72 205.44l-832.00001 0c-9.33562 0-18.28888-3.70855-24.89016-10.30981-6.60128-6.60126-10.30984-15.55451-10.30984-24.89014-0.08687-4.64642 0.76425-9.26263 2.50246-13.57257 1.73821-4.30993 4.3278-8.22503 7.61389-11.51111 3.28609-3.28607 7.20117-5.87567 11.51109-7.61389 4.30992-1.73822 8.92613-2.58929 13.57256-2.50244l832.00001 0c9.33564 0 18.28888 3.70856 24.89014 10.30981 6.60126 6.60126 10.30988 15.55457 10.30988 24.8902 0 9.33563-3.70862 18.28888-10.30988 24.89014-6.60126 6.60126-15.5545 10.30981-24.89014 10.30981z"
- fill="#323333" />
- </svg>
- <span>重命名</span>
- </div>
- <div class="popover-item delete" @click.stop.prevent="openDeleteModal(index)">
- <svg width="16" height="16" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path
- d="M768 292.57144c8.55957-0.00287 16.849 2.99677 23.42462 8.47647 6.57562 5.47971 11.02094 13.09232 12.56165 21.51209l0.58514 6.58285 0 511.99997c-0.00128 27.27368-10.16101 53.56922-28.49786 73.75867-18.33686 20.18945-43.53614 32.82532-70.68384 35.4436l-10.53259 0.51203-511.99997 0c-27.2737-0.00129-53.56917-10.16101-73.75865-28.49787-20.18946-18.33685-32.82537-43.53613-35.44365-70.68383l-0.51199-10.5326 0-511.99997c-0.01682-9.13891 3.38863-17.95294 9.54575-24.70642 6.15712-6.75348 14.61967-10.957 23.7212-11.78274 9.10154-0.82575 18.18243 1.78613 25.45445 7.32126 7.27202 5.53512 12.20813 13.59234 13.8363 22.58505l0.58515 6.58285 0 511.99997c-0.00286 8.55957 2.99676 16.84906 8.47647 23.42468 5.4797 6.57563 13.09234 11.02088 21.51211 12.56159l6.58286 0.58514 511.99997 0c8.55957 0.00287 16.849-2.9967 23.42462-8.47644 6.57562-5.47973 11.02093-13.09228 12.56164-21.51208l0.58515-6.58289 0-511.99997c0-9.69934 3.85309-19.00143 10.71155-25.85989 6.85846-6.85846 16.16058-10.71152 25.85992-10.71152z m-438.85715 73.14285c9.69934 0 19.00147 3.85306 25.85993 10.71152 6.85846 6.85846 10.71151 16.16055 10.71151 25.8599l0 292.57141c0.0008 4.80316-0.94458 9.55938-2.7821 13.99713-1.83752 4.43774-4.53122 8.47009-7.92728 11.8667-3.39605 3.3966-7.42794 6.091-11.86538 7.92926-4.43744 1.83825-9.19355 2.78436-13.99668 2.78436-4.80313 0-9.55923-0.94611-13.99667-2.78436-4.43744-1.83826-8.4693-4.53266-11.86536-7.92926-3.39605-3.39661-6.08975-7.42896-7.92727-11.8667-1.83753-4.43775-2.7829-9.19397-2.78211-13.99713l0-292.57141c0-9.69935 3.85306-19.00144 10.71152-25.8599 6.85846-6.85846 16.16055-10.71152 25.85989-10.71152z m219.42856 0c9.69934 0 19.00147 3.85306 25.85993 10.71152 6.85845 6.85846 10.71148 16.16055 10.71148 25.8599l0 292.57141c0.0008 4.80316-0.94458 9.55938-2.7821 13.99713-1.83753 4.43774-4.53119 8.47009-7.92725 11.8667-3.39606 3.3966-7.42798 6.091-11.86542 7.92926-4.43743 1.83825-9.19348 2.78436-13.99664 2.78436-4.80316 0-9.55926-0.94611-13.9967-2.78436-4.43744-1.83826-8.4693-4.53266-11.86536-7.92926-3.39606-3.39661-6.08978-7.42896-7.92731-11.8667-1.83752-4.43775-2.78283-9.19397-2.78204-13.99713l0-292.57141c0-9.69935 3.85303-19.00144 10.71149-25.8599 6.85846-6.85846 16.16058-10.71152 25.85992-10.71152z m-45.20224-365.71429c38.03427 0 73.14279 19.74857 93.11081 51.63885l5.04688 8.99658 42.78857 85.65028 196.82739 0c9.69934 0 19.00147 3.85304 25.85993 10.71151 6.85846 6.85848 10.71148 16.16059 10.71148 25.85993 0 9.69934-3.85302 19.00142-10.71148 25.85989-6.85846 6.85848-16.16059 10.71155-25.85993 10.71155l-804.57139 0c-9.69934 0-19.00144-3.85307-25.85991-10.71155-6.85847 0-10.71152-16.16055-10.71152-25.85989 0-9.69934 3.85304-19.00145 10.71152-25.85993 6.85847-6.85847 16.16056-10.71151 25.85991-10.71151l196.75428 0 42.86173-85.57715c8.32651-16.667 20.76025-30.93904 36.12915-41.47075 15.3689-10.53172 33.16663-16.97616 51.71542-18.72582l10.31314-0.51199 129.02402 0z m0 73.14286l-129.02402 0c-5.63437-0.00301-11.19326 1.29587-16.24292 3.79532-5.04965 2.49945-9.45352 6.13187-12.86795 10.61381l-3.65713 5.85143-26.40457 52.88229 247.44223 0-26.47766-52.95543c-2.52338-5.02719-6.17237-9.40457-10.66315-12.7917-4.49078-3.38713-9.70233-5.69271-15.2294-6.73744l-6.87543-0.65828z"
- fill="#f97c7c" />
- </svg>
- <span>删除</span>
- </div>
- </div>
- </button>
- </div>
- </div>
- </div>
- <AccountInfo @logout="handleAccountLogout" />
- </aside>
- <main class="main-content" :class="{ 'with-right-panel': rightPanelWidth > 0 }"
- @dragenter.prevent="handleMainDragEnter" @dragover.prevent="handleMainDragOver"
- @dragleave.prevent="handleMainDragLeave" @drop.prevent="handleMainDrop">
- <Transition name="drop-overlay-fade">
- <div v-if="isMainContentDropActive" class="main-content-drop-overlay" aria-hidden="true">
- <div class="main-content-drop-card">
- <div class="main-content-drop-icon" aria-hidden="true">
- <svg width="52" height="38" viewBox="0 0 52 38" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path
- d="M12.8333 37.3333C9.29444 37.3333 6.27083 36.1083 3.7625 33.6583C1.25417 31.2083 0 28.2139 0 24.675C0 21.6417 0.913889 18.9389 2.74167 16.5667C4.56944 14.1944 6.96111 12.6778 9.91667 12.0167C10.8889 8.43889 12.8333 5.54167 15.75 3.325C18.6667 1.10833 21.9722 0 25.6667 0C30.2167 0 34.0764 1.58472 37.2458 4.75417C40.4153 7.92361 42 11.7833 42 16.3333C44.6833 16.6444 46.9097 17.8014 48.6792 19.8042C50.4486 21.8069 51.3333 24.15 51.3333 26.8333C51.3333 29.75 50.3125 32.2292 48.2708 34.2708C46.2292 36.3125 43.75 37.3333 40.8333 37.3333H28C26.7167 37.3333 25.6181 36.8764 24.7042 35.9625C23.7903 35.0486 23.3333 33.95 23.3333 32.6667V20.65L19.6 24.2667L16.3333 21L25.6667 11.6667L35 21L31.7333 24.2667L28 20.65V32.6667H40.8333C42.4667 32.6667 43.8472 32.1028 44.975 30.975C46.1028 29.8472 46.6667 28.4667 46.6667 26.8333C46.6667 25.2 46.1028 23.8194 44.975 22.6917C43.8472 21.5639 42.4667 21 40.8333 21H37.3333V16.3333C37.3333 13.1056 36.1958 10.3542 33.9208 8.07917C31.6458 5.80417 28.8944 4.66667 25.6667 4.66667C22.4389 4.66667 19.6875 5.80417 17.4125 8.07917C15.1375 10.3542 14 13.1056 14 16.3333H12.8333C10.5778 16.3333 8.65278 17.1306 7.05833 18.725C5.46389 20.3194 4.66667 22.2444 4.66667 24.5C4.66667 26.7556 5.46389 28.6806 7.05833 30.275C8.65278 31.8694 10.5778 32.6667 12.8333 32.6667H18.6667V37.3333H12.8333Z"
- fill="#4F46E5" />
- </svg>
- </div>
- <div class="main-content-drop-title">将文件或图片拖拽至此处</div>
- <div class="main-content-drop-subtitle">支持 .jpg / .jpeg / .png / .webp / .pdf / .doc / .docx / .txt / .md
- ,单个文件不超过 50MB</div>
- </div>
- </div>
- </Transition>
- <ParticleBackground v-if="!isChatting" mouse-target-selector=".ba-pc-container" />
- <header v-if="isChatting" class="top-app-bar">
- <div class="top-app-bar-inner">
- <div class="top-app-bar-copy">
- <div class="top-app-bar-title">{{ currentSessionTitle }}</div>
- </div>
- <div class="top-app-bar-status">
- <span class="status-dot"></span>
- 正在服务
- </div>
- </div>
- </header>
- <section class="canvas-area" :class="{ 'chatting-canvas': isChatting }">
- <template v-if="!isChatting">
- <div class="hero-shell">
- <div class="overlay-border">
- <div class="dot"></div>
- <div class="overlay-text">青浦区营商助手 V2.0 在线</div>
- </div>
- <div class="welcome-text">
- <div class="heading">你好,有什么我可以帮您的?</div>
- <div class="desc">我是您的青浦区营商智能助手,可以为您提供惠企政策匹配以及出海、人才、科创、金融、法治方面的办事指引和咨询服务</div>
- </div>
- <BusinessAssistantComposer
- class="home-input"
- :modelValue="inputText"
- :uploaded-files="uploadedFiles"
- :is-generating="currentSessionGenerating"
- :is-deep-think="isDeepThink"
- :is-voice-input-mode="isVoiceInputMode"
- :is-voice-input-starting="false"
- :suggestions="suggestions"
- suggestion-align="center"
- variant="desktop"
- :show-mock-stream-test-button="isMockStreamTestButtonVisible"
- @update:modelValue="inputText = $event"
- @update:isDeepThink="isDeepThink = $event"
- @send="sendMessage"
- @stop="handleStopGenerate"
- @imageUpload="triggerImageUpload"
- @fileUpload="triggerFileUpload"
- @voiceToggle="toggleVoiceInput"
- @removeFile="removeUploadedFile"
- @suggestionClick="(text) => { inputText = text; sendMessage(); }"
- @paste="handleTextareaPaste"
- @testReplay="sendChatStreamReplayTestMessage"
- @testTypewriter="sendTypewriterTestMessage"
- @testScopeMerge="sendScopeMergeStreamTestMessage"
- @testInput="sendDocumentLineStreamTestMessage"
- @testMockStream="sendChatStreamReplayTestMessage"
- @testRefLinks="sendRefLinksStreamTestMessage"
- @testQuestionCard="sendQuestionCardTestMessage"
- />
- <div class="bento-features">
- <article class="bento-card bento-card-blue">
- <div class="bento-icon">
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
- aria-hidden="true">
- <path fill="currentColor"
- d="M18 8l-1.25-2.75-2.75-1.25 2.75-1.25 1.25-2.75 1.25 2.75 2.75 1.25-2.75 1.25-1.25 2.75 0 0m0 14l-1.25-2.75-2.75-1.25 2.75-1.25 1.25-2.75 1.25 2.75 2.75 1.25-2.75 1.25-1.25 2.75 0 0m-10-3l-2.5-5.5-5.5-2.5 5.5-2.5 2.5-5.5 2.5 5.5 5.5 2.5-5.5 2.5-2.5 5.5 0 0m0-4.85l1-2.15 2.15-1-2.15-1-1-2.15-1 2.15-2.15 1 2.15 1 1 2.15 0 0m0-3.15l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" />
- </svg>
- </div>
- <div class="bento-content">
- <div class="bento-title">政策精准匹配</div>
- <div class="bento-desc">基于最新的 AI 模型,深度解析青浦区各项惠企政策,为用户提供现场级的精准匹配与申报建议。</div>
- </div>
- </article>
- <article class="bento-card bento-card-amber">
- <div class="bento-icon">
- <svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg"
- aria-hidden="true">
- <path fill="currentColor"
- d="M8 20c-2.31667-0.58333-4.22917-1.9125-5.7375-3.9875-1.50833-2.075-2.2625-4.37917-2.2625-6.9125l0-6.1 8-3 8 3 0 6.1c0 2.53333-0.75417 4.8375-2.2625 6.9125-1.50833 2.075-3.42083 3.40416-5.7375 3.9875l0 0m0-2.1c1.61667-0.5 2.96667-1.4875 4.05-2.9625 1.08333-1.475 1.71667-3.12083 1.9-4.9375l-5.95 0 0-7.875-6 2.25 0 4.725c0 0.18333 0 0.33333 0 0.45 0 0.11667 0.01667 0.26667 0.05 0.45l5.95 0 0 7.9 0 0" />
- </svg>
- </div>
- <div class="bento-content">
- <div class="bento-title">营商环境分析</div>
- <div class="bento-desc">多维度剖析区域营商环境,实时追踪行业动向,为企业投资决策提供专业的数据支撑。</div>
- </div>
- </article>
- <article class="bento-card bento-card-rose">
- <div class="bento-icon">
- <svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg"
- aria-hidden="true">
- <path fill="currentColor"
- d="M6.6 14.6l1.4-1.45-2.15-2.15 2.15-2.15-1.4-1.45-3.6 3.6 3.6 3.6 0 0m4.8 0l3.6-3.6-3.6-3.6-1.4 1.45 2.15 2.15-2.15 2.15 1.4 1.45 0 0m-9.4 5.4c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-14c0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875l4.2 0c0.21667-0.6 0.57917-1.08333 1.0875-1.45 0.50833-0.36667 1.07917-0.55 1.7125-0.55 0.63333 0 1.20417 0.18333 1.7125 0.55 0.50833 0.36667 0.87083 0.85 1.0875 1.45l4.2 0c0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125l0 14c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-14 0 0 0m0-2l14 0 0 0 0 0 0-14 0 0 0 0-14 0 0 0 0 0 0 14 0 0 0 0 0 0m7-14.75c0.21667 0 0.39583-0.07083 0.5375-0.2125 0.14167-0.14167 0.2125-0.32083 0.2125-0.5375 0-0.21667-0.07083-0.39583-0.2125-0.5375-0.14167-0.14167-0.32083-0.2125-0.5375-0.2125-0.21667 0-0.39583 0.07083-0.5375 0.2125-0.14167 0.14167-0.2125 0.32083-0.2125 0.5375 0 0.21667 0.07083 0.39583 0.2125 0.5375 0.14167 0.14167 0.32083 0.2125 0.5375 0.2125l0 0m-7 14.75l0 0 0 0 0-14 0 0 0 0 0 0 0 0 0 0 0 14 0 0 0 0 0 0 0 0" />
- </svg>
- </div>
- <div class="bento-content">
- <div class="bento-title">惠企办事指引</div>
- <div class="bento-desc">提供办事指南、窗口导办等咨询答疑服务,助力企业高效对接青浦政务服务资源。</div>
- </div>
- </article>
- </div>
- </div>
- </template>
- <template v-else>
- <div class="chat-layout">
- <div class="chat-messages" ref="chatMessagesRef">
- <ScrollList v-if="currentSession" :data="currentSession.messages" ref="scrollListRef" :height="listHeight"
- :maxHeight="listHeight" :topPadding="8" @scrollPositionChange="handleBusinessScrollPositionChange">
- <template #item="{ data, isLast }">
- <div class="message-row" :ref="(el) => setMessageRowRef(data.id, el)"
- :class="[data.role, { 'is-last': isLast, 'is-scope-exiting': isScopeExiting(data.id) }]"
- :style="getMessageRowStyle(data.id, isLast, data.role)">
- <div v-if="data.role === 'ai'" class="ai-avatar">
- <div class="ai-avatar-bg"></div>
- </div>
- <BusinessRecord :record="data" :state="data.state" :content="data.content" :isLastRecord="isLast"
- :sessionId="currentSession?.id" :sessionViewedBefore="currentSessionVisitedBefore"
- enableFeedback
- @refreshScroll="handleBusinessRecordRefreshScroll"
- @scopeExitStateChange="handleScopeExitStateChange"
- @scopeHeightLockChange="handleScopeHeightLockChange"
- @typewriterStateChange="handleBusinessRecordTypewriterStateChange" />
- <div v-if="data.role === 'user'" class="user-avatar">
- <div class="user-avatar-bg">
- <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
- <rect width="32" height="32" rx="16" fill="#DCE2F7" />
- <path
- d="M15.9992 15.8473C17.1067 15.8473 18.1687 15.4074 18.9518 14.6243C19.7349 13.8412 20.1748 12.7791 20.1748 11.6717C20.1748 10.5643 19.7349 9.50218 18.9518 8.7191C18.1687 7.93602 17.1067 7.49609 15.9992 7.49609C14.8918 7.49609 13.8297 7.93602 13.0466 8.7191C12.2635 9.50218 11.8236 10.5643 11.8236 11.6717C11.8236 12.7791 12.2635 13.8412 13.0466 14.6243C13.8297 15.4074 14.8918 15.8473 15.9992 15.8473ZM18.1992 16.8065H13.7992C12.0488 16.8065 10.3701 17.5019 9.13231 18.7396C7.89457 19.9773 7.19922 21.6561 7.19922 23.4065C7.19922 23.6982 7.31511 23.978 7.5214 24.1843C7.72769 24.3906 8.00748 24.5065 8.29922 24.5065H23.6992C23.991 24.5065 24.2707 24.3906 24.477 24.1843C24.6833 23.978 24.7992 23.6982 24.7992 23.4065C24.7992 21.6561 24.1039 19.9773 22.8661 18.7396C21.6284 17.5019 19.9496 16.8065 18.1992 16.8065Z"
- fill="#4F46E5" />
- </svg>
- </div>
- </div>
- </div>
- </template>
- </ScrollList>
- </div>
- <div class="chat-input-wrapper">
- <BusinessAssistantComposer
- class="chat-composer-shell"
- :modelValue="inputText"
- :uploaded-files="uploadedFiles"
- :is-generating="currentSessionGenerating"
- :is-deep-think="isDeepThink"
- :is-voice-input-mode="isVoiceInputMode"
- :is-voice-input-starting="false"
- :suggestions="currentSessionSuggestions"
- suggestion-align="start"
- variant="desktop"
- :show-mock-stream-test-button="isMockStreamTestButtonVisible"
- @update:modelValue="inputText = $event"
- @update:isDeepThink="isDeepThink = $event"
- @send="sendMessage"
- @stop="handleStopGenerate"
- @imageUpload="triggerImageUpload"
- @fileUpload="triggerFileUpload"
- @voiceToggle="toggleVoiceInput"
- @removeFile="removeUploadedFile"
- @suggestionClick="(text) => { inputText = text; sendMessage(); }"
- @paste="handleTextareaPaste"
- @testReplay="sendChatStreamReplayTestMessage"
- @testTypewriter="sendTypewriterTestMessage"
- @testScopeMerge="sendScopeMergeStreamTestMessage"
- @testInput="sendDocumentLineStreamTestMessage"
- @testMockStream="sendChatStreamReplayTestMessage"
- @testRefLinks="sendRefLinksStreamTestMessage"
- @testQuestionCard="sendQuestionCardTestMessage"
- />
- <div class="ai-disclaimer">所有内容均由 AI 生成,仅供参考</div>
- </div>
- </div>
- </template>
- </section>
- </main>
- <Teleport to="body">
- <div v-if="activeTooltipId && activeTooltipAnchor" class="pc-tooltip-portal" aria-hidden="true">
- <div class="custom-tooltip custom-tooltip--portal" :style="tooltipStyle">
- {{ tooltipCopyMap[activeTooltipId] }}
- </div>
- </div>
- </Teleport>
- <Teleport to="body">
- <div v-if="isRenameModalOpen" class="rename-modal-overlay">
- <div class="rename-modal">
- <div class="rename-modal-header">
- <div class="rename-modal-title">编辑对话名称</div>
- <button class="rename-close-btn" @click="closeRenameModal">
- <svg width="16" height="16" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path
- d="M70.49379 10.20875l2.34666 2.15466 289.92002 289.94134 279.25332-279.25334c7.7948-7.87058 18.34504-12.40114 29.4198-12.63368 11.07477-0.23254 21.80591 3.85116 29.9242 11.38761 8.11828 7.53644 12.9873 17.93479 13.57751 28.99627 0.59021 11.06148-3.14465 21.91892-10.41485 30.27648l-2.13337 2.34666-279.29592 279.21069 279.27462 279.25329c7.87873 7.7909 12.41761 18.34119 12.65638 29.41889 0.23877 11.07769-3.84119 21.81384-11.37684 29.93701-7.53564 8.12317-17.93579 12.99621-29.00024 13.58826-11.06445 0.59204-21.92517-3.14344-30.28461-10.41614l-2.34668-2.17603-279.25332-279.25329-289.92002 289.96264c-7.80624 7.80823-18.33541 12.28638-29.37458 12.49317-11.03917 0.20678-21.72869-3.87384-29.8219-11.38422-8.09321-7.51037-12.95989-17.86566-13.57718-28.88953-0.61729-11.02386 3.06286-21.85772 10.267-30.22473l2.15468-2.34668 289.94134-289.96264-289.92001-289.92002c-7.75343-7.81585-12.18563-18.3262-12.37007-29.33388-0.18444-11.00768 3.89315-21.66061 11.38037-29.73183 7.48723-8.07122 17.80451-12.93594 28.79504-13.57721 10.99054-0.64127 21.80355 2.99055 30.17865 10.13625z"
- fill="#111111" />
- </svg>
- </button>
- </div>
- <div class="rename-modal-body">
- <input type="text" class="rename-input" v-model="renameInput" placeholder="输入名称" />
- </div>
- <div class="rename-modal-footer">
- <button class="rename-btn-cancel" @click="closeRenameModal">取消</button>
- <button class="rename-btn-confirm" @click="confirmRename">确定</button>
- </div>
- </div>
- </div>
- <div v-if="isDeleteModalOpen" class="rename-modal-overlay">
- <div class="rename-modal">
- <div class="rename-modal-header">
- <div class="rename-modal-title">提示</div>
- <button class="rename-close-btn" @click="closeDeleteModal">
- <svg width="16" height="16" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path
- d="M70.49379 10.20875l2.34666 2.15466 289.92002 289.94134 279.25332-279.25334c7.7948-7.87058 18.34504-12.40114 29.4198-12.63368 11.07477-0.23254 21.80591 3.85116 29.9242 11.38761 8.11828 7.53644 12.9873 17.93479 13.57751 28.99627 0.59021 11.06148-3.14465 21.91892-10.41485 30.27648l-2.13337 2.34666-279.29592 279.21069 279.27462 279.25329c7.87873 7.7909 12.41761 18.34119 12.65638 29.41889 0.23877 11.07769-3.84119 21.81384-11.37684 29.93701-7.53564 8.12317-17.93579 12.99621-29.00024 13.58826-11.06445 0.59204-21.92517-3.14344-30.28461-10.41614l-2.34668-2.17603-279.25332-279.25329-289.92002 289.96264c-7.80624 7.80823-18.33541 12.28638-29.37458 12.49317-11.03917 0.20678-21.72869-3.87384-29.8219-11.38422-8.09321-7.51037-12.95989-17.86566-13.57718-28.88953-0.61729-11.02386 3.06286-21.85772 10.267-30.22473l2.15468-2.34668 289.94134-289.96264-289.92001-289.92002c-7.75343-7.81585-12.18563-18.3262-12.37007-29.33388-0.18444-11.00768 3.89315-21.66061 11.38037-29.73183 7.48723-8.07122 17.80451-12.93594 28.79504-13.57721 10.99054-0.64127 21.80355 2.99055 30.17865 10.13625z"
- fill="#111111" />
- </svg>
- </button>
- </div>
- <div class="rename-modal-body" style="padding-top: 0; padding-bottom: 32px;">
- <div style="font-size: 15px; color: #464555;">确认删除该会话记录吗?</div>
- </div>
- <div class="rename-modal-footer">
- <button class="rename-btn-cancel" @click="closeDeleteModal">取消</button>
- <button class="rename-btn-confirm" @click="confirmDeleteSession" style="background: #f97c7c;">删除</button>
- </div>
- </div>
- </div>
- </Teleport>
- <input type="file" ref="imageInputRef" accept="image/jpeg,image/png,image/jpg,image/webp" style="display: none"
- @change="handleImageSelected" multiple />
- <input type="file" ref="fileInputRef" accept=".pdf,.doc,.docx,.txt,.md" style="display: none"
- @change="handleFileSelected" multiple />
- </div>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, onBeforeUnmount, nextTick, computed, provide, watch, reactive } from 'vue';
- import { useResizeObserver } from '@vueuse/core';
- import BusinessRecord from "./Chat/BusinessRecord.vue";
- import Toast from "./Common/Toast.vue";
- import ScrollList from './Common/ScrollList.vue';
- import ASR from '@/three-libs/asr/index';
- import ParticleBackground from "./Common/ParticleBackground.vue";
- import { useBusinessAssistantChat } from './business-assistant/useBusinessAssistantChat';
- import { useBusinessAssistantUpload } from './business-assistant/useBusinessAssistantUpload';
- import {
- maybeStopAutoScrollAtLastAiRowTop,
- } from './business-assistant/auto-scroll-top-stop';
- import { scrollBusinessAssistantListToBottom } from './business-assistant/scroll-list-bottom';
- import AccountInfo from './Chat/AccountInfo.vue';
- import { useEnterpriseAuth } from '@/components/useEnterpriseAuth';
- import { createModalMutex, MODAL_MUTEX_KEY } from './business-assistant/modal-mutex';
- provide(MODAL_MUTEX_KEY, createModalMutex());
- provide("formOptionsControl", {
- setFormOptions: () => { }
- });
- provide("speechControl", {
- stopSpeech: () => { },
- handleStopSpeech: () => { },
- handleEndSpeech: () => { }
- });
- const imageInputRef = ref<HTMLInputElement | null>(null);
- const fileInputRef = ref<HTMLInputElement | null>(null);
- const {
- uploadedFiles,
- triggerImageUpload,
- triggerFileUpload,
- handleFiles,
- handleImageSelected,
- handleFileSelected,
- removeUploadedFile,
- isSupportedImageFile,
- isSupportedDropFile,
- consumeSuccessfulFileTransmission,
- setUploadDraftKey,
- clearUploadDraft,
- } = useBusinessAssistantUpload({ imageInputRef, fileInputRef });
- const {
- isLoggedIn,
- creditCode,
- initAuth,
- } = useEnterpriseAuth();
- const {
- suggestions,
- chatHistory,
- currentSessionIndex,
- currentSessionVisitedBefore,
- currentSession,
- currentSessionTitle,
- currentSessionSuggestions,
- inputText,
- isGenerating,
- currentSessionGenerating,
- toastMessage,
- stopAllTasks,
- isDeepThink,
- isChatting,
- loadHistory,
- sendMessage,
- sendChatStreamReplayTestMessage,
- sendTypewriterTestMessage,
- sendScopeMergeStreamTestMessage,
- sendDocumentLineStreamTestMessage,
- sendRefLinksStreamTestMessage,
- sendQuestionCardTestMessage,
- handleStopGenerate,
- startNewChat: startSharedNewChat,
- switchSession: switchSharedSession,
- renameSessionAt,
- deleteSessionAt,
- mergeRemoteSessions,
- loadRemoteSessionRecords,
- } = useBusinessAssistantChat({
- // 新协调器自行读取 VITE_CHAT_API,此参数仅保留签名兼容
- apiBaseUrl: import.meta.env.VITE_API || "",
- beforeSend: async () => {
- if (isVoiceInputMode.value) {
- await stopVoiceInput();
- }
- },
- afterSend: () => scrollToBottom(true),
- onMessageUpdated: () => scrollToBottom(),
- getInputFileTransmission: consumeSuccessfulFileTransmission,
- });
- watch(
- () => currentSession.value?.id || null,
- (sessionId) => {
- setUploadDraftKey(sessionId);
- },
- { immediate: true },
- );
- const startNewChat = () => {
- setUploadDraftKey(null);
- clearUploadDraft(null);
- startSharedNewChat();
- };
- const hoveredSessionIndex = ref<number | null>(null);
- const activePopoverIndex = ref<number | null>(null);
- const activeTooltipId = ref<'image' | 'attachment' | null>(null);
- const activeTooltipAnchor = ref<{ left: number; top: number } | null>(null);
- const tooltipCopyMap = {
- image: '上传图片,支持JPG、JPEG、PNG等格式,单个图片不超过20MB',
- attachment: '上传附件,支持PDF、DOCX、TXT等格式,单个附件不超过50MB',
- } as const;
- const tooltipStyle = computed(() => {
- const anchor = activeTooltipAnchor.value;
- if (!anchor) {
- return {};
- }
- const left = Math.min(Math.max(anchor.left, 12), Math.max(12, window.innerWidth - 12));
- return {
- left: `${left}px`,
- top: `${anchor.top}px`,
- };
- });
- const openPopover = (index: number) => {
- if (activePopoverIndex.value === index) {
- activePopoverIndex.value = null;
- } else {
- activePopoverIndex.value = index;
- }
- };
- const closePopover = () => {
- activePopoverIndex.value = null;
- };
- onMounted(() => {
- document.addEventListener('click', closePopover);
- window.addEventListener('dragend', stopMainContentDrag);
- window.addEventListener('drop', stopMainContentDrag);
- });
- onBeforeUnmount(() => {
- clearLastAiRowTypingReleaseTimer();
- document.removeEventListener('click', closePopover);
- window.removeEventListener('dragend', stopMainContentDrag);
- window.removeEventListener('drop', stopMainContentDrag);
- });
- const isRenameModalOpen = ref(false);
- const renameInput = ref('');
- const renameSessionIndex = ref<number | null>(null);
- const isMainContentDropActive = ref(false);
- const mainContentDragDepth = ref(0);
- const stopMainContentDrag = () => {
- mainContentDragDepth.value = 0;
- isMainContentDropActive.value = false;
- };
- const handleMainDragEnter = (event: DragEvent) => {
- if (!event.dataTransfer || !Array.from(event.dataTransfer.types || []).includes('Files')) {
- return;
- }
- mainContentDragDepth.value += 1;
- isMainContentDropActive.value = true;
- };
- const handleMainDragOver = (event: DragEvent) => {
- if (!event.dataTransfer || !Array.from(event.dataTransfer.types || []).includes('Files')) {
- return;
- }
- event.dataTransfer.dropEffect = 'copy';
- isMainContentDropActive.value = true;
- };
- const handleMainDragLeave = (event: DragEvent) => {
- if (!event.dataTransfer || !Array.from(event.dataTransfer.types || []).includes('Files')) {
- return;
- }
- mainContentDragDepth.value = Math.max(0, mainContentDragDepth.value - 1);
- if (mainContentDragDepth.value === 0) {
- isMainContentDropActive.value = false;
- }
- };
- const handleMainDrop = (event: DragEvent) => {
- stopMainContentDrag();
- const fileList = Array.from(event.dataTransfer?.files || []);
- if (fileList.length === 0) return;
- const supportedFiles = fileList.filter(isSupportedDropFile);
- if (supportedFiles.length === 0) {
- alert('仅支持拖拽上传图片、PDF、Word、TXT 和 Markdown 文件');
- return;
- }
- const imageFiles = supportedFiles.filter(isSupportedImageFile);
- const attachmentFiles = supportedFiles.filter((file) => !isSupportedImageFile(file));
- if (imageFiles.length > 0) {
- void handleFiles(imageFiles, true);
- }
- if (attachmentFiles.length > 0) {
- void handleFiles(attachmentFiles, false);
- }
- };
- const getClipboardImageExtension = (file: File) => {
- const extension = file.type.split('/')[1]?.toLowerCase() || 'png';
- if (extension === 'jpeg') {
- return 'jpg';
- }
- return extension;
- };
- const normalizeClipboardImageFile = (file: File, index: number) => {
- const hasExtension = /\.[a-z0-9]+$/i.test(file.name || '');
- if (file.name && hasExtension) {
- return file;
- }
- return new File([file], `clipboard-image-${Date.now()}-${index}.${getClipboardImageExtension(file)}`, {
- type: file.type || 'image/png',
- lastModified: file.lastModified || Date.now(),
- });
- };
- const handleTextareaPaste = (event: ClipboardEvent) => {
- const clipboardData = event.clipboardData;
- if (!clipboardData) {
- return;
- }
- const imageFiles = Array.from(clipboardData.items || [])
- .filter((item) => item.kind === 'file' && item.type.startsWith('image/'))
- .map((item) => item.getAsFile())
- .filter((file): file is File => Boolean(file))
- .map(normalizeClipboardImageFile);
- if (imageFiles.length === 0) {
- return;
- }
- event.preventDefault();
- handleFiles(imageFiles, true);
- };
- const openRenameModal = (index: number) => {
- activePopoverIndex.value = null;
- renameSessionIndex.value = index;
- renameInput.value = chatHistory.value[index].title || '';
- isRenameModalOpen.value = true;
- };
- const closeRenameModal = () => {
- isRenameModalOpen.value = false;
- };
- const confirmRename = () => {
- if (renameSessionIndex.value !== null) {
- renameSessionAt(renameSessionIndex.value, renameInput.value);
- }
- isRenameModalOpen.value = false;
- };
- const deleteSessionIndex = ref<number | null>(null);
- const isDeleteModalOpen = ref(false);
- const openDeleteModal = (index: number) => {
- activePopoverIndex.value = null;
- deleteSessionIndex.value = index;
- isDeleteModalOpen.value = true;
- };
- const closeDeleteModal = () => {
- isDeleteModalOpen.value = false;
- };
- const confirmDeleteSession = () => {
- if (deleteSessionIndex.value !== null) {
- deleteSessionAt(deleteSessionIndex.value);
- }
- isDeleteModalOpen.value = false;
- };
- const scrollListRef = ref<any>(null);
- const listHeight = ref(400);
- const scopeExitStates = reactive<Record<string, { active: boolean }>>({});
- const chatMessagesRef = ref<HTMLElement | null>(null);
- const messageRowRefs = reactive<Record<string, HTMLElement | null>>({});
- const messageRowMinHeights = reactive<Record<string, number>>({});
- const BUSINESS_RECORD_BOTTOM_STICKY_DISTANCE = 24;
- const BUSINESS_RECORD_LARGE_HEIGHT_GROWTH = 80;
- const hasStoppedAtLastAiRowTop = ref(false);
- const lastAiRowTopConsumedRecordId = ref<string | null>(null);
- const lastAiRowTopSnapshot = ref<{ recordId: string; rowOffsetTop: number } | null>(null);
- const lastAiRowTypewriterStates = ref<Record<string, boolean>>({});
- let lastAiRowTypingReleaseTimer: number | null = null;
- const rightPanelWidth = ref(0);
- const handlePanelWidthChange = (e: Event) => {
- const customEvent = e as CustomEvent;
- rightPanelWidth.value = Number(customEvent.detail?.width || 0);
- };
- const setMessageRowRef = (id: string | number, el: HTMLElement | null) => {
- const key = String(id);
- if (el) {
- messageRowRefs[key] = el;
- } else {
- delete messageRowRefs[key];
- }
- };
- const setMessageRowMinHeight = (recordId: string | number, height: number | null | undefined) => {
- const key = String(recordId);
- if (!Number.isFinite(Number(height)) || Number(height) <= 0) {
- delete messageRowMinHeights[key];
- return;
- }
- messageRowMinHeights[key] = Math.ceil(Number(height));
- };
- const getMessageRowMinHeight = (recordId: string | number) => {
- const key = String(recordId);
- const height = messageRowMinHeights[key];
- return Number.isFinite(height) && height > 0 ? height : null;
- };
- const getMessageRowStyle = (recordId: string | number, isLast: boolean, role: string) => {
- if (!isLast || role !== 'ai') {
- return undefined;
- }
- const minHeight = getMessageRowMinHeight(recordId);
- if (!minHeight) {
- return undefined;
- }
- return {
- minHeight: `${minHeight}px`,
- };
- };
- const getLastAiMessage = () => {
- const messages = currentSession.value?.messages || [];
- for (let index = messages.length - 1; index >= 0; index -= 1) {
- const message = messages[index];
- if (message?.role === 'ai') {
- return message;
- }
- }
- return null;
- };
- const getRowScrollTop = (scrollElement: HTMLElement, rowElement: HTMLElement) => {
- const scrollRect = scrollElement.getBoundingClientRect();
- const rowRect = rowElement.getBoundingClientRect();
- return rowRect.top - scrollRect.top + scrollElement.scrollTop;
- };
- const isLastAiRowTyping = () => {
- const lastAiMessage = getLastAiMessage();
- if (!lastAiMessage) {
- return false;
- }
- return Boolean(lastAiRowTypewriterStates.value[String(lastAiMessage.id)]);
- };
- const clearLastAiRowTopSnapshot = () => {
- lastAiRowTopSnapshot.value = null;
- };
- const consumeLastAiRowTopForRecord = (recordId: string | number | null | undefined) => {
- if (recordId === undefined || recordId === null) {
- return;
- }
- lastAiRowTopConsumedRecordId.value = String(recordId);
- hasStoppedAtLastAiRowTop.value = true;
- };
- const clearLastAiRowTypingReleaseTimer = () => {
- if (lastAiRowTypingReleaseTimer !== null) {
- window.clearTimeout(lastAiRowTypingReleaseTimer);
- lastAiRowTypingReleaseTimer = null;
- }
- };
- const setLastAiRowTopPinState = (active: boolean) => {
- const scrollList = scrollListRef.value;
- if (scrollList && typeof scrollList.setAutoScrollTopPinned === "function") {
- scrollList.setAutoScrollTopPinned(active);
- }
- };
- const isLastAiRowTopPinned = () => {
- const scrollList = scrollListRef.value;
- if (!scrollList || typeof scrollList.isAutoScrollTopPinned !== "function") {
- return false;
- }
- return scrollList.isAutoScrollTopPinned();
- };
- const releaseLastAiRowTopPin = (reason: "typewriter-finished" | "session-switch" | "user-scroll") => {
- clearLastAiRowTypingReleaseTimer();
- setLastAiRowTopPinState(false);
- clearLastAiRowTopSnapshot();
- if (reason === "user-scroll") {
- return;
- }
- };
- const getOrCreateLastAiRowTopSnapshot = (recordId: string, rowOffsetTop: number) => {
- if (
- lastAiRowTopSnapshot.value?.recordId !== recordId ||
- !Number.isFinite(lastAiRowTopSnapshot.value?.rowOffsetTop)
- ) {
- lastAiRowTopSnapshot.value = {
- recordId,
- rowOffsetTop,
- };
- }
- return lastAiRowTopSnapshot.value;
- };
- const stopAutoScrollAtLastAiRowTopIfNeeded = () => {
- const scrollList = scrollListRef.value;
- if (scrollList && typeof scrollList.isAutoScrollTopPinSuppressedByUser === "function" && scrollList.isAutoScrollTopPinSuppressedByUser()) {
- return false;
- }
- if (!isLastAiRowTyping()) {
- clearLastAiRowTopSnapshot();
- if (isLastAiRowTopPinned()) {
- releaseLastAiRowTopPin("typewriter-finished");
- }
- return false;
- }
- if (!scrollList || typeof scrollList.getScrollElement !== "function") {
- return false;
- }
- const scrollElement = scrollList.getScrollElement();
- const lastAiMessage = getLastAiMessage();
- if (!scrollElement || !lastAiMessage) {
- return false;
- }
- const lastAiMessageId = String(lastAiMessage.id);
- if (lastAiRowTopConsumedRecordId.value === lastAiMessageId) {
- return false;
- }
- const rowElement = messageRowRefs[String(lastAiMessage.id)];
- if (!rowElement) {
- return false;
- }
- if (typeof scrollList.enableAutoScroll === "function" && !isLastAiRowTopPinned()) {
- scrollList.enableAutoScroll();
- }
- const isAutoScrollActive =
- typeof scrollList.isAutoScrollEnabled === "function" ? scrollList.isAutoScrollEnabled() : true;
- const rowScrollTop = getRowScrollTop(scrollElement, rowElement);
- const snapshot = getOrCreateLastAiRowTopSnapshot(String(lastAiMessage.id), rowScrollTop);
- const result = maybeStopAutoScrollAtLastAiRowTop({
- isLastAiRowTyping: true,
- isAutoScrollActive,
- hasStoppedAtLastAiRowTop: hasStoppedAtLastAiRowTop.value,
- scrollTop: scrollElement.scrollTop,
- rowOffsetTop: snapshot.rowOffsetTop,
- disableAutoScroll: () => {
- if (typeof scrollList.disableAutoScroll === "function") {
- scrollList.disableAutoScroll();
- }
- },
- });
- hasStoppedAtLastAiRowTop.value = result.hasStoppedAtLastAiRowTop;
- if (result.didStop) {
- consumeLastAiRowTopForRecord(lastAiMessage.id);
- setLastAiRowTopPinState(true);
- }
- if (result.didStop && result.nextScrollTop !== null) {
- if (typeof scrollList.scrollToPositionInstant === "function") {
- scrollList.scrollToPositionInstant(result.nextScrollTop);
- } else {
- scrollElement.scrollTop = result.nextScrollTop;
- }
- }
- return result.didStop;
- };
- const syncAutoScrollFollowState = () => {
- const scrollList = scrollListRef.value;
- if (!scrollList || typeof scrollList.getScrollElement !== "function") {
- return;
- }
- const scrollElement = scrollList.getScrollElement();
- const lastAiMessage = getLastAiMessage();
- if (!scrollElement || !lastAiMessage) {
- return;
- }
- const rowElement = messageRowRefs[String(lastAiMessage.id)];
- if (!rowElement) {
- return;
- }
- const distanceToBottom =
- typeof scrollList.getDistanceToBottom === "function" ? scrollList.getDistanceToBottom() : Number.POSITIVE_INFINITY;
- const isUserScrollSuppressed =
- Boolean(
- typeof scrollList.isAutoScrollTopPinSuppressedByUser === "function" &&
- scrollList.isAutoScrollTopPinSuppressedByUser()
- );
- if (isUserScrollSuppressed) {
- if (distanceToBottom < BUSINESS_RECORD_BOTTOM_STICKY_DISTANCE && typeof scrollList.enableAutoScroll === "function") {
- scrollList.enableAutoScroll();
- }
- return;
- }
- if (stopAutoScrollAtLastAiRowTopIfNeeded()) {
- return;
- }
- if (distanceToBottom < BUSINESS_RECORD_BOTTOM_STICKY_DISTANCE && typeof scrollList.enableAutoScroll === "function") {
- scrollList.enableAutoScroll();
- }
- };
- const isMockStreamTestButtonVisible = import.meta.env.MODE === 'development'
- const isVoiceInputMode = ref(false);
- const audioCanvasRef = ref<HTMLCanvasElement | null>(null);
- const homeAudioCanvasRef = ref<HTMLCanvasElement | null>(null);
- let asrInstance: ASR | null = null;
- let animationFrameId: number;
- let analyser: AnalyserNode | null = null;
- const drawWave = () => {
- if (!isVoiceInputMode.value || !analyser) return;
- const canvas = isChatting.value ? audioCanvasRef.value : homeAudioCanvasRef.value;
- if (!canvas) {
- animationFrameId = requestAnimationFrame(drawWave);
- return;
- }
- const ctx = canvas.getContext('2d');
- if (!ctx) return;
- const bufferLength = analyser.frequencyBinCount;
- const dataArray = new Uint8Array(bufferLength);
- analyser.getByteTimeDomainData(dataArray);
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.lineWidth = 2;
- ctx.strokeStyle = '#3525cd';
- ctx.beginPath();
- const sliceWidth = canvas.width * 1.0 / bufferLength;
- let x = 0;
- for (let i = 0; i < bufferLength; i++) {
- const v = dataArray[i] / 128.0;
- const y = v * canvas.height / 2;
- if (i === 0) {
- ctx.moveTo(x, y);
- } else {
- ctx.lineTo(x, y);
- }
- x += sliceWidth;
- }
- ctx.lineTo(canvas.width, canvas.height / 2);
- ctx.stroke();
- animationFrameId = requestAnimationFrame(drawWave);
- };
- const stopVoiceInput = async () => {
- if (!isVoiceInputMode.value) return;
- isVoiceInputMode.value = false;
- if (animationFrameId) {
- cancelAnimationFrame(animationFrameId);
- }
- if (analyser) {
- analyser.disconnect();
- analyser = null;
- }
- if (asrInstance) {
- try {
- const result = await asrInstance.transform();
- if (result && result.text) {
- inputText.value = result.text;
- }
- } catch (e) {
- console.error(e);
- }
- asrInstance.close();
- asrInstance.clear();
- asrInstance = null;
- }
- };
- const toggleVoiceInput = async () => {
- if (isVoiceInputMode.value) {
- await stopVoiceInput();
- return;
- }
- if (inputText.value.trim().length > 0) {
- const confirmClear = window.confirm("可能会清空之前的文字,确认要清空吗?");
- if (!confirmClear) {
- return;
- }
- inputText.value = "";
- }
- try {
- if (!asrInstance) {
- asrInstance = new ASR("ce6cb2020037ed20f8a09a4822e869f380a0de96", {
- audio: true,
- video: false,
- });
- }
- const success = await asrInstance.openMicroPhone();
- if (!success) return;
- isVoiceInputMode.value = true;
- asrInstance.addEventListener("transform", (arg: any) => {
- inputText.value = arg.transformText;
- });
- asrInstance.startRecord();
- await asrInstance.openSocket();
- if (asrInstance.audioContext && asrInstance.source) {
- analyser = asrInstance.audioContext.createAnalyser();
- analyser.fftSize = 256;
- asrInstance.source.connect(analyser);
- drawWave();
- }
- } catch (error) {
- console.error("Voice input error:", error);
- window.alert(error instanceof Error ? error.message : "语音输入初始化失败");
- await stopVoiceInput();
- }
- };
- useResizeObserver(chatMessagesRef, (entries) => {
- const entry = entries[0];
- if (entry) {
- listHeight.value = entry.contentRect.height;
- }
- });
- const handleAccountLogout = () => {
- // ⚠️ 必须先停掉全部生成任务:退出登录会切换 localStorage key,
- // 旧账户的任务继续写会把内容落到新身份下
- stopAllTasks();
- void scrollToBottom(true);
- loadHistory();
- startNewChat();
- };
- onMounted(() => {
- document.addEventListener("policyPanelWidthChange", handlePanelWidthChange);
- document.addEventListener("refLinksPanelWidthChange", handlePanelWidthChange);
- loadHistory();
- const fetchAndMergeRemoteSessions = async () => {
- const { fetchRemoteSessions } = await import('@/network/api/chat-sessions');
- const remoteSessions = await fetchRemoteSessions(creditCode.value);
- if (remoteSessions.length > 0) {
- mergeRemoteSessions(remoteSessions);
- }
- };
- initAuth().then(() => {
- if (isLoggedIn.value) {
- // Switched from temp to auth key - reload history
- loadHistory();
- void fetchAndMergeRemoteSessions();
- }
- });
- });
- onBeforeUnmount(() => {
- document.removeEventListener("policyPanelWidthChange", handlePanelWidthChange);
- document.removeEventListener("refLinksPanelWidthChange", handlePanelWidthChange);
- activeTooltipId.value = null;
- activeTooltipAnchor.value = null;
- releaseLastAiRowTopPin("session-switch");
- if (isVoiceInputMode.value) {
- stopVoiceInput();
- }
- });
- const scrollToBottom = async (force = false) => {
- await nextTick();
- if (scrollListRef.value) {
- if (force && typeof scrollListRef.value.enableAutoScroll === "function") {
- scrollListRef.value.enableAutoScroll();
- releaseLastAiRowTopPin("session-switch");
- }
- scrollListRef.value.scrollToBottom();
- }
- };
- const scrollToBottomAfterSessionSwitch = async () => {
- await scrollToBottom(true);
- await nextTick();
- scrollBusinessAssistantListToBottom(scrollListRef.value, "instant");
- };
- const switchSession = (index: number) => {
- const session = chatHistory.value[index];
- if (session && session.messages.length === 0 && isLoggedIn.value) {
- void loadRemoteSessionRecords(session.id).then(() => {
- switchSharedSession(index);
- void scrollToBottomAfterSessionSwitch();
- });
- return;
- }
- switchSharedSession(index);
- void scrollToBottomAfterSessionSwitch();
- };
- const getLastAiRowBodyHeight = () => {
- const lastAiMessage = getLastAiMessage();
- if (!lastAiMessage) {
- return 0;
- }
- const rowElement = messageRowRefs[String(lastAiMessage.id)];
- const bodyElement = rowElement?.querySelector(".business-record-body") as HTMLElement | null;
- return bodyElement ? Math.ceil(bodyElement.getBoundingClientRect().height) : 0;
- };
- const releaseScopeHeightLockIfReady = () => {
- const lastAiMessage = getLastAiMessage();
- if (!lastAiMessage) {
- return false;
- }
- const key = String(lastAiMessage.id);
- const lockHeight = messageRowMinHeights[key];
- if (!Number.isFinite(lockHeight) || lockHeight <= 0) {
- return false;
- }
- const bodyHeight = getLastAiRowBodyHeight();
- if (bodyHeight < lockHeight) {
- return false;
- }
- delete messageRowMinHeights[key];
- return true;
- };
- const handleBusinessRecordRefreshScroll = async (payload?: { heightGrowth?: number; scrollDelta?: number; onComplete?: () => void }) => {
- const scrollList = scrollListRef.value;
- if (scrollList && typeof scrollList.isAutoScrollTopPinned === "function" && scrollList.isAutoScrollTopPinned()) {
- payload?.onComplete?.();
- return;
- }
- if (stopAutoScrollAtLastAiRowTopIfNeeded()) {
- syncAutoScrollFollowState();
- payload?.onComplete?.();
- return;
- }
- const isUserScrollSuppressed =
- Boolean(scrollList && typeof scrollList.isAutoScrollTopPinSuppressedByUser === "function" && scrollList.isAutoScrollTopPinSuppressedByUser());
- if (scrollList && typeof scrollList.enableAutoScroll === "function" && !isLastAiRowTopPinned()) {
- const distanceToBottom =
- typeof scrollList.getDistanceToBottom === "function" ? scrollList.getDistanceToBottom() : Number.POSITIVE_INFINITY;
- if (isUserScrollSuppressed) {
- if (distanceToBottom < BUSINESS_RECORD_BOTTOM_STICKY_DISTANCE) {
- scrollList.enableAutoScroll();
- }
- payload?.onComplete?.();
- return;
- }
- scrollList.enableAutoScroll();
- payload?.onComplete?.();
- return;
- }
- const releasedLock = releaseScopeHeightLockIfReady();
- if (releasedLock) {
- syncAutoScrollFollowState();
- }
- const scrollDelta = Number(payload?.scrollDelta || 0);
- if (Number.isFinite(scrollDelta) && scrollDelta !== 0) {
- let scrollAnimationResult: unknown;
- if (scrollListRef.value && typeof scrollListRef.value.scrollByAnimated === "function") {
- scrollAnimationResult = scrollListRef.value.scrollByAnimated(scrollDelta, 300);
- } else if (scrollListRef.value && typeof scrollListRef.value.scrollBy === "function") {
- scrollListRef.value.scrollBy(scrollDelta);
- }
- if (scrollAnimationResult && typeof (scrollAnimationResult as Promise<unknown>).then === "function") {
- await scrollAnimationResult;
- }
- syncAutoScrollFollowState();
- payload?.onComplete?.();
- return;
- }
- await nextTick();
- if (!scrollListRef.value) {
- return;
- }
- if (!releasedLock && getLastAiMessage() && messageRowMinHeights[String(getLastAiMessage()?.id || "")]) {
- const lockHeight = messageRowMinHeights[String(getLastAiMessage()?.id || "")];
- const bodyHeight = getLastAiRowBodyHeight();
- if (bodyHeight < lockHeight && !isLastAiRowTyping()) {
- return;
- }
- }
- const heightGrowth = Number(payload?.heightGrowth || 0);
- const distanceAfterGrowth =
- typeof scrollListRef.value.getDistanceToBottom === "function"
- ? scrollListRef.value.getDistanceToBottom()
- : Number.POSITIVE_INFINITY;
- const distanceBeforeGrowth = Math.max(0, distanceAfterGrowth - heightGrowth);
- // 如果没有高度增长,我们只根据当前的实际距离决定如何滚动到底部
- if (!Number.isFinite(heightGrowth) || heightGrowth <= 0) {
- if (distanceAfterGrowth <= 24) {
- scrollBusinessAssistantListToBottom(scrollListRef.value, "instant");
- } else {
- scrollBusinessAssistantListToBottom(scrollListRef.value);
- }
- return;
- }
- if (
- distanceBeforeGrowth < BUSINESS_RECORD_BOTTOM_STICKY_DISTANCE ||
- heightGrowth > BUSINESS_RECORD_LARGE_HEIGHT_GROWTH
- ) {
- // 高度增加后,判断新增前是否贴近底部。
- // 如果现在距离底部极近 (<= 24px),则瞬间跳到底部,否则平滑滚动
- if (distanceAfterGrowth <= 24) {
- scrollBusinessAssistantListToBottom(scrollListRef.value, "instant");
- } else {
- scrollBusinessAssistantListToBottom(scrollListRef.value, "once");
- }
- }
- syncAutoScrollFollowState();
- };
- const handleBusinessScrollPositionChange = (payload?: { isTrusted?: boolean; isProgrammaticScroll?: boolean }) => {
- if (payload?.isTrusted && !payload?.isProgrammaticScroll) {
- consumeLastAiRowTopForRecord(getLastAiMessage()?.id ?? null);
- releaseLastAiRowTopPin("user-scroll");
- }
- if (stopAutoScrollAtLastAiRowTopIfNeeded()) {
- syncAutoScrollFollowState();
- return;
- }
- syncAutoScrollFollowState();
- };
- const handleScopeExitStateChange = (payload?: { recordId?: string | number; active?: boolean }) => {
- const recordId = payload?.recordId;
- if (recordId === undefined || recordId === null) {
- return;
- }
- const key = String(recordId);
- if (payload?.active) {
- scopeExitStates[key] = { active: true };
- } else {
- delete scopeExitStates[key];
- }
- };
- const handleBusinessRecordTypewriterStateChange = (payload?: { recordId?: string | number; isTypingActive?: boolean }) => {
- const recordId = payload?.recordId;
- if (recordId === undefined || recordId === null) {
- return;
- }
- const key = String(recordId);
- if (payload?.isTypingActive) {
- if (lastAiRowTopConsumedRecordId.value && lastAiRowTopConsumedRecordId.value !== key) {
- lastAiRowTopConsumedRecordId.value = null;
- hasStoppedAtLastAiRowTop.value = false;
- clearLastAiRowTopSnapshot();
- }
- clearLastAiRowTypingReleaseTimer();
- lastAiRowTypewriterStates.value[key] = true;
- syncAutoScrollFollowState();
- return;
- }
- clearLastAiRowTypingReleaseTimer();
- lastAiRowTypingReleaseTimer = window.setTimeout(() => {
- if (lastAiRowTypewriterStates.value[key]) {
- return;
- }
- delete lastAiRowTypewriterStates.value[key];
- releaseLastAiRowTopPin("typewriter-finished");
- syncAutoScrollFollowState();
- }, 180);
- };
- const handleScopeHeightLockChange = (payload?: { recordId?: string | number; active?: boolean; height?: number }) => {
- const recordId = payload?.recordId;
- if (recordId === undefined || recordId === null) {
- return;
- }
- if (payload?.active) {
- Object.keys(messageRowMinHeights).forEach((key) => {
- delete messageRowMinHeights[key];
- });
- setMessageRowMinHeight(recordId, payload.height);
- return;
- }
- setMessageRowMinHeight(recordId, null);
- };
- const isScopeExiting = (recordId: string | number) => {
- return !!scopeExitStates[String(recordId)]?.active;
- };
- </script>
- <style scoped>
- .ba-pc-container {
- position: relative;
- width: 100vw;
- min-height: 100dvh;
- display: flex;
- background: linear-gradient(180deg, #ffffff 0%, #f9f9ff 100%);
- font-family: "HarmonyOS Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
- overflow: hidden;
- color: #141b2b;
- }
- .bg-orb {
- display: none;
- }
- .bg-image {
- position: absolute;
- left: 280px;
- top: 0;
- width: calc(100% - 280px);
- height: 100%;
- z-index: 0;
- pointer-events: none;
- }
- .particle-bg {
- position: absolute;
- inset: 0;
- pointer-events: none;
- overflow: hidden;
- }
- .particle-bg :deep(canvas) {
- display: block;
- width: 100% !important;
- height: 100% !important;
- }
- .aside-nav {
- position: absolute;
- left: 0;
- top: 0;
- width: 280px;
- height: 100%;
- background: #f1f3ff;
- border-right: 1px solid rgba(199, 196, 216, 0.3);
- box-shadow: 0 4px 17.5px rgba(0, 0, 0, 0.03);
- display: flex;
- flex-direction: column;
- z-index: 10;
- box-sizing: border-box;
- }
- .aside-shell {
- width: 100%;
- flex: 1;
- display: flex;
- flex-direction: column;
- padding: 32px 16px 0;
- overflow: hidden;
- min-height: 0;
- position: relative;
- z-index: 10;
- }
- .aside-header {
- display: flex;
- align-items: center;
- gap: 12px;
- margin-bottom: 32px;
- padding: 0 8px;
- }
- .logo {
- width: 54px;
- height: 35px;
- object-fit: contain;
- flex-shrink: 0;
- }
- .logo-text {
- display: flex;
- flex-direction: column;
- text-align: left;
- gap: 3px;
- }
- .title {
- color: #141b2b;
- font-size: 18px;
- font-weight: 500;
- letter-spacing: 0.02em;
- }
- .divider {
- width: 100%;
- height: 1px;
- background: #686868;
- margin: 0;
- }
- .subtitle {
- color: #555;
- font-size: 9px;
- font-weight: 500;
- letter-spacing: 0.3em;
- }
- .cta-button {
- appearance: none;
- border: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- background: #4f46e5;
- color: #dad7ff;
- padding: 12px 16px;
- border-radius: 12px;
- cursor: pointer;
- font-size: 16px;
- font-weight: 500;
- transition: transform 0.2s ease, background-color 0.2s ease, opacity 0.2s ease;
- line-height: 1.5;
- width: 100%;
- box-sizing: border-box;
- box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
- margin-bottom: 20px;
- }
- .cta-button:hover {
- background: #4338ca;
- transform: translateY(-1px);
- }
- .cta-button:active,
- .action-btn:active,
- .suggestion:active,
- .chat-suggestion-chip:active,
- .nav-item:active,
- .send-btn:active {
- transform: translateY(1px) scale(0.99);
- }
- .nav-tabs {
- display: flex;
- flex-direction: column;
- gap: 4px;
- flex-grow: 1;
- min-height: 0;
- padding-right: 4px;
- }
- .nav-tabs-header {
- padding: 8px 12px;
- color: rgba(70, 69, 85, 0.5);
- font-size: 11px;
- font-weight: 500;
- letter-spacing: 1.1px;
- line-height: 1.5;
- text-align: left;
- flex-shrink: 0;
- }
- .nav-tabs-scroll {
- display: flex;
- flex-direction: column;
- gap: 4px;
- min-height: 0;
- flex: 1;
- overflow-y: auto;
- overflow-x: hidden;
- padding-right: 4px;
- scrollbar-width: auto;
- }
- .nav-empty {
- padding: 12px;
- color: rgba(70, 69, 85, 0.5);
- font-size: 12px;
- line-height: 1.7;
- }
- .nav-item,
- .suggestion,
- .chat-suggestion-chip,
- .action-btn,
- .send-btn {
- font: inherit;
- }
- .nav-item {
- display: flex;
- align-items: center;
- gap: 12px;
- width: 100%;
- padding: 10px 12px;
- cursor: pointer;
- color: #464555;
- transition: background-color 0.2s ease, transform 0.2s ease, color 0.2s ease;
- border: 1px solid transparent;
- border-left: 3px solid transparent;
- border-radius: 8px;
- background: transparent;
- text-align: left;
- position: relative;
- z-index: 0;
- }
- .nav-item:hover {
- background: rgba(195, 192, 255, 0.4);
- }
- .nav-item.popover-open {
- z-index: 20;
- }
- .nav-item.active {
- background: rgba(195, 192, 255, 0.05);
- color: #3525cd;
- border-left-color: #3525cd;
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
- .nav-icon {
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- color: currentColor;
- }
- .nav-item-content {
- font-size: 14px;
- font-weight: 500;
- line-height: 1.5;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- flex: 1;
- min-width: 0;
- transition: padding-right 0.2s ease;
- }
- .nav-item.actions-visible .nav-item-content {
- padding-right: 28px;
- }
- .nav-item-actions {
- position: absolute;
- right: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- width: 24px;
- height: 24px;
- border-radius: 4px;
- background: transparent;
- color: #464555;
- z-index: 2;
- opacity: 0;
- pointer-events: none;
- transition: opacity 0.2s ease, background 0.2s ease;
- }
- .nav-item-actions .action-dots {
- width: 24px;
- height: 24px;
- }
- .nav-item-actions.is-visible {
- opacity: 1;
- pointer-events: auto;
- }
- .nav-item-actions:hover {
- background: #d4d9f8;
- }
- .nav-popover {
- position: absolute;
- top: 100%;
- right: 0;
- width: 90px;
- background: #ffffff;
- border-radius: 4px;
- box-shadow: 0 4px 10.5px rgba(0, 0, 0, 0.05);
- display: flex;
- flex-direction: column;
- z-index: 1000;
- overflow: hidden;
- margin-top: 4px;
- }
- .popover-item {
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 8px 12px;
- font-size: 12px;
- color: #000;
- cursor: pointer;
- background: #fafbff;
- transition: background-color 0.2s;
- }
- .popover-item:hover {
- background: rgba(234, 237, 255, 1)
- }
- .popover-item.delete {
- color: #f97c7c;
- }
- .popover-item.delete:hover {
- background: #FFF5F5;
- }
- .rename-modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background: rgba(0, 0, 0, 0.3);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 9999;
- }
- .rename-modal {
- width: 480px;
- background: #ffffff;
- border-radius: 8px;
- box-shadow: 4px 4px 14px rgba(0, 0, 0, 0.12);
- display: flex;
- flex-direction: column;
- }
- .rename-modal-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16px 20px;
- }
- .rename-modal-title {
- font-size: 16px;
- font-weight: 700;
- color: #000;
- }
- .rename-close-btn {
- background: transparent;
- border: none;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #111;
- padding: 4px;
- }
- .rename-modal-body {
- padding: 24px 20px;
- }
- .rename-input {
- width: 100%;
- padding: 8px 12px;
- font-size: 15px;
- color: #000;
- border: 1px solid #cecece;
- border-radius: 4px;
- outline: none;
- transition: border-color 0.2s;
- }
- .rename-input:focus {
- border-color: #3525cd;
- }
- .rename-modal-footer {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 12px;
- padding: 16px 20px;
- }
- .rename-btn-cancel,
- .rename-btn-confirm {
- width: 84px;
- height: 36px;
- border-radius: 8px;
- font-size: 14px;
- cursor: pointer;
- border: none;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: opacity 0.2s;
- }
- .rename-btn-cancel {
- background: #cecece;
- color: #ffffff;
- }
- .rename-btn-confirm {
- background: #3525cd;
- color: #ffffff;
- }
- .rename-btn-cancel:hover,
- .rename-btn-confirm:hover {
- opacity: 0.9;
- }
- .main-content {
- flex-grow: 1;
- margin-left: 280px;
- display: flex;
- flex-direction: column;
- position: relative;
- z-index: 1;
- height: 100vh;
- transition: margin-right 0.3s ease;
- }
- .main-content.with-right-panel {
- margin-right: calc(v-bind(rightPanelWidth) * 1px);
- }
- .main-content-drop-overlay {
- position: absolute;
- inset: 0;
- z-index: 40;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(53, 37, 205, 0.05);
- backdrop-filter: blur(21px);
- -webkit-backdrop-filter: blur(21px);
- pointer-events: none;
- }
- .main-content-drop-card {
- width: min(672px, calc(100% - 64px));
- border-radius: 16px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 48px;
- text-align: center;
- }
- .main-content-drop-icon {
- width: 96px;
- height: 96px;
- border-radius: 9999px;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 32px;
- background: rgba(53, 37, 205, 0.1);
- color: #4f46e5;
- }
- .main-content-drop-icon svg {
- display: block;
- }
- .main-content-drop-title {
- color: #141b2b;
- font-size: 24px;
- font-weight: 500;
- line-height: 1.3;
- letter-spacing: -0.01em;
- margin-bottom: 12px;
- }
- .main-content-drop-subtitle {
- color: #464555;
- font-size: 16px;
- font-weight: 400;
- line-height: 1.5;
- max-width: 552px;
- }
- .drop-overlay-fade-enter-active,
- .drop-overlay-fade-leave-active {
- transition: opacity 0.16s ease, transform 0.16s ease;
- }
- .drop-overlay-fade-enter-from,
- .drop-overlay-fade-leave-to {
- opacity: 0;
- transform: scale(0.985);
- }
- .drop-overlay-fade-enter-to,
- .drop-overlay-fade-leave-from {
- opacity: 1;
- transform: scale(1);
- }
- .top-app-bar {
- height: 64px;
- border-bottom: 1px solid rgba(199, 196, 216, 0.3);
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f9f9ff;
- position: relative;
- z-index: 1;
- }
- .top-app-bar-inner {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 24px;
- }
- .top-app-bar-copy {
- display: flex;
- flex-direction: column;
- gap: 3px;
- }
- .top-app-bar-title {
- color: #3525cd;
- font-size: 16px;
- font-weight: 500;
- letter-spacing: -0.24px;
- }
- .top-app-bar-status {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 8px 12px;
- border-radius: 999px;
- background: rgba(195, 192, 255, 0.1);
- color: #3525cd;
- font-size: 12px;
- letter-spacing: 0.02em;
- }
- .status-dot {
- width: 7px;
- height: 7px;
- border-radius: 50%;
- background: #3525cd;
- box-shadow: 0 0 0 4px rgba(53, 37, 205, 0.15);
- }
- .canvas-area {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 24px 24px 96px 24px;
- /* overflow-y: auto;
- overflow-x: hidden; */
- height: 0;
- position: relative;
- z-index: 1;
- }
- .chatting-canvas {
- padding: 0;
- /* overflow: hidden; */
- }
- .hero-shell {
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 14.8px;
- margin-top: 54px;
- padding-bottom: 48px;
- }
- .overlay-border {
- padding: 4px 12px;
- border-radius: 9999px;
- background: rgba(195, 192, 255, 0.1);
- border: 1px solid rgba(195, 192, 255, 0.2);
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .overlay-text {
- color: #3525cd;
- font-size: 12px;
- font-weight: 600;
- letter-spacing: 0.16em;
- line-height: 1.4;
- text-transform: uppercase;
- }
- .dot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background-color: #3525cd;
- box-shadow: 0 0 0 4px rgba(53, 37, 205, 0.15);
- }
- .welcome-text {
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 14.8px;
- text-align: center;
- }
- .heading {
- color: #141b2b;
- font-size: 32px;
- font-weight: 500;
- letter-spacing: -0.04em;
- line-height: 1.2;
- margin: 0;
- padding-top: 8.2px;
- width: 100%;
- text-wrap: balance;
- }
- .desc {
- color: #464555;
- font-size: 16px;
- font-weight: 500;
- line-height: 1.6;
- margin: 0 auto;
- width: 100%;
- text-wrap: pretty;
- }
- .input-area {
- background: #ffffff;
- border: 1px solid rgba(199, 196, 216, 0.3);
- border-radius: 16px;
- box-shadow: 0 4px 17.5px rgba(0, 0, 0, 0.03);
- padding: 16px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- width: 100%;
- }
- .home-input {
- max-width: 800px;
- padding: 16px;
- }
- .home-input-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: 100%;
- }
- .actions-left {
- display: flex;
- align-items: center;
- gap: 4px;
- .action-icon-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border-radius: 8px;
- background: transparent;
- transition: background 0.2s ease;
- }
- }
- .actions-right {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .action-btn {
- appearance: none;
- border: 0;
- width: 36px;
- height: 36px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 8px;
- color: #464555;
- background: transparent;
- cursor: pointer;
- transition: background-color 0.2s ease, transform 0.2s ease, color 0.2s ease;
- }
- .action-btn:hover {
- background-color: #e3e3e3;
- color: #3525cd;
- }
- .action-btn:disabled {
- cursor: not-allowed;
- opacity: 0.45;
- }
- .action-btn:disabled:hover {
- background-color: transparent;
- color: #464555;
- }
- .action-btn.voice-active {
- background-color: rgba(195, 192, 255, 0.15);
- color: #3525cd;
- }
- .voice-wave-canvas {
- margin-left: 8px;
- border-radius: 4px;
- }
- textarea {
- width: 100%;
- border: none;
- outline: none;
- resize: none;
- font-family: inherit;
- font-size: 16px;
- font-weight: 500;
- color: #141b2b;
- background: transparent;
- min-height: 56px;
- padding: 7px 48px 22.41px 12px;
- line-height: 1.6;
- }
- textarea::placeholder {
- color: rgba(70, 69, 85, 0.45);
- }
- .send-btn {
- appearance: none;
- border: 0;
- width: 40px;
- height: 40px;
- border-radius: 10px;
- background: #e2e8f0;
- color: #94a3b8;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: not-allowed;
- transition: transform 0.2s ease, opacity 0.2s ease, background-color 0.2s ease;
- }
- .send-btn svg {
- transform: translateX(1px);
- }
- .send-btn.active {
- cursor: pointer;
- background: #4f46e5;
- color: #ffffff;
- box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
- }
- .send-btn.is-generating {
- cursor: pointer;
- background: #e2e8f0;
- color: #64748b;
- }
- .send-btn:disabled {
- pointer-events: none;
- }
- .quick-start {
- display: flex;
- gap: 12px;
- margin-top: 32px;
- flex-wrap: wrap;
- justify-content: center;
- max-width: 800px;
- }
- .suggestion {
- appearance: none;
- border: 1px solid rgba(199, 196, 216, 0.3);
- padding: 8px 16px;
- border-radius: 9999px;
- background: #ffffff;
- color: #464555;
- font-size: 12px;
- cursor: pointer;
- transition: transform 0.2s ease, background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
- letter-spacing: 0.12px;
- line-height: 1.4;
- font-weight: 500;
- }
- .suggestion:hover {
- background-color: rgba(195, 192, 255, 0.05);
- border-color: #3525cd;
- color: #3525cd;
- }
- /* Attachment Display Area */
- .attachments-area {
- display: flex;
- flex-wrap: wrap;
- gap: 16px;
- width: 100%;
- padding-bottom: 8px;
- }
- .attachment-item {
- border-radius: 12px;
- position: relative;
- overflow: hidden;
- box-shadow: 0 1px 1.75px rgba(0, 0, 0, 0.05);
- }
- .attachment-item.is-image {
- width: 68px;
- height: 68px;
- border: 1px solid rgba(199, 196, 216, 0.3);
- }
- .attachment-image-preview {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .attachment-delete-btn {
- position: absolute;
- top: 5px;
- right: 5px;
- z-index: 2;
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.8);
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- border: none;
- backdrop-filter: blur(7px);
- color: #464555;
- }
- .attachment-delete-btn:hover {
- background: rgba(255, 255, 255, 1);
- }
- .attachment-progress-overlay {
- position: absolute;
- inset: 0;
- z-index: 1;
- background: rgba(255, 255, 255, 0.6);
- backdrop-filter: blur(3.5px);
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .progress-spinner {
- width: 24px;
- height: 24px;
- border: 2px solid rgba(53, 37, 205, 0.2);
- border-top-color: #3525cd;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- .attachment-item.is-file {
- display: flex;
- align-items: center;
- padding: 12px;
- gap: 12px;
- border: 1px solid rgba(199, 196, 216, 0.3);
- background: #ffffff;
- max-width: 280px;
- }
- .file-icon {
- width: 40px;
- height: 40px;
- border-radius: 8px;
- background: rgba(186, 26, 26, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .file-info {
- display: flex;
- flex-direction: column;
- gap: 2px;
- flex: 1;
- min-width: 0;
- }
- .file-progress-bar {
- width: 100%;
- height: 4px;
- border-radius: 999px;
- background: rgba(53, 37, 205, 0.08);
- overflow: hidden;
- margin-top: 6px;
- }
- .file-progress-bar-fill {
- height: 100%;
- border-radius: inherit;
- background: linear-gradient(90deg, #3525cd 0%, #6a5cf5 100%);
- transition: width 0.2s ease;
- }
- .file-name {
- font-size: 14px;
- font-weight: 500;
- color: #141b2b;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .file-progress {
- font-size: 12px;
- color: #464555;
- }
- .file-delete {
- position: relative;
- z-index: 2;
- padding: 4px;
- cursor: pointer;
- color: #464555;
- }
- /* Tooltip Styles */
- .tooltip-container {
- position: relative;
- display: inline-flex;
- }
- .custom-tooltip {
- background: #ffffff;
- color: #000000;
- font-size: 12px;
- font-weight: 500;
- padding: 8px 16px;
- border-radius: 4px;
- box-shadow: 0 4px 10.5px rgba(0, 0, 0, 0.05);
- pointer-events: none;
- z-index: 99999;
- }
- .pc-tooltip-portal {
- position: fixed;
- inset: 0;
- z-index: 20000;
- pointer-events: none;
- }
- .custom-tooltip--portal {
- position: fixed;
- max-width: min(320px, calc(100vw - 24px));
- white-space: normal;
- word-break: break-word;
- line-height: 1.4;
- opacity: 1;
- visibility: visible;
- transform: translate(-50%, -100%);
- }
- .custom-tooltip::after {
- content: '';
- position: absolute;
- top: 100%;
- left: 50%;
- transform: translateX(-50%);
- border-width: 6px 4px 0 4px;
- border-style: solid;
- border-color: #ffffff transparent transparent transparent;
- }
- .tooltip-container:hover {
- z-index: 100000;
- }
- .tooltip-container:hover .action-btn {
- background-color: #e3e3e3;
- }
- .tooltip-container:hover .action-icon-wrapper {
- background: #e3e3e3;
- }
- .bento-features {
- display: grid;
- grid-template-columns: repeat(3, 384px);
- gap: 24px;
- margin-top: 96px;
- width: 100%;
- max-width: none;
- justify-content: center;
- }
- .bento-card {
- background: rgba(241, 243, 255, 0.5);
- border: 1px solid rgba(199, 196, 216, 0.1);
- border-radius: 16px;
- padding: 24px;
- display: flex;
- flex-direction: column;
- gap: 16px;
- min-height: 184px;
- }
- .bento-icon {
- width: 40px;
- height: 40px;
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .bento-card-blue .bento-icon {
- background: rgba(70, 72, 212, 0.1);
- color: #4648d4;
- }
- .bento-card-amber .bento-icon {
- background: rgba(126, 48, 0, 0.1);
- color: #7e3000;
- }
- .bento-card-rose .bento-icon {
- background: rgba(53, 37, 205, 0.1);
- color: #3525cd;
- }
- .bento-content {
- display: flex;
- flex-direction: column;
- gap: 7px;
- text-align: left;
- }
- .bento-title {
- color: #141b2b;
- font-size: 18px;
- font-weight: 600;
- line-height: 1.4;
- }
- .bento-desc {
- color: #464555;
- font-size: 14px;
- line-height: 1.6;
- }
- .chat-layout {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- position: relative;
- }
- .chat-messages {
- flex: 1;
- width: 100%;
- max-width: 1200px;
- margin: 0 auto;
- padding: 0 24px 0;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- }
- .message-row {
- display: flex;
- width: 100%;
- max-width: 1200px;
- margin: 0 auto;
- gap: 16px;
- margin-bottom: 16px;
- transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
- will-change: transform;
- }
- .message-row.is-last {
- margin-bottom: 50px;
- }
- .message-row.user {
- justify-content: flex-end;
- align-items: flex-start;
- }
- .message-row.ai {
- justify-content: flex-start;
- align-items: flex-start;
- }
- .ai-avatar {
- width: 40px;
- height: 40px;
- /* border-radius: 50%; */
- background: #ffffff;
- flex-shrink: 0;
- margin-top: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- /* box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); */
- overflow: hidden;
- }
- .ai-avatar-bg {
- width: 100%;
- height: 100%;
- background-image: url('@/assets/20260810-173429.png');
- background-size: cover;
- background-repeat: no-repeat;
- background-position: center;
- }
- .user-avatar {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- background: #f1f3ff;
- color: #3525cd;
- flex-shrink: 0;
- margin-top: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- border: 1px solid rgba(199, 196, 216, 0.3);
- }
- .user-avatar-bg {
- display: flex;
- align-items: center;
- justify-content: center;
- color: currentColor;
- }
- :deep(.chat-record-line) {
- min-width: 80px !important;
- min-height: 55px !important;
- max-width: 809px !important;
- font-size: 15px !important;
- line-height: 1.68 !important;
- }
- :deep(.text-right) {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- }
- :deep(.text-left) {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- }
- .chat-input-wrapper {
- flex-shrink: 0;
- width: 100%;
- max-width: 1200px;
- margin: 0 auto;
- padding: 0 16px 14px;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- box-sizing: border-box;
- z-index: 10;
- position: relative;
- }
- .chat-suggestions-container {
- position: absolute;
- bottom: 100%;
- left: 0;
- width: 100%;
- display: flex;
- justify-content: center;
- pointer-events: none;
- padding: 0 16px 8px;
- }
- .chat-suggestions-gradient {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(to bottom, rgba(249, 249, 255, 0) 0%, rgba(249, 249, 255, 0.8) 50%, #f9f9ff 80%, #f9f9ff 100%);
- pointer-events: none;
- z-index: 0;
- }
- .chat-suggestions {
- display: flex;
- gap: 12px;
- flex-wrap: wrap;
- justify-content: flex-start;
- width: 100%;
- max-width: 1200px;
- position: relative;
- z-index: 1;
- pointer-events: auto;
- }
- .chat-suggestions-fade-enter-active,
- .chat-suggestions-fade-leave-active {
- transition: opacity 0.22s ease, transform 0.22s ease;
- }
- .chat-suggestions-fade-enter-from,
- .chat-suggestions-fade-leave-to {
- opacity: 0;
- transform: translateY(10px);
- }
- .chat-suggestions-fade-enter-to,
- .chat-suggestions-fade-leave-from {
- opacity: 1;
- transform: translateY(0);
- }
- .chat-suggestion-chip {
- appearance: none;
- border: 1px solid rgba(199, 196, 216, 0.3);
- padding: 8px 16px;
- border-radius: 9999px;
- background: #ffffff;
- color: #464555;
- font-size: 12px;
- cursor: pointer;
- transition: transform 0.2s ease, background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
- letter-spacing: 0.12px;
- line-height: 1.4;
- font-weight: 500;
- }
- .chat-suggestion-chip:hover {
- background-color: rgba(195, 192, 255, 0.05);
- border-color: #3525cd;
- color: #3525cd;
- }
- .chat-input-box {
- width: 100%;
- max-width: 1200px;
- background: #ffffff;
- border: 1px solid rgba(199, 196, 216, 0.3);
- border-radius: 16px;
- box-shadow: 0 4px 17.5px rgba(0, 0, 0, 0.03);
- padding: 12px 16px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .chat-input-box textarea {
- min-height: 60px;
- padding: 0 8px;
- }
- .ai-disclaimer {
- color: #4f46e5;
- font-size: 12px;
- text-align: center;
- margin-top: 0;
- letter-spacing: 0.12px;
- }
- @media (max-width: 1180px) {
- .bento-features {
- grid-template-columns: 1fr;
- }
- }
- @media (max-width: 760px) {
- .ba-pc-container {
- overflow: auto;
- }
- .aside-nav {
- position: static;
- width: 100%;
- height: auto;
- }
- .main-content {
- margin-left: 0;
- height: auto;
- }
- .top-app-bar {
- height: auto;
- padding: 16px 0;
- }
- .top-app-bar-inner {
- flex-direction: column;
- align-items: flex-start;
- }
- .canvas-area {
- padding: 20px 16px 84px;
- height: auto;
- }
- .hero-shell {
- margin-top: 28px;
- padding-bottom: 40px;
- }
- .quick-start,
- .chat-suggestions {
- justify-content: flex-start;
- }
- .chat-input-box,
- .chat-suggestions {
- margin-left: 0;
- margin-right: 0;
- }
- }
- </style>
|