You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1 line
247 KiB
Plaintext

[{"D:\\NextWeb\\app\\api\\alibaba.ts":"1","D:\\NextWeb\\app\\api\\anthropic.ts":"2","D:\\NextWeb\\app\\api\\artifacts\\route.ts":"3","D:\\NextWeb\\app\\api\\auth.ts":"4","D:\\NextWeb\\app\\api\\azure.ts":"5","D:\\NextWeb\\app\\api\\baidu.ts":"6","D:\\NextWeb\\app\\api\\bytedance.ts":"7","D:\\NextWeb\\app\\api\\common.ts":"8","D:\\NextWeb\\app\\api\\config\\route.ts":"9","D:\\NextWeb\\app\\api\\deepseek.ts":"10","D:\\NextWeb\\app\\api\\glm.ts":"11","D:\\NextWeb\\app\\api\\google.ts":"12","D:\\NextWeb\\app\\api\\iflytek.ts":"13","D:\\NextWeb\\app\\api\\moonshot.ts":"14","D:\\NextWeb\\app\\api\\openai.ts":"15","D:\\NextWeb\\app\\api\\proxy.ts":"16","D:\\NextWeb\\app\\api\\siliconflow.ts":"17","D:\\NextWeb\\app\\api\\stability.ts":"18","D:\\NextWeb\\app\\api\\tencent\\route.ts":"19","D:\\NextWeb\\app\\api\\upstash\\[action]\\[...key]\\route.ts":"20","D:\\NextWeb\\app\\api\\webdav\\[...path]\\route.ts":"21","D:\\NextWeb\\app\\api\\xai.ts":"22","D:\\NextWeb\\app\\api\\[provider]\\[...path]\\route.ts":"23","D:\\NextWeb\\app\\client\\api.ts":"24","D:\\NextWeb\\app\\client\\controller.ts":"25","D:\\NextWeb\\app\\client\\platforms\\alibaba.ts":"26","D:\\NextWeb\\app\\client\\platforms\\anthropic.ts":"27","D:\\NextWeb\\app\\client\\platforms\\baidu.ts":"28","D:\\NextWeb\\app\\client\\platforms\\bytedance.ts":"29","D:\\NextWeb\\app\\client\\platforms\\deepseek.ts":"30","D:\\NextWeb\\app\\client\\platforms\\glm.ts":"31","D:\\NextWeb\\app\\client\\platforms\\google.ts":"32","D:\\NextWeb\\app\\client\\platforms\\iflytek.ts":"33","D:\\NextWeb\\app\\client\\platforms\\moonshot.ts":"34","D:\\NextWeb\\app\\client\\platforms\\openai.ts":"35","D:\\NextWeb\\app\\client\\platforms\\siliconflow.ts":"36","D:\\NextWeb\\app\\client\\platforms\\tencent.ts":"37","D:\\NextWeb\\app\\client\\platforms\\xai.ts":"38","D:\\NextWeb\\app\\command.ts":"39","D:\\NextWeb\\app\\components\\artifacts.tsx":"40","D:\\NextWeb\\app\\components\\auth.tsx":"41","D:\\NextWeb\\app\\components\\button.tsx":"42","D:\\NextWeb\\app\\components\\chat-list.tsx":"43","D:\\NextWeb\\app\\components\\chat.tsx":"44","D:\\NextWeb\\app\\components\\emoji.tsx":"45","D:\\NextWeb\\app\\components\\error.tsx":"46","D:\\NextWeb\\app\\components\\exporter.tsx":"47","D:\\NextWeb\\app\\components\\home.tsx":"48","D:\\NextWeb\\app\\components\\input-range.tsx":"49","D:\\NextWeb\\app\\components\\markdown.tsx":"50","D:\\NextWeb\\app\\components\\mask.tsx":"51","D:\\NextWeb\\app\\components\\mcp-market.tsx":"52","D:\\NextWeb\\app\\components\\message-selector.tsx":"53","D:\\NextWeb\\app\\components\\model-config.tsx":"54","D:\\NextWeb\\app\\components\\new-chat.tsx":"55","D:\\NextWeb\\app\\components\\plugin.tsx":"56","D:\\NextWeb\\app\\components\\realtime-chat\\index.ts":"57","D:\\NextWeb\\app\\components\\realtime-chat\\realtime-chat.tsx":"58","D:\\NextWeb\\app\\components\\realtime-chat\\realtime-config.tsx":"59","D:\\NextWeb\\app\\components\\sd\\index.tsx":"60","D:\\NextWeb\\app\\components\\sd\\sd-panel.tsx":"61","D:\\NextWeb\\app\\components\\sd\\sd-sidebar.tsx":"62","D:\\NextWeb\\app\\components\\sd\\sd.tsx":"63","D:\\NextWeb\\app\\components\\search-chat.tsx":"64","D:\\NextWeb\\app\\components\\settings.tsx":"65","D:\\NextWeb\\app\\components\\sidebar.tsx":"66","D:\\NextWeb\\app\\components\\tts-config.tsx":"67","D:\\NextWeb\\app\\components\\ui-lib.tsx":"68","D:\\NextWeb\\app\\components\\voice-print\\index.ts":"69","D:\\NextWeb\\app\\components\\voice-print\\voice-print.tsx":"70","D:\\NextWeb\\app\\config\\build.ts":"71","D:\\NextWeb\\app\\config\\client.ts":"72","D:\\NextWeb\\app\\config\\server.ts":"73","D:\\NextWeb\\app\\constant.ts":"74","D:\\NextWeb\\app\\global.d.ts":"75","D:\\NextWeb\\app\\layout.tsx":"76","D:\\NextWeb\\app\\lib\\audio.ts":"77","D:\\NextWeb\\app\\locales\\ar.ts":"78","D:\\NextWeb\\app\\locales\\bn.ts":"79","D:\\NextWeb\\app\\locales\\cn.ts":"80","D:\\NextWeb\\app\\locales\\cs.ts":"81","D:\\NextWeb\\app\\locales\\de.ts":"82","D:\\NextWeb\\app\\locales\\en.ts":"83","D:\\NextWeb\\app\\locales\\es.ts":"84","D:\\NextWeb\\app\\locales\\fr.ts":"85","D:\\NextWeb\\app\\locales\\id.ts":"86","D:\\NextWeb\\app\\locales\\index.ts":"87","D:\\NextWeb\\app\\locales\\it.ts":"88","D:\\NextWeb\\app\\locales\\jp.ts":"89","D:\\NextWeb\\app\\locales\\ko.ts":"90","D:\\NextWeb\\app\\locales\\no.ts":"91","D:\\NextWeb\\app\\locales\\pt.ts":"92","D:\\NextWeb\\app\\locales\\ru.ts":"93","D:\\NextWeb\\app\\locales\\sk.ts":"94","D:\\NextWeb\\app\\locales\\tr.ts":"95","D:\\NextWeb\\app\\locales\\tw.ts":"96","D:\\NextWeb\\app\\locales\\vi.ts":"97","D:\\NextWeb\\app\\masks\\build.ts":"98","D:\\NextWeb\\app\\masks\\cn.ts":"99","D:\\NextWeb\\app\\masks\\en.ts":"100","D:\\NextWeb\\app\\masks\\index.ts":"101","D:\\NextWeb\\app\\masks\\tw.ts":"102","D:\\NextWeb\\app\\masks\\typing.ts":"103","D:\\NextWeb\\app\\mcp\\actions.ts":"104","D:\\NextWeb\\app\\mcp\\client.ts":"105","D:\\NextWeb\\app\\mcp\\logger.ts":"106","D:\\NextWeb\\app\\mcp\\types.ts":"107","D:\\NextWeb\\app\\mcp\\utils.ts":"108","D:\\NextWeb\\app\\page.tsx":"109","D:\\NextWeb\\app\\polyfill.ts":"110","D:\\NextWeb\\app\\store\\access.ts":"111","D:\\NextWeb\\app\\store\\chat.ts":"112","D:\\NextWeb\\app\\store\\config.ts":"113","D:\\NextWeb\\app\\store\\index.ts":"114","D:\\NextWeb\\app\\store\\mask.ts":"115","D:\\NextWeb\\app\\store\\plugin.ts":"116","D:\\NextWeb\\app\\store\\prompt.ts":"117","D:\\NextWeb\\app\\store\\sd.ts":"118","D:\\NextWeb\\app\\store\\sync.ts":"119","D:\\NextWeb\\app\\store\\update.ts":"120","D:\\NextWeb\\app\\typing.ts":"121","D:\\NextWeb\\app\\utils\\audio.ts":"122","D:\\NextWeb\\app\\utils\\auth-settings-events.ts":"123","D:\\NextWeb\\app\\utils\\baidu.ts":"124","D:\\NextWeb\\app\\utils\\chat.ts":"125","D:\\NextWeb\\app\\utils\\clone.ts":"126","D:\\NextWeb\\app\\utils\\cloud\\index.ts":"127","D:\\NextWeb\\app\\utils\\cloud\\upstash.ts":"128","D:\\NextWeb\\app\\utils\\cloud\\webdav.ts":"129","D:\\NextWeb\\app\\utils\\cloudflare.ts":"130","D:\\NextWeb\\app\\utils\\format.ts":"131","D:\\NextWeb\\app\\utils\\hmac.ts":"132","D:\\NextWeb\\app\\utils\\hooks.ts":"133","D:\\NextWeb\\app\\utils\\indexedDB-storage.ts":"134","D:\\NextWeb\\app\\utils\\merge.ts":"135","D:\\NextWeb\\app\\utils\\model.ts":"136","D:\\NextWeb\\app\\utils\\ms_edge_tts.ts":"137","D:\\NextWeb\\app\\utils\\object.ts":"138","D:\\NextWeb\\app\\utils\\store.ts":"139","D:\\NextWeb\\app\\utils\\stream.ts":"140","D:\\NextWeb\\app\\utils\\sync.ts":"141","D:\\NextWeb\\app\\utils\\tencent.ts":"142","D:\\NextWeb\\app\\utils\\token.ts":"143","D:\\NextWeb\\app\\utils.ts":"144"},{"size":3398,"mtime":1739213681786,"results":"145","hashOfConfig":"146"},{"size":4550,"mtime":1739213681787,"results":"147","hashOfConfig":"146"},{"size":2156,"mtime":1739213681787,"results":"148","hashOfConfig":"146"},{"size":4070,"mtime":1739213681787,"results":"149","hashOfConfig":"146"},{"size":855,"mtime":1739213681787,"results":"150","hashOfConfig":"146"},{"size":3698,"mtime":1739213681788,"results":"151","hashOfConfig":"146"},{"size":3294,"mtime":1739213681788,"results":"152","hashOfConfig":"146"},{"size":6160,"mtime":1739213681788,"results":"153","hashOfConfig":"146"},{"size":890,"mtime":1739213681788,"results":"154","hashOfConfig":"146"},{"size":3335,"mtime":1739213681788,"results":"155","hashOfConfig":"146"},{"size":3356,"mtime":1739213681789,"results":"156","hashOfConfig":"146"},{"size":3449,"mtime":1739213681789,"results":"157","hashOfConfig":"146"},{"size":3332,"mtime":1739213681789,"results":"158","hashOfConfig":"146"},{"size":3335,"mtime":1739213681789,"results":"159","hashOfConfig":"146"},{"size":2128,"mtime":1739213681790,"results":"160","hashOfConfig":"146"},{"size":2855,"mtime":1739213681790,"results":"161","hashOfConfig":"146"},{"size":3362,"mtime":1739213681790,"results":"162","hashOfConfig":"146"},{"size":2740,"mtime":1739213681790,"results":"163","hashOfConfig":"146"},{"size":2648,"mtime":1739213681790,"results":"164","hashOfConfig":"146"},{"size":1938,"mtime":1739213681791,"results":"165","hashOfConfig":"146"},{"size":4064,"mtime":1739213681791,"results":"166","hashOfConfig":"146"},{"size":3290,"mtime":1739213681792,"results":"167","hashOfConfig":"146"},{"size":2691,"mtime":1739213681786,"results":"168","hashOfConfig":"146"},{"size":10887,"mtime":1739213681792,"results":"169","hashOfConfig":"146"},{"size":951,"mtime":1739213681792,"results":"170","hashOfConfig":"146"},{"size":7714,"mtime":1739213681792,"results":"171","hashOfConfig":"146"},{"size":12621,"mtime":1739213681793,"results":"172","hashOfConfig":"146"},{"size":8379,"mtime":1739213681793,"results":"173","hashOfConfig":"146"},{"size":7475,"mtime":1739213681793,"results":"174","hashOfConfig":"146"},{"size":7351,"mtime":1739213681793,"results":"175","hashOfConfig":"146"},{"size":8405,"mtime":1739213681794,"results":"176","hashOfConfig":"146"},{"size":10041,"mtime":1739213681794,"results":"177","hashOfConfig":"146"},{"size":7500,"mtime":1739213681794,"results":"178","hashOfConfig":"146"},{"size":5900,"mtime":1739213681795,"results":"179","hashOfConfig":"146"},{"size":14641,"mtime":1739213681795,"results":"180","hashOfConfig":"146"},{"size":7260,"mtime":1739213681795,"results":"181","hashOfConfig":"146"},{"size":7917,"mtime":1739213681795,"results":"182","hashOfConfig":"146"},{"size":5678,"mtime":1739213681796,"results":"183","hashOfConfig":"146"},{"size":2060,"mtime":1739213681796,"results":"184","hashOfConfig":"146"},{"size":7851,"mtime":1739213681801,"results":"185","hashOfConfig":"146"},{"size":5952,"mtime":1739213681801,"results":"186","hashOfConfig":"146"},{"size":1558,"mtime":1739213681802,"results":"187","hashOfConfig":"146"},{"size":5148,"mtime":1739213681803,"results":"188","hashOfConfig":"146"},{"size":72578,"mtime":1739359826123,"results":"189","hashOfConfig":"146"},{"size":3890,"mtime":1739213681805,"results":"190","hashOfConfig":"146"},{"size":2125,"mtime":1739213681805,"results":"191","hashOfConfig":"146"},{"size":19521,"mtime":1739213681806,"results":"192","hashOfConfig":"146"},{"size":7456,"mtime":1739213681806,"results":"193","hashOfConfig":"146"},{"size":803,"mtime":1739213681806,"results":"194","hashOfConfig":"146"},{"size":10089,"mtime":1739213681807,"results":"195","hashOfConfig":"146"},{"size":21805,"mtime":1739213681807,"results":"196","hashOfConfig":"146"},{"size":25365,"mtime":1739213681808,"results":"197","hashOfConfig":"146"},{"size":7313,"mtime":1739213681808,"results":"198","hashOfConfig":"146"},{"size":9291,"mtime":1739213681809,"results":"199","hashOfConfig":"146"},{"size":5541,"mtime":1739213681809,"results":"200","hashOfConfig":"146"},{"size":13180,"mtime":1739213681810,"results":"201","hashOfConfig":"146"},{"size":34,"mtime":1739213681810,"results":"202","hashOfConfig":"146"},{"size":11319,"mtime":1739213681810,"results":"203","hashOfConfig":"146"},{"size":5733,"mtime":1739213681811,"results":"204","hashOfConfig":"146"},{"size":52,"mtime":1739213681811,"results":"205","hashOfConfig":"146"},{"size":9766,"mtime":1739213681811,"results":"206","hashOfConfig":"146"},{"size":4107,"mtime":1739213681811,"results":"207","hashOfConfig":"146"},{"size":14217,"mtime":1739213681812,"results":"208","hashOfConfig":"146"},{"size":5405,"mtime":1739213681812,"results":"209","hashOfConfig":"146"},{"size":54521,"mtime":1739359581755,"results":"210","hashOfConfig":"146"},{"size":9990,"mtime":1739360397422,"results":"211","hashOfConfig":"146"},{"size":4003,"mtime":1739213681813,"results":"212","hashOfConfig":"146"},{"size":14861,"mtime":1739213681814,"results":"213","hashOfConfig":"146"},{"size":32,"mtime":1739213681814,"results":"214","hashOfConfig":"146"},{"size":5559,"mtime":1739213681815,"results":"215","hashOfConfig":"146"},{"size":1324,"mtime":1739213681815,"results":"216","hashOfConfig":"146"},{"size":642,"mtime":1739213681815,"results":"217","hashOfConfig":"146"},{"size":8235,"mtime":1739213681816,"results":"218","hashOfConfig":"146"},{"size":14657,"mtime":1739379776504,"results":"219","hashOfConfig":"146"},{"size":1222,"mtime":1739213681816,"results":"220","hashOfConfig":"146"},{"size":2001,"mtime":1739213681838,"results":"221","hashOfConfig":"146"},{"size":7033,"mtime":1739213681838,"results":"222","hashOfConfig":"146"},{"size":23172,"mtime":1739213681839,"results":"223","hashOfConfig":"146"},{"size":33330,"mtime":1739213681839,"results":"224","hashOfConfig":"146"},{"size":25441,"mtime":1739213681839,"results":"225","hashOfConfig":"146"},{"size":19774,"mtime":1739213681840,"results":"226","hashOfConfig":"146"},{"size":21122,"mtime":1739213681840,"results":"227","hashOfConfig":"146"},{"size":25098,"mtime":1739213681840,"results":"228","hashOfConfig":"146"},{"size":20795,"mtime":1739213681841,"results":"229","hashOfConfig":"146"},{"size":21294,"mtime":1739213681841,"results":"230","hashOfConfig":"146"},{"size":19260,"mtime":1739213681842,"results":"231","hashOfConfig":"146"},{"size":3417,"mtime":1739213681842,"results":"232","hashOfConfig":"146"},{"size":20664,"mtime":1739213681842,"results":"233","hashOfConfig":"146"},{"size":21972,"mtime":1739213681842,"results":"234","hashOfConfig":"146"},{"size":20149,"mtime":1739213681843,"results":"235","hashOfConfig":"146"},{"size":19511,"mtime":1739213681843,"results":"236","hashOfConfig":"146"},{"size":17051,"mtime":1739213681843,"results":"237","hashOfConfig":"146"},{"size":27009,"mtime":1739213681844,"results":"238","hashOfConfig":"146"},{"size":17679,"mtime":1739213681844,"results":"239","hashOfConfig":"146"},{"size":20214,"mtime":1739213681844,"results":"240","hashOfConfig":"146"},{"size":17655,"mtime":1739213681845,"results":"241","hashOfConfig":"146"},{"size":22071,"mtime":1739213681845,"results":"242","hashOfConfig":"146"},{"size":576,"mtime":1739213681845,"results":"243","hashOfConfig":"146"},{"size":21849,"mtime":1739213681846,"results":"244","hashOfConfig":"146"},{"size":12983,"mtime":1739213681846,"results":"245","hashOfConfig":"146"},{"size":1077,"mtime":1739213681846,"results":"246","hashOfConfig":"146"},{"size":21903,"mtime":1739213681846,"results":"247","hashOfConfig":"146"},{"size":214,"mtime":1739213681846,"results":"248","hashOfConfig":"146"},{"size":10799,"mtime":1739213681847,"results":"249","hashOfConfig":"146"},{"size":1421,"mtime":1739213681847,"results":"250","hashOfConfig":"146"},{"size":1511,"mtime":1739213681847,"results":"251","hashOfConfig":"146"},{"size":3748,"mtime":1739213681848,"results":"252","hashOfConfig":"146"},{"size":360,"mtime":1739213681848,"results":"253","hashOfConfig":"146"},{"size":391,"mtime":1739213681848,"results":"254","hashOfConfig":"146"},{"size":650,"mtime":1739213681848,"results":"255","hashOfConfig":"146"},{"size":7435,"mtime":1739213681848,"results":"256","hashOfConfig":"146"},{"size":26868,"mtime":1739390471918,"results":"257","hashOfConfig":"146"},{"size":6878,"mtime":1739382493513,"results":"258","hashOfConfig":"146"},{"size":133,"mtime":1739213681850,"results":"259","hashOfConfig":"146"},{"size":3558,"mtime":1739213681850,"results":"260","hashOfConfig":"146"},{"size":8108,"mtime":1739213681850,"results":"261","hashOfConfig":"146"},{"size":4909,"mtime":1739213681850,"results":"262","hashOfConfig":"146"},{"size":4840,"mtime":1739213681851,"results":"263","hashOfConfig":"146"},{"size":3984,"mtime":1739213681851,"results":"264","hashOfConfig":"146"},{"size":4965,"mtime":1739213681851,"results":"265","hashOfConfig":"146"},{"size":601,"mtime":1739213681853,"results":"266","hashOfConfig":"146"},{"size":1359,"mtime":1739213681853,"results":"267","hashOfConfig":"146"},{"size":648,"mtime":1739213681853,"results":"268","hashOfConfig":"146"},{"size":578,"mtime":1739213681853,"results":"269","hashOfConfig":"146"},{"size":18597,"mtime":1739213681854,"results":"270","hashOfConfig":"146"},{"size":283,"mtime":1739213681854,"results":"271","hashOfConfig":"146"},{"size":820,"mtime":1739213681854,"results":"272","hashOfConfig":"146"},{"size":3328,"mtime":1739213681854,"results":"273","hashOfConfig":"146"},{"size":2671,"mtime":1739213681855,"results":"274","hashOfConfig":"146"},{"size":1266,"mtime":1739213681855,"results":"275","hashOfConfig":"146"},{"size":871,"mtime":1739213681855,"results":"276","hashOfConfig":"146"},{"size":7907,"mtime":1739213681855,"results":"277","hashOfConfig":"146"},{"size":638,"mtime":1739213681855,"results":"278","hashOfConfig":"146"},{"size":1208,"mtime":1739213681855,"results":"279","hashOfConfig":"146"},{"size":431,"mtime":1739213681856,"results":"280","hashOfConfig":"146"},{"size":8064,"mtime":1739213681856,"results":"281","hashOfConfig":"146"},{"size":15714,"mtime":1739213681856,"results":"282","hashOfConfig":"146"},{"size":414,"mtime":1739213681856,"results":"283","hashOfConfig":"146"},{"size":2128,"mtime":1739213681857,"results":"284","hashOfConfig":"146"},{"size":3186,"mtime":1739213681857,"results":"285","hashOfConfig":"146"},{"size":4946,"mtime":1739213681857,"results":"286","hashOfConfig":"146"},{"size":2917,"mtime":1739213681858,"results":"287","hashOfConfig":"146"},{"size":487,"mtime":1739213681858,"results":"288","hashOfConfig":"146"},{"size":12631,"mtime":1739213681853,"results":"289","hashOfConfig":"146"},{"filePath":"290","messages":"291","suppressedMessages":"292","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"3t9zbn",{"filePath":"293","messages":"294","suppressedMessages":"295","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"296","messages":"297","suppressedMessages":"298","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"299","messages":"300","suppressedMessages":"301","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"302","messages":"303","suppressedMessages":"304","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"305","messages":"306","suppressedMessages":"307","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"308","messages":"309","suppressedMessages":"310","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"311","messages":"312","suppressedMessages":"313","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"314","messages":"315","suppressedMessages":"316","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"317","messages":"318","suppressedMessages":"319","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"320","messages":"321","suppressedMessages":"322","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"323","messages":"324","suppressedMessages":"325","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"326","messages":"327","suppressedMessages":"328","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"329","messages":"330","suppressedMessages":"331","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"332","messages":"333","suppressedMessages":"334","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"335","messages":"336","suppressedMessages":"337","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"338","messages":"339","suppressedMessages":"340","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"341","messages":"342","suppressedMessages":"343","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"344","messages":"345","suppressedMessages":"346","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"347","messages":"348","suppressedMessages":"349","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"350","messages":"351","suppressedMessages":"352","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"353","messages":"354","suppressedMessages":"355","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"356","messages":"357","suppressedMessages":"358","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"359","messages":"360","suppressedMessages":"361","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"362","messages":"363","suppressedMessages":"364","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"365","messages":"366","suppressedMessages":"367","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"368","messages":"369","suppressedMessages":"370","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"371","messages":"372","suppressedMessages":"373","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"374","messages":"375","suppressedMessages":"376","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"377","messages":"378","suppressedMessages":"379","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"380","messages":"381","suppressedMessages":"382","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"383","messages":"384","suppressedMessages":"385","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"386","messages":"387","suppressedMessages":"388","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"389","messages":"390","suppressedMessages":"391","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"392","messages":"393","suppressedMessages":"394","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"395","messages":"396","suppressedMessages":"397","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"398","messages":"399","suppressedMessages":"400","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"401","messages":"402","suppressedMessages":"403","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"404","messages":"405","suppressedMessages":"406","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"407","messages":"408","suppressedMessages":"409","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"410","messages":"411","suppressedMessages":"412","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"413","messages":"414","suppressedMessages":"415","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"416","messages":"417","suppressedMessages":"418","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"419","messages":"420","suppressedMessages":"421","errorCount":0,"fatalErrorCount":0,"warningCount":11,"fixableErrorCount":0,"fixableWarningCount":7,"source":"422"},{"filePath":"423","messages":"424","suppressedMessages":"425","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"426","messages":"427","suppressedMessages":"428","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"429","messages":"430","suppressedMessages":"431","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"432","messages":"433","suppressedMessages":"434","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"435","messages":"436","suppressedMessages":"437","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"438","messages":"439","suppressedMessages":"440","errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"441"},{"filePath":"442","messages":"443","suppressedMessages":"444","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"445","messages":"446","suppressedMessages":"447","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"448","messages":"449","suppressedMessages":"450","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"451","messages":"452","suppressedMessages":"453","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"454","messages":"455","suppressedMessages":"456","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"457","messages":"458","suppressedMessages":"459","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"460","messages":"461","suppressedMessages":"462","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"463","messages":"464","suppressedMessages":"465","errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"466"},{"filePath":"467","messages":"468","suppressedMessages":"469","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"470","messages":"471","suppressedMessages":"472","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"473","messages":"474","suppressedMessages":"475","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"476","messages":"477","suppressedMessages":"478","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"479","messages":"480","suppressedMessages":"481","errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"482"},{"filePath":"483","messages":"484","suppressedMessages":"485","errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"486"},{"filePath":"487","messages":"488","suppressedMessages":"489","errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":5,"source":"490"},{"filePath":"491","messages":"492","suppressedMessages":"493","errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":2,"source":"494"},{"filePath":"495","messages":"496","suppressedMessages":"497","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"498","messages":"499","suppressedMessages":"500","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"501","messages":"502","suppressedMessages":"503","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"504","messages":"505","suppressedMessages":"506","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"507","messages":"508","suppressedMessages":"509","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"510","messages":"511","suppressedMessages":"512","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"513","messages":"514","suppressedMessages":"515","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"516","messages":"517","suppressedMessages":"518","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"519","messages":"520","suppressedMessages":"521","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"522","messages":"523","suppressedMessages":"524","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"525","messages":"526","suppressedMessages":"527","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"528","messages":"529","suppressedMessages":"530","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"531","messages":"532","suppressedMessages":"533","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"534","messages":"535","suppressedMessages":"536","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"537","messages":"538","suppressedMessages":"539","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"540","messages":"541","suppressedMessages":"542","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"543","messages":"544","suppressedMessages":"545","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"546","messages":"547","suppressedMessages":"548","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"549","messages":"550","suppressedMessages":"551","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"552","messages":"553","suppressedMessages":"554","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"555","messages":"556","suppressedMessages":"557","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"558","messages":"559","suppressedMessages":"560","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"561","messages":"562","suppressedMessages":"563","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"564","messages":"565","suppressedMessages":"566","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"567","messages":"568","suppressedMessages":"569","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"570","messages":"571","suppressedMessages":"572","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"573","messages":"574","suppressedMessages":"575","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"576","messages":"577","suppressedMessages":"578","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"579","messages":"580","suppressedMessages":"581","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"582","messages":"583","suppressedMessages":"584","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"585","messages":"586","suppressedMessages":"587","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"588","messages":"589","suppressedMessages":"590","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"591","messages":"592","suppressedMessages":"593","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"594","messages":"595","suppressedMessages":"596","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"597","messages":"598","suppressedMessages":"599","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"600","messages":"601","suppressedMessages":"602","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"603","messages":"604","suppressedMessages":"605","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"606","messages":"607","suppressedMessages":"608","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"609","messages":"610","suppressedMessages":"611","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"612","messages":"613","suppressedMessages":"614","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"615","messages":"616","suppressedMessages":"617","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"618","messages":"619","suppressedMessages":"620","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"621","messages":"622","suppressedMessages":"623","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"624","messages":"625","suppressedMessages":"626","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"627","messages":"628","suppressedMessages":"629","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"630","messages":"631","suppressedMessages":"632","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"633","messages":"634","suppressedMessages":"635","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"636","messages":"637","suppressedMessages":"638","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"639","messages":"640","suppressedMessages":"641","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"642","messages":"643","suppressedMessages":"644","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"645","messages":"646","suppressedMessages":"647","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"648","messages":"649","suppressedMessages":"650","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"651","messages":"652","suppressedMessages":"653","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"654","messages":"655","suppressedMessages":"656","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"657","messages":"658","suppressedMessages":"659","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"660","messages":"661","suppressedMessages":"662","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"663","messages":"664","suppressedMessages":"665","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"666","messages":"667","suppressedMessages":"668","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"669","messages":"670","suppressedMessages":"671","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"672","messages":"673","suppressedMessages":"674","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"675","messages":"676","suppressedMessages":"677","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"678","messages":"679","suppressedMessages":"680","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"681","messages":"682","suppressedMessages":"683","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"684","messages":"685","suppressedMessages":"686","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"687","messages":"688","suppressedMessages":"689","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"690","messages":"691","suppressedMessages":"692","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"693","messages":"694","suppressedMessages":"695","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"696","messages":"697","suppressedMessages":"698","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"699","messages":"700","suppressedMessages":"701","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"702","messages":"703","suppressedMessages":"704","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"705","messages":"706","suppressedMessages":"707","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"708","messages":"709","suppressedMessages":"710","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"711","messages":"712","suppressedMessages":"713","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"714","messages":"715","suppressedMessages":"716","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"717","messages":"718","suppressedMessages":"719","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"720","messages":"721","suppressedMessages":"722","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"723","messages":"724","suppressedMessages":"725","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"726","messages":"727","suppressedMessages":"728","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"D:\\NextWeb\\app\\api\\alibaba.ts",[],[],"D:\\NextWeb\\app\\api\\anthropic.ts",[],[],"D:\\NextWeb\\app\\api\\artifacts\\route.ts",[],[],"D:\\NextWeb\\app\\api\\auth.ts",[],[],"D:\\NextWeb\\app\\api\\azure.ts",[],[],"D:\\NextWeb\\app\\api\\baidu.ts",[],[],"D:\\NextWeb\\app\\api\\bytedance.ts",[],[],"D:\\NextWeb\\app\\api\\common.ts",[],[],"D:\\NextWeb\\app\\api\\config\\route.ts",[],[],"D:\\NextWeb\\app\\api\\deepseek.ts",[],[],"D:\\NextWeb\\app\\api\\glm.ts",[],[],"D:\\NextWeb\\app\\api\\google.ts",[],[],"D:\\NextWeb\\app\\api\\iflytek.ts",[],[],"D:\\NextWeb\\app\\api\\moonshot.ts",[],[],"D:\\NextWeb\\app\\api\\openai.ts",[],[],"D:\\NextWeb\\app\\api\\proxy.ts",[],[],"D:\\NextWeb\\app\\api\\siliconflow.ts",[],[],"D:\\NextWeb\\app\\api\\stability.ts",[],[],"D:\\NextWeb\\app\\api\\tencent\\route.ts",[],[],"D:\\NextWeb\\app\\api\\upstash\\[action]\\[...key]\\route.ts",[],[],"D:\\NextWeb\\app\\api\\webdav\\[...path]\\route.ts",[],[],"D:\\NextWeb\\app\\api\\xai.ts",[],[],"D:\\NextWeb\\app\\api\\[provider]\\[...path]\\route.ts",[],[],"D:\\NextWeb\\app\\client\\api.ts",[],[],"D:\\NextWeb\\app\\client\\controller.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\alibaba.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\anthropic.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\baidu.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\bytedance.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\deepseek.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\glm.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\google.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\iflytek.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\moonshot.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\openai.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\siliconflow.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\tencent.ts",[],[],"D:\\NextWeb\\app\\client\\platforms\\xai.ts",[],[],"D:\\NextWeb\\app\\command.ts",[],["729"],"D:\\NextWeb\\app\\components\\artifacts.tsx",[],[],"D:\\NextWeb\\app\\components\\auth.tsx",[],["730"],"D:\\NextWeb\\app\\components\\button.tsx",[],[],"D:\\NextWeb\\app\\components\\chat-list.tsx",[],[],"D:\\NextWeb\\app\\components\\chat.tsx",["731","732","733","734","735","736","737","738","739","740","741"],["742","743","744","745"],"import { useDebouncedCallback } from \"use-debounce\";\r\nimport React, {\r\n Fragment,\r\n RefObject,\r\n useCallback,\r\n useEffect,\r\n useMemo,\r\n useRef,\r\n useState,\r\n} from \"react\";\r\n\r\nimport SendWhiteIcon from \"../icons/send-white.svg\";\r\nimport BrainIcon from \"../icons/brain.svg\";\r\nimport RenameIcon from \"../icons/rename.svg\";\r\nimport EditIcon from \"../icons/rename.svg\";\r\nimport ExportIcon from \"../icons/share.svg\";\r\nimport ReturnIcon from \"../icons/return.svg\";\r\nimport CopyIcon from \"../icons/copy.svg\";\r\nimport SpeakIcon from \"../icons/speak.svg\";\r\nimport SpeakStopIcon from \"../icons/speak-stop.svg\";\r\nimport LoadingIcon from \"../icons/three-dots.svg\";\r\nimport LoadingButtonIcon from \"../icons/loading.svg\";\r\nimport PromptIcon from \"../icons/prompt.svg\";\r\nimport MaskIcon from \"../icons/mask.svg\";\r\nimport MaxIcon from \"../icons/max.svg\";\r\nimport MinIcon from \"../icons/min.svg\";\r\nimport ResetIcon from \"../icons/reload.svg\";\r\nimport ReloadIcon from \"../icons/reload.svg\";\r\nimport BreakIcon from \"../icons/break.svg\";\r\nimport SettingsIcon from \"../icons/chat-settings.svg\";\r\nimport DeleteIcon from \"../icons/clear.svg\";\r\nimport PinIcon from \"../icons/pin.svg\";\r\nimport ConfirmIcon from \"../icons/confirm.svg\";\r\nimport CloseIcon from \"../icons/close.svg\";\r\nimport CancelIcon from \"../icons/cancel.svg\";\r\nimport ImageIcon from \"../icons/image.svg\";\r\n\r\nimport LightIcon from \"../icons/light.svg\";\r\nimport DarkIcon from \"../icons/dark.svg\";\r\nimport AutoIcon from \"../icons/auto.svg\";\r\nimport BottomIcon from \"../icons/bottom.svg\";\r\nimport StopIcon from \"../icons/pause.svg\";\r\nimport RobotIcon from \"../icons/robot.svg\";\r\nimport SizeIcon from \"../icons/size.svg\";\r\nimport QualityIcon from \"../icons/hd.svg\";\r\nimport StyleIcon from \"../icons/palette.svg\";\r\nimport PluginIcon from \"../icons/plugin.svg\";\r\nimport ShortcutkeyIcon from \"../icons/shortcutkey.svg\";\r\nimport McpToolIcon from \"../icons/tool.svg\";\r\nimport HeadphoneIcon from \"../icons/headphone.svg\";\r\nimport {\r\n BOT_HELLO,\r\n ChatMessage,\r\n createMessage,\r\n DEFAULT_TOPIC,\r\n ModelType,\r\n SubmitKey,\r\n Theme,\r\n useAccessStore,\r\n useAppConfig,\r\n useChatStore,\r\n usePluginStore,\r\n} from \"../store\";\r\n\r\nimport {\r\n autoGrowTextArea,\r\n copyToClipboard,\r\n getMessageImages,\r\n getMessageTextContent,\r\n isDalle3,\r\n isVisionModel,\r\n safeLocalStorage,\r\n getModelSizes,\r\n supportsCustomSize,\r\n useMobileScreen,\r\n selectOrCopy,\r\n showPlugins,\r\n} from \"../utils\";\r\n\r\nimport { uploadImage as uploadImageRemote } from \"@/app/utils/chat\";\r\n\r\nimport dynamic from \"next/dynamic\";\r\n\r\nimport { ChatControllerPool } from \"../client/controller\";\r\nimport { DalleQuality, DalleStyle, ModelSize } from \"../typing\";\r\nimport { Prompt, usePromptStore } from \"../store/prompt\";\r\nimport Locale from \"../locales\";\r\n\r\nimport { IconButton } from \"./button\";\r\nimport styles from \"./chat.module.scss\";\r\n\r\nimport {\r\n List,\r\n ListItem,\r\n Modal,\r\n Selector,\r\n showConfirm,\r\n showPrompt,\r\n showToast,\r\n} from \"./ui-lib\";\r\nimport { useNavigate } from \"react-router-dom\";\r\nimport {\r\n CHAT_PAGE_SIZE,\r\n DEFAULT_TTS_ENGINE,\r\n ModelProvider,\r\n Path,\r\n REQUEST_TIMEOUT_MS,\r\n ServiceProvider,\r\n UNFINISHED_INPUT,\r\n} from \"../constant\";\r\nimport { Avatar } from \"./emoji\";\r\nimport { ContextPrompts, MaskAvatar, MaskConfig } from \"./mask\";\r\nimport { useMaskStore } from \"../store/mask\";\r\nimport { ChatCommandPrefix, useChatCommand, useCommand } from \"../command\";\r\nimport { prettyObject } from \"../utils/format\";\r\nimport { ExportMessageModal } from \"./exporter\";\r\nimport { getClientConfig } from \"../config/client\";\r\nimport { useAllModels } from \"../utils/hooks\";\r\nimport { ClientApi, MultimodalContent } from \"../client/api\";\r\nimport { createTTSPlayer } from \"../utils/audio\";\r\nimport { MsEdgeTTS, OUTPUT_FORMAT } from \"../utils/ms_edge_tts\";\r\n\r\nimport { isEmpty } from \"lodash-es\";\r\nimport { getModelProvider } from \"../utils/model\";\r\nimport { RealtimeChat } from \"@/app/components/realtime-chat\";\r\nimport clsx from \"clsx\";\r\nimport { getAvailableClientsCount, isMcpEnabled } from \"../mcp/actions\";\r\n\r\nconst localStorage = safeLocalStorage();\r\n\r\nconst ttsPlayer = createTTSPlayer();\r\n\r\nconst Markdown = dynamic(async () => (await import(\"./markdown\")).Markdown, {\r\n loading: () => <LoadingIcon />,\r\n});\r\n\r\nconst MCPAction = () => {\r\n const navigate = useNavigate();\r\n const [count, setCount] = useState<number>(0);\r\n const [mcpEnabled, setMcpEnabled] = useState(false);\r\n\r\n useEffect(() => {\r\n const checkMcpStatus = async () => {\r\n const enabled = await isMcpEnabled();\r\n setMcpEnabled(enabled);\r\n if (enabled) {\r\n const count = await getAvailableClientsCount();\r\n setCount(count);\r\n }\r\n };\r\n checkMcpStatus();\r\n }, []);\r\n\r\n if (!mcpEnabled) return null;\r\n\r\n return (\r\n <ChatAction\r\n onClick={() => navigate(Path.McpMarket)}\r\n text={`MCP${count ? ` (${count})` : \"\"}`}\r\n icon={<McpToolIcon />}\r\n />\r\n );\r\n};\r\n\r\nexport function SessionConfigModel(props: { onClose: () => void }) {\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n const maskStore = useMaskStore();\r\n const navigate = useNavigate();\r\n\r\n return (\r\n <div className=\"modal-mask\">\r\n <Modal\r\n title={Locale.Context.Edit}\r\n onClose={() => props.onClose()}\r\n actions={[\r\n <IconButton\r\n key=\"reset\"\r\n icon={<ResetIcon />}\r\n bordered\r\n text={Locale.Chat.Config.Reset}\r\n onClick={async () => {\r\n if (await showConfirm(Locale.Memory.ResetConfirm)) {\r\n chatStore.updateTargetSession(\r\n session,\r\n (session) => (session.memoryPrompt = \"\"),\r\n );\r\n }\r\n }}\r\n />,\r\n <IconButton\r\n key=\"copy\"\r\n icon={<CopyIcon />}\r\n bordered\r\n text={Locale.Chat.Config.SaveAs}\r\n onClick={() => {\r\n navigate(Path.Masks);\r\n setTimeout(() => {\r\n maskStore.create(session.mask);\r\n }, 500);\r\n }}\r\n />,\r\n ]}\r\n >\r\n <MaskConfig\r\n mask={session.mask}\r\n updateMask={(updater) => {\r\n const mask = { ...session.mask };\r\n updater(mask);\r\n chatStore.updateTargetSession(\r\n session,\r\n (session) => (session.mask = mask),\r\n );\r\n }}\r\n shouldSyncFromGlobal\r\n extraListItems={\r\n session.mask.modelConfig.sendMemory ? (\r\n <ListItem\r\n className=\"copyable\"\r\n title={`${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`}\r\n subTitle={session.memoryPrompt || Locale.Memory.EmptyContent}\r\n ></ListItem>\r\n ) : (\r\n <></>\r\n )\r\n }\r\n ></MaskConfig>\r\n </Modal>\r\n </div>\r\n );\r\n}\r\n\r\nfunction PromptToast(props: {\r\n showToast?: boolean;\r\n showModal?: boolean;\r\n setShowModal: (_: boolean) => void;\r\n}) {\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n const context = session.mask.context;\r\n\r\n return (\r\n <div className={styles[\"prompt-toast\"]} key=\"prompt-toast\">\r\n {props.showToast && context.length > 0 && (\r\n <div\r\n className={clsx(styles[\"prompt-toast-inner\"], \"clickable\")}\r\n role=\"button\"\r\n onClick={() => props.setShowModal(true)}\r\n >\r\n <BrainIcon />\r\n <span className={styles[\"prompt-toast-content\"]}>\r\n {Locale.Context.Toast(context.length)}\r\n </span>\r\n </div>\r\n )}\r\n {props.showModal && (\r\n <SessionConfigModel onClose={() => props.setShowModal(false)} />\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nfunction useSubmitHandler() {\r\n const config = useAppConfig();\r\n const submitKey = config.submitKey;\r\n const isComposing = useRef(false);\r\n\r\n useEffect(() => {\r\n const onCompositionStart = () => {\r\n isComposing.current = true;\r\n };\r\n const onCompositionEnd = () => {\r\n isComposing.current = false;\r\n };\r\n\r\n window.addEventListener(\"compositionstart\", onCompositionStart);\r\n window.addEventListener(\"compositionend\", onCompositionEnd);\r\n\r\n return () => {\r\n window.removeEventListener(\"compositionstart\", onCompositionStart);\r\n window.removeEventListener(\"compositionend\", onCompositionEnd);\r\n };\r\n }, []);\r\n\r\n const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\r\n // Fix Chinese input method \"Enter\" on Safari\r\n if (e.keyCode == 229) return false;\r\n if (e.key !== \"Enter\") return false;\r\n if (e.key === \"Enter\" && (e.nativeEvent.isComposing || isComposing.current))\r\n return false;\r\n return (\r\n (config.submitKey === SubmitKey.AltEnter && e.altKey) ||\r\n (config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) ||\r\n (config.submitKey === SubmitKey.ShiftEnter && e.shiftKey) ||\r\n (config.submitKey === SubmitKey.MetaEnter && e.metaKey) ||\r\n (config.submitKey === SubmitKey.Enter &&\r\n !e.altKey &&\r\n !e.ctrlKey &&\r\n !e.shiftKey &&\r\n !e.metaKey)\r\n );\r\n };\r\n\r\n return {\r\n submitKey,\r\n shouldSubmit,\r\n };\r\n}\r\n\r\nexport type RenderPrompt = Pick<Prompt, \"title\" | \"content\">;\r\n\r\nexport function PromptHints(props: {\r\n prompts: RenderPrompt[];\r\n onPromptSelect: (prompt: RenderPrompt) => void;\r\n}) {\r\n const noPrompts = props.prompts.length === 0;\r\n const [selectIndex, setSelectIndex] = useState(0);\r\n const selectedRef = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n setSelectIndex(0);\r\n }, [props.prompts.length]);\r\n\r\n useEffect(() => {\r\n const onKeyDown = (e: KeyboardEvent) => {\r\n if (noPrompts || e.metaKey || e.altKey || e.ctrlKey) {\r\n return;\r\n }\r\n // arrow up / down to select prompt\r\n const changeIndex = (delta: number) => {\r\n e.stopPropagation();\r\n e.preventDefault();\r\n const nextIndex = Math.max(\r\n 0,\r\n Math.min(props.prompts.length - 1, selectIndex + delta),\r\n );\r\n setSelectIndex(nextIndex);\r\n selectedRef.current?.scrollIntoView({\r\n block: \"center\",\r\n });\r\n };\r\n\r\n if (e.key === \"ArrowUp\") {\r\n changeIndex(1);\r\n } else if (e.key === \"ArrowDown\") {\r\n changeIndex(-1);\r\n } else if (e.key === \"Enter\") {\r\n const selectedPrompt = props.prompts.at(selectIndex);\r\n if (selectedPrompt) {\r\n props.onPromptSelect(selectedPrompt);\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener(\"keydown\", onKeyDown);\r\n\r\n return () => window.removeEventListener(\"keydown\", onKeyDown);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [props.prompts.length, selectIndex]);\r\n\r\n if (noPrompts) return null;\r\n return (\r\n <div className={styles[\"prompt-hints\"]}>\r\n {props.prompts.map((prompt, i) => (\r\n <div\r\n ref={i === selectIndex ? selectedRef : null}\r\n className={clsx(styles[\"prompt-hint\"], {\r\n [styles[\"prompt-hint-selected\"]]: i === selectIndex,\r\n })}\r\n key={prompt.title + i.toString()}\r\n onClick={() => props.onPromptSelect(prompt)}\r\n onMouseEnter={() => setSelectIndex(i)}\r\n >\r\n <div className={styles[\"hint-title\"]}>{prompt.title}</div>\r\n <div className={styles[\"hint-content\"]}>{prompt.content}</div>\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\nfunction ClearContextDivider() {\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n\r\n return (\r\n <div\r\n className={styles[\"clear-context\"]}\r\n onClick={() =>\r\n chatStore.updateTargetSession(\r\n session,\r\n (session) => (session.clearContextIndex = undefined),\r\n )\r\n }\r\n >\r\n <div className={styles[\"clear-context-tips\"]}>{Locale.Context.Clear}</div>\r\n <div className={styles[\"clear-context-revert-btn\"]}>\r\n {Locale.Context.Revert}\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport function ChatAction(props: {\r\n text: string;\r\n icon: JSX.Element;\r\n onClick: () => void;\r\n}) {\r\n const iconRef = useRef<HTMLDivElement>(null);\r\n const textRef = useRef<HTMLDivElement>(null);\r\n const [width, setWidth] = useState({\r\n full: 16,\r\n icon: 16,\r\n });\r\n\r\n function updateWidth() {\r\n if (!iconRef.current || !textRef.current) return;\r\n const getWidth = (dom: HTMLDivElement) => dom.getBoundingClientRect().width;\r\n const textWidth = getWidth(textRef.current);\r\n const iconWidth = getWidth(iconRef.current);\r\n setWidth({\r\n full: textWidth + iconWidth,\r\n icon: iconWidth,\r\n });\r\n }\r\n\r\n return (\r\n <div\r\n className={clsx(styles[\"chat-input-action\"], \"clickable\")}\r\n onClick={() => {\r\n props.onClick();\r\n setTimeout(updateWidth, 1);\r\n }}\r\n onMouseEnter={updateWidth}\r\n onTouchStart={updateWidth}\r\n style={\r\n {\r\n \"--icon-width\": `${width.icon}px`,\r\n \"--full-width\": `${width.full}px`,\r\n } as React.CSSProperties\r\n }\r\n >\r\n <div ref={iconRef} className={styles[\"icon\"]}>\r\n {props.icon}\r\n </div>\r\n <div className={styles[\"text\"]} ref={textRef}>\r\n {props.text}\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nfunction useScrollToBottom(\r\n scrollRef: RefObject<HTMLDivElement>,\r\n detach: boolean = false,\r\n messages: ChatMessage[],\r\n) {\r\n // for auto-scroll\r\n const [autoScroll, setAutoScroll] = useState(true);\r\n const scrollDomToBottom = useCallback(() => {\r\n const dom = scrollRef.current;\r\n if (dom) {\r\n requestAnimationFrame(() => {\r\n setAutoScroll(true);\r\n dom.scrollTo(0, dom.scrollHeight);\r\n });\r\n }\r\n }, [scrollRef]);\r\n\r\n // auto scroll\r\n useEffect(() => {\r\n if (autoScroll && !detach) {\r\n scrollDomToBottom();\r\n }\r\n });\r\n\r\n // auto scroll when messages length changes\r\n const lastMessagesLength = useRef(messages.length);\r\n useEffect(() => {\r\n if (messages.length > lastMessagesLength.current && !detach) {\r\n scrollDomToBottom();\r\n }\r\n lastMessagesLength.current = messages.length;\r\n }, [messages.length, detach, scrollDomToBottom]);\r\n\r\n return {\r\n scrollRef,\r\n autoScroll,\r\n setAutoScroll,\r\n scrollDomToBottom,\r\n };\r\n}\r\n\r\nexport function ChatActions(props: {\r\n uploadImage: () => void;\r\n setAttachImages: (images: string[]) => void;\r\n setUploading: (uploading: boolean) => void;\r\n showPromptModal: () => void;\r\n scrollToBottom: () => void;\r\n showPromptHints: () => void;\r\n hitBottom: boolean;\r\n uploading: boolean;\r\n setShowShortcutKeyModal: React.Dispatch<React.SetStateAction<boolean>>;\r\n setUserInput: (input: string) => void;\r\n setShowChatSidePanel: React.Dispatch<React.SetStateAction<boolean>>;\r\n}) {\r\n const config = useAppConfig();\r\n const navigate = useNavigate();\r\n const chatStore = useChatStore();\r\n const pluginStore = usePluginStore();\r\n const session = chatStore.currentSession();\r\n\r\n // switch themes\r\n const theme = config.theme;\r\n\r\n function nextTheme() {\r\n const themes = [Theme.Auto, Theme.Light, Theme.Dark];\r\n const themeIndex = themes.indexOf(theme);\r\n const nextIndex = (themeIndex + 1) % themes.length;\r\n const nextTheme = themes[nextIndex];\r\n config.update((config) => (config.theme = nextTheme));\r\n }\r\n\r\n // stop all responses\r\n const couldStop = ChatControllerPool.hasPending();\r\n const stopAll = () => ChatControllerPool.stopAll();\r\n\r\n // switch model\r\n const currentModel = session.mask.modelConfig.model;\r\n const currentProviderName =\r\n session.mask.modelConfig?.providerName || ServiceProvider.OpenAI;\r\n const allModels = useAllModels();\r\n const models = useMemo(() => {\r\n const filteredModels = allModels.filter((m) => m.available);\r\n const defaultModel = filteredModels.find((m) => m.isDefault);\r\n\r\n if (defaultModel) {\r\n const arr = [\r\n defaultModel,\r\n ...filteredModels.filter((m) => m !== defaultModel),\r\n ];\r\n return arr;\r\n } else {\r\n return filteredModels;\r\n }\r\n }, [allModels]);\r\n const currentModelName = useMemo(() => {\r\n const model = models.find(\r\n (m) =>\r\n m.name == currentModel &&\r\n m?.provider?.providerName == currentProviderName,\r\n );\r\n return model?.displayName ?? \"\";\r\n }, [models, currentModel, currentProviderName]);\r\n const [showModelSelector, setShowModelSelector] = useState(false);\r\n const [showPluginSelector, setShowPluginSelector] = useState(false);\r\n const [showUploadImage, setShowUploadImage] = useState(false);\r\n\r\n const [showSizeSelector, setShowSizeSelector] = useState(false);\r\n const [showQualitySelector, setShowQualitySelector] = useState(false);\r\n const [showStyleSelector, setShowStyleSelector] = useState(false);\r\n const modelSizes = getModelSizes(currentModel);\r\n const dalle3Qualitys: DalleQuality[] = [\"standard\", \"hd\"];\r\n const dalle3Styles: DalleStyle[] = [\"vivid\", \"natural\"];\r\n const currentSize =\r\n session.mask.modelConfig?.size ?? (\"1024x1024\" as ModelSize);\r\n const currentQuality = session.mask.modelConfig?.quality ?? \"standard\";\r\n const currentStyle = session.mask.modelConfig?.style ?? \"vivid\";\r\n\r\n const isMobileScreen = useMobileScreen();\r\n\r\n useEffect(() => {\r\n const show = isVisionModel(currentModel);\r\n setShowUploadImage(show);\r\n if (!show) {\r\n props.setAttachImages([]);\r\n props.setUploading(false);\r\n }\r\n\r\n // if current model is not available\r\n // switch to first available model\r\n const isUnavailableModel = !models.some((m) => m.name === currentModel);\r\n if (isUnavailableModel && models.length > 0) {\r\n // show next model to default model if exist\r\n let nextModel = models.find((model) => model.isDefault) || models[0];\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.mask.modelConfig.model = nextModel.name;\r\n session.mask.modelConfig.providerName = nextModel?.provider\r\n ?.providerName as ServiceProvider;\r\n });\r\n showToast(\r\n nextModel?.provider?.providerName == \"ByteDance\"\r\n ? nextModel.displayName\r\n : nextModel.name,\r\n );\r\n }\r\n }, [chatStore, currentModel, models, session]);\r\n\r\n return (\r\n <div className={styles[\"chat-input-actions\"]}>\r\n <>\r\n {couldStop && (\r\n <ChatAction\r\n onClick={stopAll}\r\n text={Locale.Chat.InputActions.Stop}\r\n icon={<StopIcon />}\r\n />\r\n )}\r\n {!props.hitBottom && (\r\n <ChatAction\r\n onClick={props.scrollToBottom}\r\n text={Locale.Chat.InputActions.ToBottom}\r\n icon={<BottomIcon />}\r\n />\r\n )}\r\n {props.hitBottom && (\r\n <ChatAction\r\n onClick={props.showPromptModal}\r\n text={Locale.Chat.InputActions.Settings}\r\n icon={<SettingsIcon />}\r\n />\r\n )}\r\n\r\n {showUploadImage && (\r\n <ChatAction\r\n onClick={props.uploadImage}\r\n text={Locale.Chat.InputActions.UploadImage}\r\n icon={props.uploading ? <LoadingButtonIcon /> : <ImageIcon />}\r\n />\r\n )}\r\n\r\n <ChatAction\r\n onClick={() => setShowModelSelector(true)}\r\n text={currentModelName}\r\n icon={<RobotIcon />}\r\n />\r\n\r\n {showModelSelector && (\r\n <Selector\r\n defaultSelectedValue={`${currentModel}@${currentProviderName}`}\r\n items={models.map((m) => ({\r\n title: `${m.displayName}${\r\n m?.provider?.providerName\r\n ? \" (\" + m?.provider?.providerName + \")\"\r\n : \"\"\r\n }`,\r\n value: `${m.name}@${m?.provider?.providerName}`,\r\n }))}\r\n onClose={() => setShowModelSelector(false)}\r\n onSelection={(s) => {\r\n if (s.length === 0) return;\r\n const [model, providerName] = getModelProvider(s[0]);\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.mask.modelConfig.model = model as ModelType;\r\n session.mask.modelConfig.providerName =\r\n providerName as ServiceProvider;\r\n session.mask.syncGlobalConfig = false;\r\n });\r\n if (providerName == \"ByteDance\") {\r\n const selectedModel = models.find(\r\n (m) =>\r\n m.name == model &&\r\n m?.provider?.providerName == providerName,\r\n );\r\n showToast(selectedModel?.displayName ?? \"\");\r\n } else {\r\n showToast(model);\r\n }\r\n }}\r\n />\r\n )}\r\n\r\n {supportsCustomSize(currentModel) && (\r\n <ChatAction\r\n onClick={() => setShowSizeSelector(true)}\r\n text={currentSize}\r\n icon={<SizeIcon />}\r\n />\r\n )}\r\n\r\n {showSizeSelector && (\r\n <Selector\r\n defaultSelectedValue={currentSize}\r\n items={modelSizes.map((m) => ({\r\n title: m,\r\n value: m,\r\n }))}\r\n onClose={() => setShowSizeSelector(false)}\r\n onSelection={(s) => {\r\n if (s.length === 0) return;\r\n const size = s[0];\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.mask.modelConfig.size = size;\r\n });\r\n showToast(size);\r\n }}\r\n />\r\n )}\r\n\r\n {isDalle3(currentModel) && (\r\n <ChatAction\r\n onClick={() => setShowQualitySelector(true)}\r\n text={currentQuality}\r\n icon={<QualityIcon />}\r\n />\r\n )}\r\n\r\n {showQualitySelector && (\r\n <Selector\r\n defaultSelectedValue={currentQuality}\r\n items={dalle3Qualitys.map((m) => ({\r\n title: m,\r\n value: m,\r\n }))}\r\n onClose={() => setShowQualitySelector(false)}\r\n onSelection={(q) => {\r\n if (q.length === 0) return;\r\n const quality = q[0];\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.mask.modelConfig.quality = quality;\r\n });\r\n showToast(quality);\r\n }}\r\n />\r\n )}\r\n\r\n {isDalle3(currentModel) && (\r\n <ChatAction\r\n onClick={() => setShowStyleSelector(true)}\r\n text={currentStyle}\r\n icon={<StyleIcon />}\r\n />\r\n )}\r\n\r\n {showStyleSelector && (\r\n <Selector\r\n defaultSelectedValue={currentStyle}\r\n items={dalle3Styles.map((m) => ({\r\n title: m,\r\n value: m,\r\n }))}\r\n onClose={() => setShowStyleSelector(false)}\r\n onSelection={(s) => {\r\n if (s.length === 0) return;\r\n const style = s[0];\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.mask.modelConfig.style = style;\r\n });\r\n showToast(style);\r\n }}\r\n />\r\n )}\r\n\r\n {showPlugins(currentProviderName, currentModel) && (\r\n <ChatAction\r\n onClick={() => {\r\n if (pluginStore.getAll().length == 0) {\r\n navigate(Path.Plugins);\r\n } else {\r\n setShowPluginSelector(true);\r\n }\r\n }}\r\n text={Locale.Plugin.Name}\r\n icon={<PluginIcon />}\r\n />\r\n )}\r\n {showPluginSelector && (\r\n <Selector\r\n multiple\r\n defaultSelectedValue={chatStore.currentSession().mask?.plugin}\r\n items={pluginStore.getAll().map((item) => ({\r\n title: `${item?.title}@${item?.version}`,\r\n value: item?.id,\r\n }))}\r\n onClose={() => setShowPluginSelector(false)}\r\n onSelection={(s) => {\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.mask.plugin = s as string[];\r\n });\r\n }}\r\n />\r\n )}\r\n\r\n </>\r\n <div className={styles[\"chat-input-actions-end\"]}>\r\n {config.realtimeConfig.enable && (\r\n <ChatAction\r\n onClick={() => props.setShowChatSidePanel(true)}\r\n text={\"Realtime Chat\"}\r\n icon={<HeadphoneIcon />}\r\n />\r\n )}\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport function EditMessageModal(props: { onClose: () => void }) {\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n const [messages, setMessages] = useState(session.messages.slice());\r\n\r\n return (\r\n <div className=\"modal-mask\">\r\n <Modal\r\n title={Locale.Chat.EditMessage.Title}\r\n onClose={props.onClose}\r\n actions={[\r\n <IconButton\r\n text={Locale.UI.Cancel}\r\n icon={<CancelIcon />}\r\n key=\"cancel\"\r\n onClick={() => {\r\n props.onClose();\r\n }}\r\n />,\r\n <IconButton\r\n type=\"primary\"\r\n text={Locale.UI.Confirm}\r\n icon={<ConfirmIcon />}\r\n key=\"ok\"\r\n onClick={() => {\r\n chatStore.updateTargetSession(\r\n session,\r\n (session) => (session.messages = messages),\r\n );\r\n props.onClose();\r\n }}\r\n />,\r\n ]}\r\n >\r\n <List>\r\n <ListItem\r\n title={Locale.Chat.EditMessage.Topic.Title}\r\n subTitle={Locale.Chat.EditMessage.Topic.SubTitle}\r\n >\r\n <input\r\n type=\"text\"\r\n value={session.topic}\r\n onInput={(e) =>\r\n chatStore.updateTargetSession(\r\n session,\r\n (session) => (session.topic = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n </List>\r\n <ContextPrompts\r\n context={messages}\r\n updateContext={(updater) => {\r\n const newMessages = messages.slice();\r\n updater(newMessages);\r\n setMessages(newMessages);\r\n }}\r\n />\r\n </Modal>\r\n </div>\r\n );\r\n}\r\n\r\nexport function DeleteImageButton(props: { deleteImage: () => void }) {\r\n return (\r\n <div className={styles[\"delete-image\"]} onClick={props.deleteImage}>\r\n <DeleteIcon />\r\n </div>\r\n );\r\n}\r\n\r\nexport function ShortcutKeyModal(props: { onClose: () => void }) {\r\n const isMac = navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0;\r\n const shortcuts = [\r\n {\r\n title: Locale.Chat.ShortcutKey.newChat,\r\n keys: isMac ? [\"⌘\", \"Shift\", \"O\"] : [\"Ctrl\", \"Shift\", \"O\"],\r\n },\r\n { title: Locale.Chat.ShortcutKey.focusInput, keys: [\"Shift\", \"Esc\"] },\r\n {\r\n title: Locale.Chat.ShortcutKey.copyLastCode,\r\n keys: isMac ? [\"⌘\", \"Shift\", \";\"] : [\"Ctrl\", \"Shift\", \";\"],\r\n },\r\n {\r\n title: Locale.Chat.ShortcutKey.copyLastMessage,\r\n keys: isMac ? [\"⌘\", \"Shift\", \"C\"] : [\"Ctrl\", \"Shift\", \"C\"],\r\n },\r\n {\r\n title: Locale.Chat.ShortcutKey.showShortcutKey,\r\n keys: isMac ? [\"⌘\", \"/\"] : [\"Ctrl\", \"/\"],\r\n },\r\n {\r\n title: Locale.Chat.ShortcutKey.clearContext,\r\n keys: isMac\r\n ? [\"⌘\", \"Shift\", \"backspace\"]\r\n : [\"Ctrl\", \"Shift\", \"backspace\"],\r\n },\r\n ];\r\n return (\r\n <div className=\"modal-mask\">\r\n <Modal\r\n title={Locale.Chat.ShortcutKey.Title}\r\n onClose={props.onClose}\r\n actions={[\r\n <IconButton\r\n type=\"primary\"\r\n text={Locale.UI.Confirm}\r\n icon={<ConfirmIcon />}\r\n key=\"ok\"\r\n onClick={() => {\r\n props.onClose();\r\n }}\r\n />,\r\n ]}\r\n >\r\n <div className={styles[\"shortcut-key-container\"]}>\r\n <div className={styles[\"shortcut-key-grid\"]}>\r\n {shortcuts.map((shortcut, index) => (\r\n <div key={index} className={styles[\"shortcut-key-item\"]}>\r\n <div className={styles[\"shortcut-key-title\"]}>\r\n {shortcut.title}\r\n </div>\r\n <div className={styles[\"shortcut-key-keys\"]}>\r\n {shortcut.keys.map((key, i) => (\r\n <div key={i} className={styles[\"shortcut-key\"]}>\r\n <span>{key}</span>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </Modal>\r\n </div>\r\n );\r\n}\r\n\r\nfunction _Chat() {\r\n type RenderMessage = ChatMessage & { preview?: boolean };\r\n\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n const config = useAppConfig();\r\n const fontSize = config.fontSize;\r\n const fontFamily = config.fontFamily;\r\n\r\n const [showExport, setShowExport] = useState(false);\r\n\r\n const inputRef = useRef<HTMLTextAreaElement>(null);\r\n const [userInput, setUserInput] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const { submitKey, shouldSubmit } = useSubmitHandler();\r\n const scrollRef = useRef<HTMLDivElement>(null);\r\n const isScrolledToBottom = scrollRef?.current\r\n ? Math.abs(\r\n scrollRef.current.scrollHeight -\r\n (scrollRef.current.scrollTop + scrollRef.current.clientHeight),\r\n ) <= 1\r\n : false;\r\n const isAttachWithTop = useMemo(() => {\r\n const lastMessage = scrollRef.current?.lastElementChild as HTMLElement;\r\n // if scrolllRef is not ready or no message, return false\r\n if (!scrollRef?.current || !lastMessage) return false;\r\n const topDistance =\r\n lastMessage!.getBoundingClientRect().top -\r\n scrollRef.current.getBoundingClientRect().top;\r\n // leave some space for user question\r\n return topDistance < 100;\r\n }, [scrollRef?.current?.scrollHeight]);\r\n\r\n const isTyping = userInput !== \"\";\r\n\r\n // if user is typing, should auto scroll to bottom\r\n // if user is not typing, should auto scroll to bottom only if already at bottom\r\n const { setAutoScroll, scrollDomToBottom } = useScrollToBottom(\r\n scrollRef,\r\n (isScrolledToBottom || isAttachWithTop) && !isTyping,\r\n session.messages,\r\n );\r\n const [hitBottom, setHitBottom] = useState(true);\r\n const isMobileScreen = useMobileScreen();\r\n const navigate = useNavigate();\r\n const [attachImages, setAttachImages] = useState<string[]>([]);\r\n const [uploading, setUploading] = useState(false);\r\n\r\n // prompt hints\r\n const promptStore = usePromptStore();\r\n const [promptHints, setPromptHints] = useState<RenderPrompt[]>([]);\r\n const onSearch = useDebouncedCallback(\r\n (text: string) => {\r\n const matchedPrompts = promptStore.search(text);\r\n setPromptHints(matchedPrompts);\r\n },\r\n 100,\r\n { leading: true, trailing: true },\r\n );\r\n\r\n // auto grow input\r\n const [inputRows, setInputRows] = useState(2);\r\n const measure = useDebouncedCallback(\r\n () => {\r\n const rows = inputRef.current ? autoGrowTextArea(inputRef.current) : 1;\r\n const inputRows = Math.min(\r\n 20,\r\n Math.max(2 + Number(!isMobileScreen), rows),\r\n );\r\n setInputRows(inputRows);\r\n },\r\n 100,\r\n {\r\n leading: true,\r\n trailing: true,\r\n },\r\n );\r\n\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n useEffect(measure, [userInput]);\r\n\r\n // chat commands shortcuts\r\n const chatCommands = useChatCommand({\r\n new: () => chatStore.newSession(),\r\n newm: () => navigate(Path.NewChat),\r\n prev: () => chatStore.nextSession(-1),\r\n next: () => chatStore.nextSession(1),\r\n clear: () =>\r\n chatStore.updateTargetSession(\r\n session,\r\n (session) => (session.clearContextIndex = session.messages.length),\r\n ),\r\n fork: () => chatStore.forkSession(),\r\n del: () => chatStore.deleteSession(chatStore.currentSessionIndex),\r\n });\r\n\r\n // only search prompts when user input is short\r\n const SEARCH_TEXT_LIMIT = 30;\r\n const onInput = (text: string) => {\r\n setUserInput(text);\r\n const n = text.trim().length;\r\n\r\n // clear search results\r\n if (n === 0) {\r\n setPromptHints([]);\r\n } else if (text.match(ChatCommandPrefix)) {\r\n setPromptHints(chatCommands.search(text));\r\n } else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {\r\n // check if need to trigger auto completion\r\n if (text.startsWith(\"/\")) {\r\n let searchText = text.slice(1);\r\n onSearch(searchText);\r\n }\r\n }\r\n };\r\n\r\n const doSubmit = (userInput: string) => {\r\n if (userInput.trim() === \"\" && isEmpty(attachImages)) return;\r\n const matchCommand = chatCommands.match(userInput);\r\n if (matchCommand.matched) {\r\n setUserInput(\"\");\r\n setPromptHints([]);\r\n matchCommand.invoke();\r\n return;\r\n }\r\n setIsLoading(true);\r\n chatStore\r\n .onUserInput(userInput, attachImages)\r\n .then(() => setIsLoading(false));\r\n setAttachImages([]);\r\n chatStore.setLastInput(userInput);\r\n setUserInput(\"\");\r\n setPromptHints([]);\r\n if (!isMobileScreen) inputRef.current?.focus();\r\n setAutoScroll(true);\r\n };\r\n\r\n const onPromptSelect = (prompt: RenderPrompt) => {\r\n setTimeout(() => {\r\n setPromptHints([]);\r\n\r\n const matchedChatCommand = chatCommands.match(prompt.content);\r\n if (matchedChatCommand.matched) {\r\n // if user is selecting a chat command, just trigger it\r\n matchedChatCommand.invoke();\r\n setUserInput(\"\");\r\n } else {\r\n // or fill the prompt\r\n setUserInput(prompt.content);\r\n }\r\n inputRef.current?.focus();\r\n }, 30);\r\n };\r\n\r\n // stop response\r\n const onUserStop = (messageId: string) => {\r\n ChatControllerPool.stop(session.id, messageId);\r\n };\r\n\r\n useEffect(() => {\r\n chatStore.updateTargetSession(session, (session) => {\r\n const stopTiming = Date.now() - REQUEST_TIMEOUT_MS;\r\n session.messages.forEach((m) => {\r\n // check if should stop all stale messages\r\n if (m.isError || new Date(m.date).getTime() < stopTiming) {\r\n if (m.streaming) {\r\n m.streaming = false;\r\n }\r\n\r\n if (m.content.length === 0) {\r\n m.isError = true;\r\n m.content = prettyObject({\r\n error: true,\r\n message: \"empty response\",\r\n });\r\n }\r\n }\r\n });\r\n\r\n // auto sync mask config from global config\r\n if (session.mask.syncGlobalConfig) {\r\n console.log(\"[Mask] syncing from global, name = \", session.mask.name);\r\n session.mask.modelConfig = { ...config.modelConfig };\r\n }\r\n });\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [session]);\r\n\r\n // check if should send message\r\n const onInputKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\r\n // if ArrowUp and no userInput, fill with last input\r\n if (\r\n e.key === \"ArrowUp\" &&\r\n userInput.length <= 0 &&\r\n !(e.metaKey || e.altKey || e.ctrlKey)\r\n ) {\r\n setUserInput(chatStore.lastInput ?? \"\");\r\n e.preventDefault();\r\n return;\r\n }\r\n if (shouldSubmit(e) && promptHints.length === 0) {\r\n doSubmit(userInput);\r\n e.preventDefault();\r\n }\r\n };\r\n const onRightClick = (e: any, message: ChatMessage) => {\r\n // copy to clipboard\r\n if (selectOrCopy(e.currentTarget, getMessageTextContent(message))) {\r\n if (userInput.length === 0) {\r\n setUserInput(getMessageTextContent(message));\r\n }\r\n\r\n e.preventDefault();\r\n }\r\n };\r\n\r\n const deleteMessage = (msgId?: string) => {\r\n chatStore.updateTargetSession(\r\n session,\r\n (session) =>\r\n (session.messages = session.messages.filter((m) => m.id !== msgId)),\r\n );\r\n };\r\n\r\n const onDelete = (msgId: string) => {\r\n deleteMessage(msgId);\r\n };\r\n\r\n const onResend = (message: ChatMessage) => {\r\n // when it is resending a message\r\n // 1. for a user's message, find the next bot response\r\n // 2. for a bot's message, find the last user's input\r\n // 3. delete original user input and bot's message\r\n // 4. resend the user's input\r\n\r\n const resendingIndex = session.messages.findIndex(\r\n (m) => m.id === message.id,\r\n );\r\n\r\n if (resendingIndex < 0 || resendingIndex >= session.messages.length) {\r\n console.error(\"[Chat] failed to find resending message\", message);\r\n return;\r\n }\r\n\r\n let userMessage: ChatMessage | undefined;\r\n let botMessage: ChatMessage | undefined;\r\n\r\n if (message.role === \"assistant\") {\r\n // if it is resending a bot's message, find the user input for it\r\n botMessage = message;\r\n for (let i = resendingIndex; i >= 0; i -= 1) {\r\n if (session.messages[i].role === \"user\") {\r\n userMessage = session.messages[i];\r\n break;\r\n }\r\n }\r\n } else if (message.role === \"user\") {\r\n // if it is resending a user's input, find the bot's response\r\n userMessage = message;\r\n for (let i = resendingIndex; i < session.messages.length; i += 1) {\r\n if (session.messages[i].role === \"assistant\") {\r\n botMessage = session.messages[i];\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (userMessage === undefined) {\r\n console.error(\"[Chat] failed to resend\", message);\r\n return;\r\n }\r\n\r\n // delete the original messages\r\n deleteMessage(userMessage.id);\r\n deleteMessage(botMessage?.id);\r\n\r\n // resend the message\r\n setIsLoading(true);\r\n const textContent = getMessageTextContent(userMessage);\r\n const images = getMessageImages(userMessage);\r\n chatStore.onUserInput(textContent, images).then(() => setIsLoading(false));\r\n inputRef.current?.focus();\r\n };\r\n\r\n const onPinMessage = (message: ChatMessage) => {\r\n chatStore.updateTargetSession(session, (session) =>\r\n session.mask.context.push(message),\r\n );\r\n\r\n showToast(Locale.Chat.Actions.PinToastContent, {\r\n text: Locale.Chat.Actions.PinToastAction,\r\n onClick: () => {\r\n setShowPromptModal(true);\r\n },\r\n });\r\n };\r\n\r\n const accessStore = useAccessStore();\r\n const [speechStatus, setSpeechStatus] = useState(false);\r\n const [speechLoading, setSpeechLoading] = useState(false);\r\n\r\n async function openaiSpeech(text: string) {\r\n if (speechStatus) {\r\n ttsPlayer.stop();\r\n setSpeechStatus(false);\r\n } else {\r\n var api: ClientApi;\r\n api = new ClientApi(ModelProvider.GPT);\r\n const config = useAppConfig.getState();\r\n setSpeechLoading(true);\r\n ttsPlayer.init();\r\n let audioBuffer: ArrayBuffer;\r\n const { markdownToTxt } = require(\"markdown-to-txt\");\r\n const textContent = markdownToTxt(text);\r\n if (config.ttsConfig.engine !== DEFAULT_TTS_ENGINE) {\r\n const edgeVoiceName = accessStore.edgeVoiceName();\r\n const tts = new MsEdgeTTS();\r\n await tts.setMetadata(\r\n edgeVoiceName,\r\n OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3,\r\n );\r\n audioBuffer = await tts.toArrayBuffer(textContent);\r\n } else {\r\n audioBuffer = await api.llm.speech({\r\n model: config.ttsConfig.model,\r\n input: textContent,\r\n voice: config.ttsConfig.voice,\r\n speed: config.ttsConfig.speed,\r\n });\r\n }\r\n setSpeechStatus(true);\r\n ttsPlayer\r\n .play(audioBuffer, () => {\r\n setSpeechStatus(false);\r\n })\r\n .catch((e) => {\r\n console.error(\"[OpenAI Speech]\", e);\r\n showToast(prettyObject(e));\r\n setSpeechStatus(false);\r\n })\r\n .finally(() => setSpeechLoading(false));\r\n }\r\n }\r\n\r\n const context: RenderMessage[] = useMemo(() => {\r\n return session.mask.hideContext ? [] : session.mask.context.slice();\r\n }, [session.mask.context, session.mask.hideContext]);\r\n\r\n if (\r\n context.length === 0 &&\r\n session.messages.at(0)?.content !== BOT_HELLO.content\r\n ) {\r\n const copiedHello = Object.assign({}, BOT_HELLO);\r\n if (!accessStore.isAuthorized()) {\r\n copiedHello.content = Locale.Error.Unauthorized;\r\n }\r\n context.push(copiedHello);\r\n }\r\n\r\n // preview messages\r\n const renderMessages = useMemo(() => {\r\n return context\r\n .concat(session.messages as RenderMessage[])\r\n .concat(\r\n isLoading\r\n ? [\r\n {\r\n ...createMessage({\r\n role: \"assistant\",\r\n content: \"……\",\r\n }),\r\n preview: true,\r\n },\r\n ]\r\n : [],\r\n )\r\n .concat(\r\n userInput.length > 0 && config.sendPreviewBubble\r\n ? [\r\n {\r\n ...createMessage({\r\n role: \"user\",\r\n content: userInput,\r\n }),\r\n preview: true,\r\n },\r\n ]\r\n : [],\r\n );\r\n }, [\r\n config.sendPreviewBubble,\r\n context,\r\n isLoading,\r\n session.messages,\r\n userInput,\r\n ]);\r\n\r\n const [msgRenderIndex, _setMsgRenderIndex] = useState(\r\n Math.max(0, renderMessages.length - CHAT_PAGE_SIZE),\r\n );\r\n\r\n function setMsgRenderIndex(newIndex: number) {\r\n newIndex = Math.min(renderMessages.length - CHAT_PAGE_SIZE, newIndex);\r\n newIndex = Math.max(0, newIndex);\r\n _setMsgRenderIndex(newIndex);\r\n }\r\n\r\n const messages = useMemo(() => {\r\n const endRenderIndex = Math.min(\r\n msgRenderIndex + 3 * CHAT_PAGE_SIZE,\r\n renderMessages.length,\r\n );\r\n return renderMessages.slice(msgRenderIndex, endRenderIndex);\r\n }, [msgRenderIndex, renderMessages]);\r\n\r\n const onChatBodyScroll = (e: HTMLElement) => {\r\n const bottomHeight = e.scrollTop + e.clientHeight;\r\n const edgeThreshold = e.clientHeight;\r\n\r\n const isTouchTopEdge = e.scrollTop <= edgeThreshold;\r\n const isTouchBottomEdge = bottomHeight >= e.scrollHeight - edgeThreshold;\r\n const isHitBottom =\r\n bottomHeight >= e.scrollHeight - (isMobileScreen ? 4 : 10);\r\n\r\n const prevPageMsgIndex = msgRenderIndex - CHAT_PAGE_SIZE;\r\n const nextPageMsgIndex = msgRenderIndex + CHAT_PAGE_SIZE;\r\n\r\n if (isTouchTopEdge && !isTouchBottomEdge) {\r\n setMsgRenderIndex(prevPageMsgIndex);\r\n } else if (isTouchBottomEdge) {\r\n setMsgRenderIndex(nextPageMsgIndex);\r\n }\r\n\r\n setHitBottom(isHitBottom);\r\n setAutoScroll(isHitBottom);\r\n };\r\n\r\n function scrollToBottom() {\r\n setMsgRenderIndex(renderMessages.length - CHAT_PAGE_SIZE);\r\n scrollDomToBottom();\r\n }\r\n\r\n // clear context index = context length + index in messages\r\n const clearContextIndex =\r\n (session.clearContextIndex ?? -1) >= 0\r\n ? session.clearContextIndex! + context.length - msgRenderIndex\r\n : -1;\r\n\r\n const [showPromptModal, setShowPromptModal] = useState(false);\r\n\r\n const clientConfig = useMemo(() => getClientConfig(), []);\r\n\r\n const autoFocus = !isMobileScreen; // wont auto focus on mobile screen\r\n const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;\r\n\r\n useCommand({\r\n fill: setUserInput,\r\n submit: (text) => {\r\n doSubmit(text);\r\n },\r\n code: (text) => {\r\n if (accessStore.disableFastLink) return;\r\n console.log(\"[Command] got code from url: \", text);\r\n showConfirm(Locale.URLCommand.Code + `code = ${text}`).then((res) => {\r\n if (res) {\r\n accessStore.update((access) => (access.accessCode = text));\r\n }\r\n });\r\n },\r\n settings: (text) => {\r\n if (accessStore.disableFastLink) return;\r\n\r\n try {\r\n const payload = JSON.parse(text) as {\r\n key?: string;\r\n url?: string;\r\n };\r\n\r\n console.log(\"[Command] got settings from url: \", payload);\r\n\r\n if (payload.key || payload.url) {\r\n showConfirm(\r\n Locale.URLCommand.Settings +\r\n `\\n${JSON.stringify(payload, null, 4)}`,\r\n ).then((res) => {\r\n if (!res) return;\r\n if (payload.key) {\r\n accessStore.update(\r\n (access) => (access.openaiApiKey = payload.key!),\r\n );\r\n }\r\n if (payload.url) {\r\n accessStore.update((access) => (access.openaiUrl = payload.url!));\r\n }\r\n accessStore.update((access) => (access.useCustomConfig = true));\r\n });\r\n }\r\n } catch {\r\n console.error(\"[Command] failed to get settings from url: \", text);\r\n }\r\n },\r\n });\r\n\r\n // edit / insert message modal\r\n const [isEditingMessage, setIsEditingMessage] = useState(false);\r\n\r\n // remember unfinished input\r\n useEffect(() => {\r\n // try to load from local storage\r\n const key = UNFINISHED_INPUT(session.id);\r\n const mayBeUnfinishedInput = localStorage.getItem(key);\r\n if (mayBeUnfinishedInput && userInput.length === 0) {\r\n setUserInput(mayBeUnfinishedInput);\r\n localStorage.removeItem(key);\r\n }\r\n\r\n const dom = inputRef.current;\r\n return () => {\r\n localStorage.setItem(key, dom?.value ?? \"\");\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n const handlePaste = useCallback(\r\n async (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\r\n const currentModel = chatStore.currentSession().mask.modelConfig.model;\r\n if (!isVisionModel(currentModel)) {\r\n return;\r\n }\r\n const items = (event.clipboardData || window.clipboardData).items;\r\n for (const item of items) {\r\n if (item.kind === \"file\" && item.type.startsWith(\"image/\")) {\r\n event.preventDefault();\r\n const file = item.getAsFile();\r\n if (file) {\r\n const images: string[] = [];\r\n images.push(...attachImages);\r\n images.push(\r\n ...(await new Promise<string[]>((res, rej) => {\r\n setUploading(true);\r\n const imagesData: string[] = [];\r\n uploadImageRemote(file)\r\n .then((dataUrl) => {\r\n imagesData.push(dataUrl);\r\n setUploading(false);\r\n res(imagesData);\r\n })\r\n .catch((e) => {\r\n setUploading(false);\r\n rej(e);\r\n });\r\n })),\r\n );\r\n const imagesLength = images.length;\r\n\r\n if (imagesLength > 3) {\r\n images.splice(3, imagesLength - 3);\r\n }\r\n setAttachImages(images);\r\n }\r\n }\r\n }\r\n },\r\n [attachImages, chatStore],\r\n );\r\n\r\n async function uploadImage() {\r\n const images: string[] = [];\r\n images.push(...attachImages);\r\n\r\n images.push(\r\n ...(await new Promise<string[]>((res, rej) => {\r\n const fileInput = document.createElement(\"input\");\r\n fileInput.type = \"file\";\r\n fileInput.accept =\r\n \"image/png, image/jpeg, image/webp, image/heic, image/heif\";\r\n fileInput.multiple = true;\r\n fileInput.onchange = (event: any) => {\r\n setUploading(true);\r\n const files = event.target.files;\r\n const imagesData: string[] = [];\r\n for (let i = 0; i < files.length; i++) {\r\n const file = event.target.files[i];\r\n uploadImageRemote(file)\r\n .then((dataUrl) => {\r\n imagesData.push(dataUrl);\r\n if (\r\n imagesData.length === 3 ||\r\n imagesData.length === files.length\r\n ) {\r\n setUploading(false);\r\n res(imagesData);\r\n }\r\n })\r\n .catch((e) => {\r\n setUploading(false);\r\n rej(e);\r\n });\r\n }\r\n };\r\n fileInput.click();\r\n })),\r\n );\r\n\r\n const imagesLength = images.length;\r\n if (imagesLength > 3) {\r\n images.splice(3, imagesLength - 3);\r\n }\r\n setAttachImages(images);\r\n }\r\n\r\n // 快捷键 shortcut keys\r\n const [showShortcutKeyModal, setShowShortcutKeyModal] = useState(false);\r\n\r\n useEffect(() => {\r\n const handleKeyDown = (event: KeyboardEvent) => {\r\n // 打开新聊天 command + shift + o\r\n if (\r\n (event.metaKey || event.ctrlKey) &&\r\n event.shiftKey &&\r\n event.key.toLowerCase() === \"o\"\r\n ) {\r\n event.preventDefault();\r\n setTimeout(() => {\r\n chatStore.newSession();\r\n navigate(Path.Chat);\r\n }, 10);\r\n }\r\n // 聚焦聊天输入 shift + esc\r\n else if (event.shiftKey && event.key.toLowerCase() === \"escape\") {\r\n event.preventDefault();\r\n inputRef.current?.focus();\r\n }\r\n // 复制最后一个代码块 command + shift + ;\r\n else if (\r\n (event.metaKey || event.ctrlKey) &&\r\n event.shiftKey &&\r\n event.code === \"Semicolon\"\r\n ) {\r\n event.preventDefault();\r\n const copyCodeButton =\r\n document.querySelectorAll<HTMLElement>(\".copy-code-button\");\r\n if (copyCodeButton.length > 0) {\r\n copyCodeButton[copyCodeButton.length - 1].click();\r\n }\r\n }\r\n // 复制最后一个回复 command + shift + c\r\n else if (\r\n (event.metaKey || event.ctrlKey) &&\r\n event.shiftKey &&\r\n event.key.toLowerCase() === \"c\"\r\n ) {\r\n event.preventDefault();\r\n const lastNonUserMessage = messages\r\n .filter((message) => message.role !== \"user\")\r\n .pop();\r\n if (lastNonUserMessage) {\r\n const lastMessageContent = getMessageTextContent(lastNonUserMessage);\r\n copyToClipboard(lastMessageContent);\r\n }\r\n }\r\n // 展示快捷键 command + /\r\n else if ((event.metaKey || event.ctrlKey) && event.key === \"/\") {\r\n event.preventDefault();\r\n setShowShortcutKeyModal(true);\r\n }\r\n // 清除上下文 command + shift + backspace\r\n else if (\r\n (event.metaKey || event.ctrlKey) &&\r\n event.shiftKey &&\r\n event.key.toLowerCase() === \"backspace\"\r\n ) {\r\n event.preventDefault();\r\n chatStore.updateTargetSession(session, (session) => {\r\n if (session.clearContextIndex === session.messages.length) {\r\n session.clearContextIndex = undefined;\r\n } else {\r\n session.clearContextIndex = session.messages.length;\r\n session.memoryPrompt = \"\"; // will clear memory\r\n }\r\n });\r\n }\r\n };\r\n\r\n document.addEventListener(\"keydown\", handleKeyDown);\r\n\r\n return () => {\r\n document.removeEventListener(\"keydown\", handleKeyDown);\r\n };\r\n }, [messages, chatStore, navigate, session]);\r\n\r\n const [showChatSidePanel, setShowChatSidePanel] = useState(false);\r\n\r\n return (\r\n <>\r\n <div className={styles.chat} key={session.id}>\r\n <div className=\"window-header\" data-tauri-drag-region>\r\n {isMobileScreen && (\r\n <div className=\"window-actions\">\r\n <div className={\"window-action-button\"}>\r\n <IconButton\r\n icon={<ReturnIcon />}\r\n bordered\r\n title={Locale.Chat.Actions.ChatList}\r\n onClick={() => navigate(Path.Home)}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n\r\n <div\r\n className={clsx(\"window-header-title\", styles[\"chat-body-title\"])}\r\n >\r\n <div\r\n className={clsx(\r\n \"window-header-main-title\",\r\n styles[\"chat-body-main-title\"],\r\n )}\r\n onClickCapture={() => setIsEditingMessage(true)}\r\n >\r\n {!session.topic ? DEFAULT_TOPIC : session.topic}\r\n </div>\r\n <div className=\"window-header-sub-title\">\r\n {Locale.Chat.SubTitle(session.messages.length)}\r\n </div>\r\n </div>\r\n <div className=\"window-actions\">\r\n <div className=\"window-action-button\">\r\n <IconButton\r\n icon={<ReloadIcon />}\r\n bordered\r\n title={Locale.Chat.Actions.RefreshTitle}\r\n onClick={() => {\r\n showToast(Locale.Chat.Actions.RefreshToast);\r\n chatStore.summarizeSession(true, session);\r\n }}\r\n />\r\n </div>\r\n {!isMobileScreen && (\r\n <div className=\"window-action-button\">\r\n <IconButton\r\n icon={<RenameIcon />}\r\n bordered\r\n title={Locale.Chat.EditMessage.Title}\r\n aria={Locale.Chat.EditMessage.Title}\r\n onClick={() => setIsEditingMessage(true)}\r\n />\r\n </div>\r\n )}\r\n <div className=\"window-action-button\">\r\n <IconButton\r\n icon={<ExportIcon />}\r\n bordered\r\n title={Locale.Chat.Actions.Export}\r\n onClick={() => {\r\n setShowExport(true);\r\n }}\r\n />\r\n </div>\r\n {showMaxIcon && (\r\n <div className=\"window-action-button\">\r\n <IconButton\r\n icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}\r\n bordered\r\n title={Locale.Chat.Actions.FullScreen}\r\n aria={Locale.Chat.Actions.FullScreen}\r\n onClick={() => {\r\n config.update(\r\n (config) => (config.tightBorder = !config.tightBorder),\r\n );\r\n }}\r\n />\r\n </div>\r\n )}\r\n </div>\r\n\r\n <PromptToast\r\n showToast={!hitBottom}\r\n showModal={showPromptModal}\r\n setShowModal={setShowPromptModal}\r\n />\r\n </div>\r\n <div className={styles[\"chat-main\"]}>\r\n <div className={styles[\"chat-body-container\"]}>\r\n <div\r\n className={styles[\"chat-body\"]}\r\n ref={scrollRef}\r\n onScroll={(e) => onChatBodyScroll(e.currentTarget)}\r\n onMouseDown={() => inputRef.current?.blur()}\r\n onTouchStart={() => {\r\n inputRef.current?.blur();\r\n setAutoScroll(false);\r\n }}\r\n >\r\n {messages\r\n // TODO\r\n // .filter((m) => !m.isMcpResponse)\r\n .map((message, i) => {\r\n const isUser = message.role === \"user\";\r\n const isContext = i < context.length;\r\n const showActions =\r\n i > 0 &&\r\n !(message.preview || message.content.length === 0) &&\r\n !isContext;\r\n const showTyping = message.preview || message.streaming;\r\n\r\n const shouldShowClearContextDivider =\r\n i === clearContextIndex - 1;\r\n\r\n return (\r\n <Fragment key={message.id}>\r\n <div\r\n className={\r\n isUser\r\n ? styles[\"chat-message-user\"]\r\n : styles[\"chat-message\"]\r\n }\r\n >\r\n <div className={styles[\"chat-message-container\"]}>\r\n <div className={styles[\"chat-message-header\"]}>\r\n <div className={styles[\"chat-message-avatar\"]}>\r\n <div className={styles[\"chat-message-edit\"]}>\r\n <IconButton\r\n icon={<EditIcon />}\r\n aria={Locale.Chat.Actions.Edit}\r\n onClick={async () => {\r\n const newMessage = await showPrompt(\r\n Locale.Chat.Actions.Edit,\r\n getMessageTextContent(message),\r\n 10,\r\n );\r\n let newContent:\r\n | string\r\n | MultimodalContent[] = newMessage;\r\n const images = getMessageImages(message);\r\n if (images.length > 0) {\r\n newContent = [\r\n { type: \"text\", text: newMessage },\r\n ];\r\n for (let i = 0; i < images.length; i++) {\r\n newContent.push({\r\n type: \"image_url\",\r\n image_url: {\r\n url: images[i],\r\n },\r\n });\r\n }\r\n }\r\n chatStore.updateTargetSession(\r\n session,\r\n (session) => {\r\n const m = session.mask.context\r\n .concat(session.messages)\r\n .find((m) => m.id === message.id);\r\n if (m) {\r\n m.content = newContent;\r\n }\r\n },\r\n );\r\n }}\r\n ></IconButton>\r\n </div>\r\n {isUser ? (\r\n <Avatar avatar={config.avatar} />\r\n ) : (\r\n <>\r\n {[\"system\"].includes(message.role) ? (\r\n <Avatar avatar=\"2699-fe0f\" />\r\n ) : (\r\n <MaskAvatar\r\n avatar={session.mask.avatar}\r\n model={\r\n message.model ||\r\n session.mask.modelConfig.model\r\n }\r\n />\r\n )}\r\n </>\r\n )}\r\n </div>\r\n {!isUser && (\r\n <div className={styles[\"chat-model-name\"]}>\r\n {message.model}\r\n </div>\r\n )}\r\n\r\n {showActions && (\r\n <div className={styles[\"chat-message-actions\"]}>\r\n <div className={styles[\"chat-input-actions\"]}>\r\n {message.streaming ? (\r\n <ChatAction\r\n text={Locale.Chat.Actions.Stop}\r\n icon={<StopIcon />}\r\n onClick={() =>\r\n onUserStop(message.id ?? i)\r\n }\r\n />\r\n ) : (\r\n <>\r\n <ChatAction\r\n text={Locale.Chat.Actions.Retry}\r\n icon={<ResetIcon />}\r\n onClick={() => onResend(message)}\r\n />\r\n\r\n <ChatAction\r\n text={Locale.Chat.Actions.Delete}\r\n icon={<DeleteIcon />}\r\n onClick={() =>\r\n onDelete(message.id ?? i)\r\n }\r\n />\r\n\r\n <ChatAction\r\n text={Locale.Chat.Actions.Pin}\r\n icon={<PinIcon />}\r\n onClick={() => onPinMessage(message)}\r\n />\r\n <ChatAction\r\n text={Locale.Chat.Actions.Copy}\r\n icon={<CopyIcon />}\r\n onClick={() =>\r\n copyToClipboard(\r\n getMessageTextContent(message),\r\n )\r\n }\r\n />\r\n {config.ttsConfig.enable && (\r\n <ChatAction\r\n text={\r\n speechStatus\r\n ? Locale.Chat.Actions.StopSpeech\r\n : Locale.Chat.Actions.Speech\r\n }\r\n icon={\r\n speechStatus ? (\r\n <SpeakStopIcon />\r\n ) : (\r\n <SpeakIcon />\r\n )\r\n }\r\n onClick={() =>\r\n openaiSpeech(\r\n getMessageTextContent(message),\r\n )\r\n }\r\n />\r\n )}\r\n </>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n {message?.tools?.length == 0 && showTyping && (\r\n <div className={styles[\"chat-message-status\"]}>\r\n {Locale.Chat.Typing}\r\n </div>\r\n )}\r\n {/*@ts-ignore*/}\r\n {message?.tools?.length > 0 && (\r\n <div className={styles[\"chat-message-tools\"]}>\r\n {message?.tools?.map((tool) => (\r\n <div\r\n key={tool.id}\r\n title={tool?.errorMsg}\r\n className={styles[\"chat-message-tool\"]}\r\n >\r\n {tool.isError === false ? (\r\n <ConfirmIcon />\r\n ) : tool.isError === true ? (\r\n <CloseIcon />\r\n ) : (\r\n <LoadingButtonIcon />\r\n )}\r\n <span>{tool?.function?.name}</span>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n <div className={styles[\"chat-message-item\"]}>\r\n <Markdown\r\n key={message.streaming ? \"loading\" : \"done\"}\r\n content={getMessageTextContent(message)}\r\n loading={\r\n (message.preview || message.streaming) &&\r\n message.content.length === 0 &&\r\n !isUser\r\n }\r\n // onContextMenu={(e) => onRightClick(e, message)} // hard to use\r\n onDoubleClickCapture={() => {\r\n if (!isMobileScreen) return;\r\n setUserInput(getMessageTextContent(message));\r\n }}\r\n fontSize={fontSize}\r\n fontFamily={fontFamily}\r\n parentRef={scrollRef}\r\n defaultShow={i >= messages.length - 6}\r\n />\r\n {getMessageImages(message).length == 1 && (\r\n <img\r\n className={styles[\"chat-message-item-image\"]}\r\n src={getMessageImages(message)[0]}\r\n alt=\"\"\r\n />\r\n )}\r\n {getMessageImages(message).length > 1 && (\r\n <div\r\n className={styles[\"chat-message-item-images\"]}\r\n style={\r\n {\r\n \"--image-count\":\r\n getMessageImages(message).length,\r\n } as React.CSSProperties\r\n }\r\n >\r\n {getMessageImages(message).map(\r\n (image, index) => {\r\n return (\r\n <img\r\n className={\r\n styles[\r\n \"chat-message-item-image-multi\"\r\n ]\r\n }\r\n key={index}\r\n src={image}\r\n alt=\"\"\r\n />\r\n );\r\n },\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n {message?.audio_url && (\r\n <div className={styles[\"chat-message-audio\"]}>\r\n <audio src={message.audio_url} controls />\r\n </div>\r\n )}\r\n\r\n <div className={styles[\"chat-message-action-date\"]}>\r\n {isContext\r\n ? Locale.Chat.IsContext\r\n : message.date.toLocaleString()}\r\n </div>\r\n </div>\r\n </div>\r\n {shouldShowClearContextDivider && <ClearContextDivider />}\r\n </Fragment>\r\n );\r\n })}\r\n </div>\r\n <div className={styles[\"chat-input-panel\"]}>\r\n <PromptHints\r\n prompts={promptHints}\r\n onPromptSelect={onPromptSelect}\r\n />\r\n\r\n <ChatActions\r\n uploadImage={uploadImage}\r\n setAttachImages={setAttachImages}\r\n setUploading={setUploading}\r\n showPromptModal={() => setShowPromptModal(true)}\r\n scrollToBottom={scrollToBottom}\r\n hitBottom={hitBottom}\r\n uploading={uploading}\r\n showPromptHints={() => {\r\n // Click again to close\r\n if (promptHints.length > 0) {\r\n setPromptHints([]);\r\n return;\r\n }\r\n\r\n inputRef.current?.focus();\r\n setUserInput(\"/\");\r\n onSearch(\"\");\r\n }}\r\n setShowShortcutKeyModal={setShowShortcutKeyModal}\r\n setUserInput={setUserInput}\r\n setShowChatSidePanel={setShowChatSidePanel}\r\n />\r\n <label\r\n className={clsx(styles[\"chat-input-panel-inner\"], {\r\n [styles[\"chat-input-panel-inner-attach\"]]:\r\n attachImages.length !== 0,\r\n })}\r\n htmlFor=\"chat-input\"\r\n >\r\n <textarea\r\n id=\"chat-input\"\r\n ref={inputRef}\r\n className={styles[\"chat-input\"]}\r\n placeholder={Locale.Chat.Input(submitKey)}\r\n onInput={(e) => onInput(e.currentTarget.value)}\r\n value={userInput}\r\n onKeyDown={onInputKeyDown}\r\n onFocus={scrollToBottom}\r\n onClick={scrollToBottom}\r\n onPaste={handlePaste}\r\n rows={inputRows}\r\n autoFocus={autoFocus}\r\n style={{\r\n fontSize: config.fontSize,\r\n fontFamily: config.fontFamily,\r\n }}\r\n />\r\n {attachImages.length != 0 && (\r\n <div className={styles[\"attach-images\"]}>\r\n {attachImages.map((image, index) => {\r\n return (\r\n <div\r\n key={index}\r\n className={styles[\"attach-image\"]}\r\n style={{ backgroundImage: `url(\"${image}\")` }}\r\n >\r\n <div className={styles[\"attach-image-mask\"]}>\r\n <DeleteImageButton\r\n deleteImage={() => {\r\n setAttachImages(\r\n attachImages.filter((_, i) => i !== index),\r\n );\r\n }}\r\n />\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n <IconButton\r\n icon={<SendWhiteIcon />}\r\n text={Locale.Chat.Send}\r\n className={styles[\"chat-input-send\"]}\r\n type=\"primary\"\r\n onClick={() => doSubmit(userInput)}\r\n />\r\n </label>\r\n </div>\r\n </div>\r\n <div\r\n className={clsx(styles[\"chat-side-panel\"], {\r\n [styles[\"mobile\"]]: isMobileScreen,\r\n [styles[\"chat-side-panel-show\"]]: showChatSidePanel,\r\n })}\r\n >\r\n {showChatSidePanel && (\r\n <RealtimeChat\r\n onClose={() => {\r\n setShowChatSidePanel(false);\r\n }}\r\n onStartVoice={async () => {\r\n console.log(\"start voice\");\r\n }}\r\n />\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n {showExport && (\r\n <ExportMessageModal onClose={() => setShowExport(false)} />\r\n )}\r\n\r\n {isEditingMessage && (\r\n <EditMessageModal\r\n onClose={() => {\r\n setIsEditingMessage(false);\r\n }}\r\n />\r\n )}\r\n\r\n {showShortcutKeyModal && (\r\n <ShortcutKeyModal onClose={() => setShowShortcutKeyModal(false)} />\r\n )}\r\n </>\r\n );\r\n}\r\n\r\nexport function Chat() {\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n return <_Chat key={session.id}></_Chat>;\r\n}\r\n","D:\\NextWeb\\app\\components\\emoji.tsx",[],[],"D:\\NextWeb\\app\\components\\error.tsx",[],[],"D:\\NextWeb\\app\\components\\exporter.tsx",[],["746","747","748","749"],"D:\\NextWeb\\app\\components\\home.tsx",[],["750"],"D:\\NextWeb\\app\\components\\input-range.tsx",[],[],"D:\\NextWeb\\app\\components\\markdown.tsx",["751"],[],"import ReactMarkdown from \"react-markdown\";\r\nimport \"katex/dist/katex.min.css\";\r\nimport RemarkMath from \"remark-math\";\r\nimport RemarkBreaks from \"remark-breaks\";\r\nimport RehypeKatex from \"rehype-katex\";\r\nimport RemarkGfm from \"remark-gfm\";\r\nimport RehypeHighlight from \"rehype-highlight\";\r\nimport { useRef, useState, RefObject, useEffect, useMemo } from \"react\";\r\nimport { copyToClipboard, useWindowSize } from \"../utils\";\r\nimport mermaid from \"mermaid\";\r\nimport Locale from \"../locales\";\r\nimport LoadingIcon from \"../icons/three-dots.svg\";\r\nimport ReloadButtonIcon from \"../icons/reload.svg\";\r\nimport React from \"react\";\r\nimport { useDebouncedCallback } from \"use-debounce\";\r\nimport { showImageModal, FullScreen } from \"./ui-lib\";\r\nimport {\r\n ArtifactsShareButton,\r\n HTMLPreview,\r\n HTMLPreviewHander,\r\n} from \"./artifacts\";\r\nimport { useChatStore } from \"../store\";\r\nimport { IconButton } from \"./button\";\r\n\r\nimport { useAppConfig } from \"../store/config\";\r\nimport clsx from \"clsx\";\r\n\r\nexport function Mermaid(props: { code: string }) {\r\n const ref = useRef<HTMLDivElement>(null);\r\n const [hasError, setHasError] = useState(false);\r\n\r\n useEffect(() => {\r\n if (props.code && ref.current) {\r\n mermaid\r\n .run({\r\n nodes: [ref.current],\r\n suppressErrors: true,\r\n })\r\n .catch((e) => {\r\n setHasError(true);\r\n console.error(\"[Mermaid] \", e.message);\r\n });\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [props.code]);\r\n\r\n function viewSvgInNewWindow() {\r\n const svg = ref.current?.querySelector(\"svg\");\r\n if (!svg) return;\r\n const text = new XMLSerializer().serializeToString(svg);\r\n const blob = new Blob([text], { type: \"image/svg+xml\" });\r\n showImageModal(URL.createObjectURL(blob));\r\n }\r\n\r\n if (hasError) {\r\n return null;\r\n }\r\n\r\n return (\r\n <div\r\n className={clsx(\"no-dark\", \"mermaid\")}\r\n style={{\r\n cursor: \"pointer\",\r\n overflow: \"auto\",\r\n }}\r\n ref={ref}\r\n onClick={() => viewSvgInNewWindow()}\r\n >\r\n {props.code}\r\n </div>\r\n );\r\n}\r\n\r\nexport function PreCode(props: { children: any }) {\r\n const ref = useRef<HTMLPreElement>(null);\r\n const previewRef = useRef<HTMLPreviewHander>(null);\r\n const [mermaidCode, setMermaidCode] = useState(\"\");\r\n const [htmlCode, setHtmlCode] = useState(\"\");\r\n const { height } = useWindowSize();\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n\r\n const renderArtifacts = useDebouncedCallback(() => {\r\n if (!ref.current) return;\r\n const mermaidDom = ref.current.querySelector(\"code.language-mermaid\");\r\n if (mermaidDom) {\r\n setMermaidCode((mermaidDom as HTMLElement).innerText);\r\n }\r\n const htmlDom = ref.current.querySelector(\"code.language-html\");\r\n const refText = ref.current.querySelector(\"code\")?.innerText;\r\n if (htmlDom) {\r\n setHtmlCode((htmlDom as HTMLElement).innerText);\r\n } else if (\r\n refText?.startsWith(\"<!DOCTYPE\") ||\r\n refText?.startsWith(\"<svg\") ||\r\n refText?.startsWith(\"<?xml\")\r\n ) {\r\n setHtmlCode(refText);\r\n }\r\n }, 600);\r\n\r\n const config = useAppConfig();\r\n const enableArtifacts =\r\n session.mask?.enableArtifacts !== false && config.enableArtifacts;\r\n\r\n //Wrap the paragraph for plain-text\r\n useEffect(() => {\r\n if (ref.current) {\r\n const codeElements = ref.current.querySelectorAll(\r\n \"code\",\r\n ) as NodeListOf<HTMLElement>;\r\n const wrapLanguages = [\r\n \"\",\r\n \"md\",\r\n \"markdown\",\r\n \"text\",\r\n \"txt\",\r\n \"plaintext\",\r\n \"tex\",\r\n \"latex\",\r\n ];\r\n codeElements.forEach((codeElement) => {\r\n let languageClass = codeElement.className.match(/language-(\\w+)/);\r\n let name = languageClass ? languageClass[1] : \"\";\r\n if (wrapLanguages.includes(name)) {\r\n codeElement.style.whiteSpace = \"pre-wrap\";\r\n }\r\n });\r\n setTimeout(renderArtifacts, 1);\r\n }\r\n }, []);\r\n\r\n return (\r\n <>\r\n <pre ref={ref}>\r\n <span\r\n className=\"copy-code-button\"\r\n onClick={() => {\r\n if (ref.current) {\r\n copyToClipboard(\r\n ref.current.querySelector(\"code\")?.innerText ?? \"\",\r\n );\r\n }\r\n }}\r\n ></span>\r\n {props.children}\r\n </pre>\r\n {mermaidCode.length > 0 && (\r\n <Mermaid code={mermaidCode} key={mermaidCode} />\r\n )}\r\n {htmlCode.length > 0 && enableArtifacts && (\r\n <FullScreen className=\"no-dark html\" right={70}>\r\n <ArtifactsShareButton\r\n style={{ position: \"absolute\", right: 20, top: 10 }}\r\n getCode={() => htmlCode}\r\n />\r\n <IconButton\r\n style={{ position: \"absolute\", right: 120, top: 10 }}\r\n bordered\r\n icon={<ReloadButtonIcon />}\r\n shadow\r\n onClick={() => previewRef.current?.reload()}\r\n />\r\n <HTMLPreview\r\n ref={previewRef}\r\n code={htmlCode}\r\n autoHeight={!document.fullscreenElement}\r\n height={!document.fullscreenElement ? 600 : height}\r\n />\r\n </FullScreen>\r\n )}\r\n </>\r\n );\r\n}\r\n\r\nfunction CustomCode(props: { children: any; className?: string }) {\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n const config = useAppConfig();\r\n const enableCodeFold =\r\n session.mask?.enableCodeFold !== false && config.enableCodeFold;\r\n\r\n const ref = useRef<HTMLPreElement>(null);\r\n const [collapsed, setCollapsed] = useState(true);\r\n const [showToggle, setShowToggle] = useState(false);\r\n\r\n useEffect(() => {\r\n if (ref.current) {\r\n const codeHeight = ref.current.scrollHeight;\r\n setShowToggle(codeHeight > 400);\r\n ref.current.scrollTop = ref.current.scrollHeight;\r\n }\r\n }, [props.children]);\r\n\r\n const toggleCollapsed = () => {\r\n setCollapsed((collapsed) => !collapsed);\r\n };\r\n const renderShowMoreButton = () => {\r\n if (showToggle && enableCodeFold && collapsed) {\r\n return (\r\n <div\r\n className={clsx(\"show-hide-button\", {\r\n collapsed,\r\n expanded: !collapsed,\r\n })}\r\n >\r\n <button onClick={toggleCollapsed}>{Locale.NewChat.More}</button>\r\n </div>\r\n );\r\n }\r\n return null;\r\n };\r\n return (\r\n <>\r\n <code\r\n className={clsx(props?.className)}\r\n ref={ref}\r\n style={{\r\n maxHeight: enableCodeFold && collapsed ? \"400px\" : \"none\",\r\n overflowY: \"hidden\",\r\n }}\r\n >\r\n {props.children}\r\n </code>\r\n\r\n {renderShowMoreButton()}\r\n </>\r\n );\r\n}\r\n\r\nfunction escapeBrackets(text: string) {\r\n const pattern =\r\n /(```[\\s\\S]*?```|`.*?`)|\\\\\\[([\\s\\S]*?[^\\\\])\\\\\\]|\\\\\\((.*?)\\\\\\)/g;\r\n return text.replace(\r\n pattern,\r\n (match, codeBlock, squareBracket, roundBracket) => {\r\n if (codeBlock) {\r\n return codeBlock;\r\n } else if (squareBracket) {\r\n return `$$${squareBracket}$$`;\r\n } else if (roundBracket) {\r\n return `$${roundBracket}$`;\r\n }\r\n return match;\r\n },\r\n );\r\n}\r\n\r\nfunction tryWrapHtmlCode(text: string) {\r\n // try add wrap html code (fixed: html codeblock include 2 newline)\r\n // ignore embed codeblock\r\n if (text.includes(\"```\")) {\r\n return text;\r\n }\r\n return text\r\n .replace(\r\n /([`]*?)(\\w*?)([\\n\\r]*?)(<!DOCTYPE html>)/g,\r\n (match, quoteStart, lang, newLine, doctype) => {\r\n return !quoteStart ? \"\\n```html\\n\" + doctype : match;\r\n },\r\n )\r\n .replace(\r\n /(<\\/body>)([\\r\\n\\s]*?)(<\\/html>)([\\n\\r]*)([`]*)([\\n\\r]*?)/g,\r\n (match, bodyEnd, space, htmlEnd, newLine, quoteEnd) => {\r\n return !quoteEnd ? bodyEnd + space + htmlEnd + \"\\n```\\n\" : match;\r\n },\r\n );\r\n}\r\n\r\nfunction _MarkDownContent(props: { content: string }) {\r\n const escapedContent = useMemo(() => {\r\n return tryWrapHtmlCode(escapeBrackets(props.content));\r\n }, [props.content]);\r\n\r\n return (\r\n <ReactMarkdown\r\n remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}\r\n rehypePlugins={[\r\n RehypeKatex,\r\n [\r\n RehypeHighlight,\r\n {\r\n detect: false,\r\n ignoreMissing: true,\r\n },\r\n ],\r\n ]}\r\n components={{\r\n pre: PreCode,\r\n code: CustomCode,\r\n p: (pProps) => <p {...pProps} dir=\"auto\" />,\r\n a: (aProps) => {\r\n const href = aProps.href || \"\";\r\n if (/\\.(aac|mp3|opus|wav)$/.test(href)) {\r\n return (\r\n <figure>\r\n <audio controls src={href}></audio>\r\n </figure>\r\n );\r\n }\r\n if (/\\.(3gp|3g2|webm|ogv|mpeg|mp4|avi)$/.test(href)) {\r\n return (\r\n <video controls width=\"99.9%\">\r\n <source src={href} />\r\n </video>\r\n );\r\n }\r\n const isInternal = /^\\/#/i.test(href);\r\n const target = isInternal ? \"_self\" : aProps.target ?? \"_blank\";\r\n return <a {...aProps} target={target} />;\r\n },\r\n }}\r\n >\r\n {escapedContent}\r\n </ReactMarkdown>\r\n );\r\n}\r\n\r\nexport const MarkdownContent = React.memo(_MarkDownContent);\r\n\r\nexport function Markdown(\r\n props: {\r\n content: string;\r\n loading?: boolean;\r\n fontSize?: number;\r\n fontFamily?: string;\r\n parentRef?: RefObject<HTMLDivElement>;\r\n defaultShow?: boolean;\r\n } & React.DOMAttributes<HTMLDivElement>,\r\n) {\r\n const mdRef = useRef<HTMLDivElement>(null);\r\n\r\n return (\r\n <div\r\n className=\"markdown-body\"\r\n style={{\r\n fontSize: `${props.fontSize ?? 14}px`,\r\n fontFamily: props.fontFamily || \"inherit\",\r\n }}\r\n ref={mdRef}\r\n onContextMenu={props.onContextMenu}\r\n onDoubleClickCapture={props.onDoubleClickCapture}\r\n dir=\"auto\"\r\n >\r\n {props.loading ? (\r\n <LoadingIcon />\r\n ) : (\r\n <MarkdownContent content={props.content} />\r\n )}\r\n </div>\r\n );\r\n}\r\n","D:\\NextWeb\\app\\components\\mask.tsx",[],[],"D:\\NextWeb\\app\\components\\mcp-market.tsx",[],[],"D:\\NextWeb\\app\\components\\message-selector.tsx",[],["752","753"],"D:\\NextWeb\\app\\components\\model-config.tsx",[],[],"D:\\NextWeb\\app\\components\\new-chat.tsx",[],["754"],"D:\\NextWeb\\app\\components\\plugin.tsx",[],[],"D:\\NextWeb\\app\\components\\realtime-chat\\index.ts",[],[],"D:\\NextWeb\\app\\components\\realtime-chat\\realtime-chat.tsx",["755"],[],"import VoiceIcon from \"@/app/icons/voice.svg\";\r\nimport VoiceOffIcon from \"@/app/icons/voice-off.svg\";\r\nimport PowerIcon from \"@/app/icons/power.svg\";\r\n\r\nimport styles from \"./realtime-chat.module.scss\";\r\nimport clsx from \"clsx\";\r\n\r\nimport { useState, useRef, useEffect } from \"react\";\r\n\r\nimport { useChatStore, createMessage, useAppConfig } from \"@/app/store\";\r\n\r\nimport { IconButton } from \"@/app/components/button\";\r\n\r\nimport {\r\n Modality,\r\n RTClient,\r\n RTInputAudioItem,\r\n RTResponse,\r\n TurnDetection,\r\n} from \"rt-client\";\r\nimport { AudioHandler } from \"@/app/lib/audio\";\r\nimport { uploadImage } from \"@/app/utils/chat\";\r\nimport { VoicePrint } from \"@/app/components/voice-print\";\r\n\r\ninterface RealtimeChatProps {\r\n onClose?: () => void;\r\n onStartVoice?: () => void;\r\n onPausedVoice?: () => void;\r\n}\r\n\r\nexport function RealtimeChat({\r\n onClose,\r\n onStartVoice,\r\n onPausedVoice,\r\n}: RealtimeChatProps) {\r\n const chatStore = useChatStore();\r\n const session = chatStore.currentSession();\r\n const config = useAppConfig();\r\n const [status, setStatus] = useState(\"\");\r\n const [isRecording, setIsRecording] = useState(false);\r\n const [isConnected, setIsConnected] = useState(false);\r\n const [isConnecting, setIsConnecting] = useState(false);\r\n const [modality, setModality] = useState(\"audio\");\r\n const [useVAD, setUseVAD] = useState(true);\r\n const [frequencies, setFrequencies] = useState<Uint8Array | undefined>();\r\n\r\n const clientRef = useRef<RTClient | null>(null);\r\n const audioHandlerRef = useRef<AudioHandler | null>(null);\r\n const initRef = useRef(false);\r\n\r\n const temperature = config.realtimeConfig.temperature;\r\n const apiKey = config.realtimeConfig.apiKey;\r\n const model = config.realtimeConfig.model;\r\n const azure = config.realtimeConfig.provider === \"Azure\";\r\n const azureEndpoint = config.realtimeConfig.azure.endpoint;\r\n const azureDeployment = config.realtimeConfig.azure.deployment;\r\n const voice = config.realtimeConfig.voice;\r\n\r\n const handleConnect = async () => {\r\n if (isConnecting) return;\r\n if (!isConnected) {\r\n try {\r\n setIsConnecting(true);\r\n clientRef.current = azure\r\n ? new RTClient(\r\n new URL(azureEndpoint),\r\n { key: apiKey },\r\n { deployment: azureDeployment },\r\n )\r\n : new RTClient({ key: apiKey }, { model });\r\n const modalities: Modality[] =\r\n modality === \"audio\" ? [\"text\", \"audio\"] : [\"text\"];\r\n const turnDetection: TurnDetection = useVAD\r\n ? { type: \"server_vad\" }\r\n : null;\r\n await clientRef.current.configure({\r\n instructions: \"\",\r\n voice,\r\n input_audio_transcription: { model: \"whisper-1\" },\r\n turn_detection: turnDetection,\r\n tools: [],\r\n temperature,\r\n modalities,\r\n });\r\n startResponseListener();\r\n\r\n setIsConnected(true);\r\n // TODO\r\n // try {\r\n // const recentMessages = chatStore.getMessagesWithMemory();\r\n // for (const message of recentMessages) {\r\n // const { role, content } = message;\r\n // if (typeof content === \"string\") {\r\n // await clientRef.current.sendItem({\r\n // type: \"message\",\r\n // role: role as any,\r\n // content: [\r\n // {\r\n // type: (role === \"assistant\" ? \"text\" : \"input_text\") as any,\r\n // text: content as string,\r\n // },\r\n // ],\r\n // });\r\n // }\r\n // }\r\n // // await clientRef.current.generateResponse();\r\n // } catch (error) {\r\n // console.error(\"Set message failed:\", error);\r\n // }\r\n } catch (error) {\r\n console.error(\"Connection failed:\", error);\r\n setStatus(\"Connection failed\");\r\n } finally {\r\n setIsConnecting(false);\r\n }\r\n } else {\r\n await disconnect();\r\n }\r\n };\r\n\r\n const disconnect = async () => {\r\n if (clientRef.current) {\r\n try {\r\n await clientRef.current.close();\r\n clientRef.current = null;\r\n setIsConnected(false);\r\n } catch (error) {\r\n console.error(\"Disconnect failed:\", error);\r\n }\r\n }\r\n };\r\n\r\n const startResponseListener = async () => {\r\n if (!clientRef.current) return;\r\n\r\n try {\r\n for await (const serverEvent of clientRef.current.events()) {\r\n if (serverEvent.type === \"response\") {\r\n await handleResponse(serverEvent);\r\n } else if (serverEvent.type === \"input_audio\") {\r\n await handleInputAudio(serverEvent);\r\n }\r\n }\r\n } catch (error) {\r\n if (clientRef.current) {\r\n console.error(\"Response iteration error:\", error);\r\n }\r\n }\r\n };\r\n\r\n const handleResponse = async (response: RTResponse) => {\r\n for await (const item of response) {\r\n if (item.type === \"message\" && item.role === \"assistant\") {\r\n const botMessage = createMessage({\r\n role: item.role,\r\n content: \"\",\r\n });\r\n // add bot message first\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.messages = session.messages.concat([botMessage]);\r\n });\r\n let hasAudio = false;\r\n for await (const content of item) {\r\n if (content.type === \"text\") {\r\n for await (const text of content.textChunks()) {\r\n botMessage.content += text;\r\n }\r\n } else if (content.type === \"audio\") {\r\n const textTask = async () => {\r\n for await (const text of content.transcriptChunks()) {\r\n botMessage.content += text;\r\n }\r\n };\r\n const audioTask = async () => {\r\n audioHandlerRef.current?.startStreamingPlayback();\r\n for await (const audio of content.audioChunks()) {\r\n hasAudio = true;\r\n audioHandlerRef.current?.playChunk(audio);\r\n }\r\n };\r\n await Promise.all([textTask(), audioTask()]);\r\n }\r\n // update message.content\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.messages = session.messages.concat();\r\n });\r\n }\r\n if (hasAudio) {\r\n // upload audio get audio_url\r\n const blob = audioHandlerRef.current?.savePlayFile();\r\n uploadImage(blob!).then((audio_url) => {\r\n botMessage.audio_url = audio_url;\r\n // update text and audio_url\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.messages = session.messages.concat();\r\n });\r\n });\r\n }\r\n }\r\n }\r\n };\r\n\r\n const handleInputAudio = async (item: RTInputAudioItem) => {\r\n await item.waitForCompletion();\r\n if (item.transcription) {\r\n const userMessage = createMessage({\r\n role: \"user\",\r\n content: item.transcription,\r\n });\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.messages = session.messages.concat([userMessage]);\r\n });\r\n // save input audio_url, and update session\r\n const { audioStartMillis, audioEndMillis } = item;\r\n // upload audio get audio_url\r\n const blob = audioHandlerRef.current?.saveRecordFile(\r\n audioStartMillis,\r\n audioEndMillis,\r\n );\r\n uploadImage(blob!).then((audio_url) => {\r\n userMessage.audio_url = audio_url;\r\n chatStore.updateTargetSession(session, (session) => {\r\n session.messages = session.messages.concat();\r\n });\r\n });\r\n }\r\n // stop streaming play after get input audio.\r\n audioHandlerRef.current?.stopStreamingPlayback();\r\n };\r\n\r\n const toggleRecording = async () => {\r\n if (!isRecording && clientRef.current) {\r\n try {\r\n if (!audioHandlerRef.current) {\r\n audioHandlerRef.current = new AudioHandler();\r\n await audioHandlerRef.current.initialize();\r\n }\r\n await audioHandlerRef.current.startRecording(async (chunk) => {\r\n await clientRef.current?.sendAudio(chunk);\r\n });\r\n setIsRecording(true);\r\n } catch (error) {\r\n console.error(\"Failed to start recording:\", error);\r\n }\r\n } else if (audioHandlerRef.current) {\r\n try {\r\n audioHandlerRef.current.stopRecording();\r\n if (!useVAD) {\r\n const inputAudio = await clientRef.current?.commitAudio();\r\n await handleInputAudio(inputAudio!);\r\n await clientRef.current?.generateResponse();\r\n }\r\n setIsRecording(false);\r\n } catch (error) {\r\n console.error(\"Failed to stop recording:\", error);\r\n }\r\n }\r\n };\r\n\r\n useEffect(() => {\r\n // 防止重复初始化\r\n if (initRef.current) return;\r\n initRef.current = true;\r\n\r\n const initAudioHandler = async () => {\r\n const handler = new AudioHandler();\r\n await handler.initialize();\r\n audioHandlerRef.current = handler;\r\n await handleConnect();\r\n await toggleRecording();\r\n };\r\n\r\n initAudioHandler().catch((error) => {\r\n setStatus(error);\r\n console.error(error);\r\n });\r\n\r\n return () => {\r\n if (isRecording) {\r\n toggleRecording();\r\n }\r\n audioHandlerRef.current?.close().catch(console.error);\r\n disconnect();\r\n };\r\n }, []);\r\n\r\n useEffect(() => {\r\n let animationFrameId: number;\r\n\r\n if (isConnected && isRecording) {\r\n const animationFrame = () => {\r\n if (audioHandlerRef.current) {\r\n const freqData = audioHandlerRef.current.getByteFrequencyData();\r\n setFrequencies(freqData);\r\n }\r\n animationFrameId = requestAnimationFrame(animationFrame);\r\n };\r\n\r\n animationFrameId = requestAnimationFrame(animationFrame);\r\n } else {\r\n setFrequencies(undefined);\r\n }\r\n\r\n return () => {\r\n if (animationFrameId) {\r\n cancelAnimationFrame(animationFrameId);\r\n }\r\n };\r\n }, [isConnected, isRecording]);\r\n\r\n // update session params\r\n useEffect(() => {\r\n clientRef.current?.configure({ voice });\r\n }, [voice]);\r\n useEffect(() => {\r\n clientRef.current?.configure({ temperature });\r\n }, [temperature]);\r\n\r\n const handleClose = async () => {\r\n onClose?.();\r\n if (isRecording) {\r\n await toggleRecording();\r\n }\r\n disconnect().catch(console.error);\r\n };\r\n\r\n return (\r\n <div className={styles[\"realtime-chat\"]}>\r\n <div\r\n className={clsx(styles[\"circle-mic\"], {\r\n [styles[\"pulse\"]]: isRecording,\r\n })}\r\n >\r\n <VoicePrint frequencies={frequencies} isActive={isRecording} />\r\n </div>\r\n\r\n <div className={styles[\"bottom-icons\"]}>\r\n <div>\r\n <IconButton\r\n icon={isRecording ? <VoiceIcon /> : <VoiceOffIcon />}\r\n onClick={toggleRecording}\r\n disabled={!isConnected}\r\n shadow\r\n bordered\r\n />\r\n </div>\r\n <div className={styles[\"icon-center\"]}>{status}</div>\r\n <div>\r\n <IconButton\r\n icon={<PowerIcon />}\r\n onClick={handleClose}\r\n shadow\r\n bordered\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n","D:\\NextWeb\\app\\components\\realtime-chat\\realtime-config.tsx",[],[],"D:\\NextWeb\\app\\components\\sd\\index.tsx",[],[],"D:\\NextWeb\\app\\components\\sd\\sd-panel.tsx",[],[],"D:\\NextWeb\\app\\components\\sd\\sd-sidebar.tsx",[],[],"D:\\NextWeb\\app\\components\\sd\\sd.tsx",["756","757"],[],"import chatStyles from \"@/app/components/chat.module.scss\";\r\nimport styles from \"@/app/components/sd/sd.module.scss\";\r\nimport homeStyles from \"@/app/components/home.module.scss\";\r\n\r\nimport { IconButton } from \"@/app/components/button\";\r\nimport ReturnIcon from \"@/app/icons/return.svg\";\r\nimport Locale from \"@/app/locales\";\r\nimport { Path } from \"@/app/constant\";\r\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\r\nimport {\r\n copyToClipboard,\r\n getMessageTextContent,\r\n useMobileScreen,\r\n} from \"@/app/utils\";\r\nimport { useNavigate, useLocation } from \"react-router-dom\";\r\nimport { useAppConfig } from \"@/app/store\";\r\nimport MinIcon from \"@/app/icons/min.svg\";\r\nimport MaxIcon from \"@/app/icons/max.svg\";\r\nimport { getClientConfig } from \"@/app/config/client\";\r\nimport { ChatAction } from \"@/app/components/chat\";\r\nimport DeleteIcon from \"@/app/icons/clear.svg\";\r\nimport CopyIcon from \"@/app/icons/copy.svg\";\r\nimport PromptIcon from \"@/app/icons/prompt.svg\";\r\nimport ResetIcon from \"@/app/icons/reload.svg\";\r\nimport { useSdStore } from \"@/app/store/sd\";\r\nimport LoadingIcon from \"@/app/icons/three-dots.svg\";\r\nimport ErrorIcon from \"@/app/icons/delete.svg\";\r\nimport SDIcon from \"@/app/icons/sd.svg\";\r\nimport { Property } from \"csstype\";\r\nimport {\r\n showConfirm,\r\n showImageModal,\r\n showModal,\r\n} from \"@/app/components/ui-lib\";\r\nimport { removeImage } from \"@/app/utils/chat\";\r\nimport { SideBar } from \"./sd-sidebar\";\r\nimport { WindowContent } from \"@/app/components/home\";\r\nimport { params } from \"./sd-panel\";\r\nimport clsx from \"clsx\";\r\n\r\nfunction getSdTaskStatus(item: any) {\r\n let s: string;\r\n let color: Property.Color | undefined = undefined;\r\n switch (item.status) {\r\n case \"success\":\r\n s = Locale.Sd.Status.Success;\r\n color = \"green\";\r\n break;\r\n case \"error\":\r\n s = Locale.Sd.Status.Error;\r\n color = \"red\";\r\n break;\r\n case \"wait\":\r\n s = Locale.Sd.Status.Wait;\r\n color = \"yellow\";\r\n break;\r\n case \"running\":\r\n s = Locale.Sd.Status.Running;\r\n color = \"blue\";\r\n break;\r\n default:\r\n s = item.status.toUpperCase();\r\n }\r\n return (\r\n <p className={styles[\"line-1\"]} title={item.error} style={{ color: color }}>\r\n <span>\r\n {Locale.Sd.Status.Name}: {s}\r\n </span>\r\n {item.status === \"error\" && (\r\n <span\r\n className=\"clickable\"\r\n onClick={() => {\r\n showModal({\r\n title: Locale.Sd.Detail,\r\n children: (\r\n <div style={{ color: color, userSelect: \"text\" }}>\r\n {item.error}\r\n </div>\r\n ),\r\n });\r\n }}\r\n >\r\n - {item.error}\r\n </span>\r\n )}\r\n </p>\r\n );\r\n}\r\n\r\nexport function Sd() {\r\n const isMobileScreen = useMobileScreen();\r\n const navigate = useNavigate();\r\n const location = useLocation();\r\n const clientConfig = useMemo(() => getClientConfig(), []);\r\n const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;\r\n const config = useAppConfig();\r\n const scrollRef = useRef<HTMLDivElement>(null);\r\n const sdStore = useSdStore();\r\n const [sdImages, setSdImages] = useState(sdStore.draw);\r\n const isSd = location.pathname === Path.Sd;\r\n\r\n useEffect(() => {\r\n setSdImages(sdStore.draw);\r\n }, [sdStore.currentId]);\r\n\r\n return (\r\n <>\r\n <SideBar className={clsx({ [homeStyles[\"sidebar-show\"]]: isSd })} />\r\n <WindowContent>\r\n <div className={chatStyles.chat} key={\"1\"}>\r\n <div className=\"window-header\" data-tauri-drag-region>\r\n {isMobileScreen && (\r\n <div className=\"window-actions\">\r\n <div className={\"window-action-button\"}>\r\n <IconButton\r\n icon={<ReturnIcon />}\r\n bordered\r\n title={Locale.Chat.Actions.ChatList}\r\n onClick={() => navigate(Path.Sd)}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n <div\r\n className={clsx(\r\n \"window-header-title\",\r\n chatStyles[\"chat-body-title\"],\r\n )}\r\n >\r\n <div className={`window-header-main-title`}>Stability AI</div>\r\n <div className=\"window-header-sub-title\">\r\n {Locale.Sd.SubTitle(sdImages.length || 0)}\r\n </div>\r\n </div>\r\n\r\n <div className=\"window-actions\">\r\n {showMaxIcon && (\r\n <div className=\"window-action-button\">\r\n <IconButton\r\n aria={Locale.Chat.Actions.FullScreen}\r\n icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}\r\n bordered\r\n onClick={() => {\r\n config.update(\r\n (config) => (config.tightBorder = !config.tightBorder),\r\n );\r\n }}\r\n />\r\n </div>\r\n )}\r\n {isMobileScreen && <SDIcon width={50} height={50} />}\r\n </div>\r\n </div>\r\n <div className={chatStyles[\"chat-body\"]} ref={scrollRef}>\r\n <div className={styles[\"sd-img-list\"]}>\r\n {sdImages.length > 0 ? (\r\n sdImages.map((item: any) => {\r\n return (\r\n <div\r\n key={item.id}\r\n style={{ display: \"flex\" }}\r\n className={styles[\"sd-img-item\"]}\r\n >\r\n {item.status === \"success\" ? (\r\n <img\r\n className={styles[\"img\"]}\r\n src={item.img_data}\r\n alt={item.id}\r\n onClick={(e) =>\r\n showImageModal(\r\n item.img_data,\r\n true,\r\n isMobileScreen\r\n ? { width: \"100%\", height: \"fit-content\" }\r\n : { maxWidth: \"100%\", maxHeight: \"100%\" },\r\n isMobileScreen\r\n ? { width: \"100%\", height: \"fit-content\" }\r\n : { width: \"100%\", height: \"100%\" },\r\n )\r\n }\r\n />\r\n ) : item.status === \"error\" ? (\r\n <div className={styles[\"pre-img\"]}>\r\n <ErrorIcon />\r\n </div>\r\n ) : (\r\n <div className={styles[\"pre-img\"]}>\r\n <LoadingIcon />\r\n </div>\r\n )}\r\n <div\r\n style={{ marginLeft: \"10px\" }}\r\n className={styles[\"sd-img-item-info\"]}\r\n >\r\n <p className={styles[\"line-1\"]}>\r\n {Locale.SdPanel.Prompt}:{\" \"}\r\n <span\r\n className=\"clickable\"\r\n title={item.params.prompt}\r\n onClick={() => {\r\n showModal({\r\n title: Locale.Sd.Detail,\r\n children: (\r\n <div style={{ userSelect: \"text\" }}>\r\n {item.params.prompt}\r\n </div>\r\n ),\r\n });\r\n }}\r\n >\r\n {item.params.prompt}\r\n </span>\r\n </p>\r\n <p>\r\n {Locale.SdPanel.AIModel}: {item.model_name}\r\n </p>\r\n {getSdTaskStatus(item)}\r\n <p>{item.created_at}</p>\r\n <div className={chatStyles[\"chat-message-actions\"]}>\r\n <div className={chatStyles[\"chat-input-actions\"]}>\r\n <ChatAction\r\n text={Locale.Sd.Actions.Params}\r\n icon={<PromptIcon />}\r\n onClick={() => {\r\n showModal({\r\n title: Locale.Sd.GenerateParams,\r\n children: (\r\n <div style={{ userSelect: \"text\" }}>\r\n {Object.keys(item.params).map((key) => {\r\n let label = key;\r\n let value = item.params[key];\r\n switch (label) {\r\n case \"prompt\":\r\n label = Locale.SdPanel.Prompt;\r\n break;\r\n case \"negative_prompt\":\r\n label =\r\n Locale.SdPanel.NegativePrompt;\r\n break;\r\n case \"aspect_ratio\":\r\n label = Locale.SdPanel.AspectRatio;\r\n break;\r\n case \"seed\":\r\n label = \"Seed\";\r\n value = value || 0;\r\n break;\r\n case \"output_format\":\r\n label = Locale.SdPanel.OutFormat;\r\n value = value?.toUpperCase();\r\n break;\r\n case \"style\":\r\n label = Locale.SdPanel.ImageStyle;\r\n value = params\r\n .find(\r\n (item) =>\r\n item.value === \"style\",\r\n )\r\n ?.options?.find(\r\n (item) => item.value === value,\r\n )?.name;\r\n break;\r\n default:\r\n break;\r\n }\r\n\r\n return (\r\n <div\r\n key={key}\r\n style={{ margin: \"10px\" }}\r\n >\r\n <strong>{label}: </strong>\r\n {value}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n ),\r\n });\r\n }}\r\n />\r\n <ChatAction\r\n text={Locale.Sd.Actions.Copy}\r\n icon={<CopyIcon />}\r\n onClick={() =>\r\n copyToClipboard(\r\n getMessageTextContent({\r\n role: \"user\",\r\n content: item.params.prompt,\r\n }),\r\n )\r\n }\r\n />\r\n <ChatAction\r\n text={Locale.Sd.Actions.Retry}\r\n icon={<ResetIcon />}\r\n onClick={() => {\r\n const reqData = {\r\n model: item.model,\r\n model_name: item.model_name,\r\n status: \"wait\",\r\n params: { ...item.params },\r\n created_at: new Date().toLocaleString(),\r\n img_data: \"\",\r\n };\r\n sdStore.sendTask(reqData);\r\n }}\r\n />\r\n <ChatAction\r\n text={Locale.Sd.Actions.Delete}\r\n icon={<DeleteIcon />}\r\n onClick={async () => {\r\n if (\r\n await showConfirm(Locale.Sd.Danger.Delete)\r\n ) {\r\n // remove img_data + remove item in list\r\n removeImage(item.img_data).finally(() => {\r\n sdStore.draw = sdImages.filter(\r\n (i: any) => i.id !== item.id,\r\n );\r\n sdStore.getNextId();\r\n });\r\n }\r\n }}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n })\r\n ) : (\r\n <div>{Locale.Sd.EmptyRecord}</div>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n </WindowContent>\r\n </>\r\n );\r\n}\r\n","D:\\NextWeb\\app\\components\\search-chat.tsx",["758"],[],"import { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport { ErrorBoundary } from \"./error\";\r\nimport styles from \"./mask.module.scss\";\r\nimport { useNavigate } from \"react-router-dom\";\r\nimport { IconButton } from \"./button\";\r\nimport CloseIcon from \"../icons/close.svg\";\r\nimport EyeIcon from \"../icons/eye.svg\";\r\nimport Locale from \"../locales\";\r\nimport { Path } from \"../constant\";\r\n\r\nimport { useChatStore } from \"../store\";\r\n\r\ntype Item = {\r\n id: number;\r\n name: string;\r\n content: string;\r\n};\r\nexport function SearchChatPage() {\r\n const navigate = useNavigate();\r\n\r\n const chatStore = useChatStore();\r\n\r\n const sessions = chatStore.sessions;\r\n const selectSession = chatStore.selectSession;\r\n\r\n const [searchResults, setSearchResults] = useState<Item[]>([]);\r\n\r\n const previousValueRef = useRef<string>(\"\");\r\n const searchInputRef = useRef<HTMLInputElement>(null);\r\n const doSearch = useCallback((text: string) => {\r\n const lowerCaseText = text.toLowerCase();\r\n const results: Item[] = [];\r\n\r\n sessions.forEach((session, index) => {\r\n const fullTextContents: string[] = [];\r\n\r\n session.messages.forEach((message) => {\r\n const content = message.content as string;\r\n if (!content.toLowerCase || content === \"\") return;\r\n const lowerCaseContent = content.toLowerCase();\r\n\r\n // full text search\r\n let pos = lowerCaseContent.indexOf(lowerCaseText);\r\n while (pos !== -1) {\r\n const start = Math.max(0, pos - 35);\r\n const end = Math.min(content.length, pos + lowerCaseText.length + 35);\r\n fullTextContents.push(content.substring(start, end));\r\n pos = lowerCaseContent.indexOf(\r\n lowerCaseText,\r\n pos + lowerCaseText.length,\r\n );\r\n }\r\n });\r\n\r\n if (fullTextContents.length > 0) {\r\n results.push({\r\n id: index,\r\n name: session.topic,\r\n content: fullTextContents.join(\"... \"), // concat content with...\r\n });\r\n }\r\n });\r\n\r\n // sort by length of matching content\r\n results.sort((a, b) => b.content.length - a.content.length);\r\n\r\n return results;\r\n }, []);\r\n\r\n useEffect(() => {\r\n const intervalId = setInterval(() => {\r\n if (searchInputRef.current) {\r\n const currentValue = searchInputRef.current.value;\r\n if (currentValue !== previousValueRef.current) {\r\n if (currentValue.length > 0) {\r\n const result = doSearch(currentValue);\r\n setSearchResults(result);\r\n }\r\n previousValueRef.current = currentValue;\r\n }\r\n }\r\n }, 1000);\r\n\r\n // Cleanup the interval on component unmount\r\n return () => clearInterval(intervalId);\r\n }, [doSearch]);\r\n\r\n return (\r\n <ErrorBoundary>\r\n <div className={styles[\"mask-page\"]}>\r\n {/* header */}\r\n <div className=\"window-header\">\r\n <div className=\"window-header-title\">\r\n <div className=\"window-header-main-title\">\r\n {Locale.SearchChat.Page.Title}\r\n </div>\r\n <div className=\"window-header-submai-title\">\r\n {Locale.SearchChat.Page.SubTitle(searchResults.length)}\r\n </div>\r\n </div>\r\n\r\n <div className=\"window-actions\">\r\n <div className=\"window-action-button\">\r\n <IconButton\r\n icon={<CloseIcon />}\r\n bordered\r\n onClick={() => navigate(-1)}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div className={styles[\"mask-page-body\"]}>\r\n <div className={styles[\"mask-filter\"]}>\r\n {/**搜索输入框 */}\r\n <input\r\n type=\"text\"\r\n className={styles[\"search-bar\"]}\r\n placeholder={Locale.SearchChat.Page.Search}\r\n autoFocus\r\n ref={searchInputRef}\r\n onKeyDown={(e) => {\r\n if (e.key === \"Enter\") {\r\n e.preventDefault();\r\n const searchText = e.currentTarget.value;\r\n if (searchText.length > 0) {\r\n const result = doSearch(searchText);\r\n setSearchResults(result);\r\n }\r\n }\r\n }}\r\n />\r\n </div>\r\n\r\n <div>\r\n {searchResults.map((item) => (\r\n <div\r\n className={styles[\"mask-item\"]}\r\n key={item.id}\r\n onClick={() => {\r\n navigate(Path.Chat);\r\n selectSession(item.id);\r\n }}\r\n style={{ cursor: \"pointer\" }}\r\n >\r\n {/** 搜索匹配的文本 */}\r\n <div className={styles[\"mask-header\"]}>\r\n <div className={styles[\"mask-title\"]}>\r\n <div className={styles[\"mask-name\"]}>{item.name}</div>\r\n {item.content.slice(0, 70)}\r\n </div>\r\n </div>\r\n {/** 操作按钮 */}\r\n <div className={styles[\"mask-actions\"]}>\r\n <IconButton\r\n icon={<EyeIcon />}\r\n text={Locale.SearchChat.Item.View}\r\n />\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n </ErrorBoundary>\r\n );\r\n}\r\n","D:\\NextWeb\\app\\components\\settings.tsx",["759","760","761","762","763"],["764","765","766"],"import { useState, useEffect, useMemo } from \"react\";\r\n\r\nimport styles from \"./settings.module.scss\";\r\n\r\nimport ResetIcon from \"../icons/reload.svg\";\r\nimport AddIcon from \"../icons/add.svg\";\r\nimport CloseIcon from \"../icons/close.svg\";\r\nimport CopyIcon from \"../icons/copy.svg\";\r\nimport ClearIcon from \"../icons/clear.svg\";\r\nimport LoadingIcon from \"../icons/three-dots.svg\";\r\nimport EditIcon from \"../icons/edit.svg\";\r\nimport FireIcon from \"../icons/fire.svg\";\r\nimport EyeIcon from \"../icons/eye.svg\";\r\nimport DownloadIcon from \"../icons/download.svg\";\r\nimport UploadIcon from \"../icons/upload.svg\";\r\nimport ConfigIcon from \"../icons/config.svg\";\r\nimport ConfirmIcon from \"../icons/confirm.svg\";\r\n\r\nimport ConnectionIcon from \"../icons/connection.svg\";\r\nimport CloudSuccessIcon from \"../icons/cloud-success.svg\";\r\nimport CloudFailIcon from \"../icons/cloud-fail.svg\";\r\nimport { trackSettingsPageGuideToCPaymentClick } from \"../utils/auth-settings-events\";\r\nimport {\r\n Input,\r\n List,\r\n ListItem,\r\n Modal,\r\n PasswordInput,\r\n Popover,\r\n Select,\r\n showConfirm,\r\n showToast,\r\n} from \"./ui-lib\";\r\nimport { ModelConfigList } from \"./model-config\";\r\n\r\nimport { IconButton } from \"./button\";\r\nimport {\r\n SubmitKey,\r\n useChatStore,\r\n Theme,\r\n useUpdateStore,\r\n useAccessStore,\r\n useAppConfig,\r\n} from \"../store\";\r\n\r\nimport Locale, {\r\n AllLangs,\r\n ALL_LANG_OPTIONS,\r\n changeLang,\r\n getLang,\r\n} from \"../locales\";\r\nimport { copyToClipboard, clientUpdate, semverCompare } from \"../utils\";\r\nimport Link from \"next/link\";\r\nimport {\r\n Anthropic,\r\n Azure,\r\n Baidu,\r\n Tencent,\r\n ByteDance,\r\n Alibaba,\r\n Moonshot,\r\n XAI,\r\n Google,\r\n GoogleSafetySettingsThreshold,\r\n OPENAI_BASE_URL,\r\n Path,\r\n RELEASE_URL,\r\n STORAGE_KEY,\r\n ServiceProvider,\r\n SlotID,\r\n UPDATE_URL,\r\n Stability,\r\n Iflytek,\r\n SAAS_CHAT_URL,\r\n ChatGLM,\r\n DeepSeek,\r\n SiliconFlow,\r\n} from \"../constant\";\r\nimport { Prompt, SearchService, usePromptStore } from \"../store/prompt\";\r\nimport { ErrorBoundary } from \"./error\";\r\nimport { InputRange } from \"./input-range\";\r\nimport { useNavigate } from \"react-router-dom\";\r\nimport { Avatar, AvatarPicker } from \"./emoji\";\r\nimport { getClientConfig } from \"../config/client\";\r\nimport { useSyncStore } from \"../store/sync\";\r\nimport { nanoid } from \"nanoid\";\r\nimport { useMaskStore } from \"../store/mask\";\r\nimport { ProviderType } from \"../utils/cloud\";\r\nimport { TTSConfigList } from \"./tts-config\";\r\nimport { RealtimeConfigList } from \"./realtime-chat/realtime-config\";\r\n\r\nfunction EditPromptModal(props: { id: string; onClose: () => void }) {\r\n const promptStore = usePromptStore();\r\n const prompt = promptStore.get(props.id);\r\n\r\n return prompt ? (\r\n <div className=\"modal-mask\">\r\n <Modal\r\n title={Locale.Settings.Prompt.EditModal.Title}\r\n onClose={props.onClose}\r\n actions={[\r\n <IconButton\r\n key=\"\"\r\n onClick={props.onClose}\r\n text={Locale.UI.Confirm}\r\n bordered\r\n />,\r\n ]}\r\n >\r\n <div className={styles[\"edit-prompt-modal\"]}>\r\n <input\r\n type=\"text\"\r\n value={prompt.title}\r\n readOnly={!prompt.isUser}\r\n className={styles[\"edit-prompt-title\"]}\r\n onInput={(e) =>\r\n promptStore.updatePrompt(\r\n props.id,\r\n (prompt) => (prompt.title = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n <Input\r\n value={prompt.content}\r\n readOnly={!prompt.isUser}\r\n className={styles[\"edit-prompt-content\"]}\r\n rows={10}\r\n onInput={(e) =>\r\n promptStore.updatePrompt(\r\n props.id,\r\n (prompt) => (prompt.content = e.currentTarget.value),\r\n )\r\n }\r\n ></Input>\r\n </div>\r\n </Modal>\r\n </div>\r\n ) : null;\r\n}\r\n\r\nfunction UserPromptModal(props: { onClose?: () => void }) {\r\n const promptStore = usePromptStore();\r\n const userPrompts = promptStore.getUserPrompts();\r\n const builtinPrompts = SearchService.builtinPrompts;\r\n const allPrompts = userPrompts.concat(builtinPrompts);\r\n const [searchInput, setSearchInput] = useState(\"\");\r\n const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);\r\n const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;\r\n\r\n const [editingPromptId, setEditingPromptId] = useState<string>();\r\n\r\n useEffect(() => {\r\n if (searchInput.length > 0) {\r\n const searchResult = SearchService.search(searchInput);\r\n setSearchPrompts(searchResult);\r\n } else {\r\n setSearchPrompts([]);\r\n }\r\n }, [searchInput]);\r\n\r\n return (\r\n <div className=\"modal-mask\">\r\n <Modal\r\n title={Locale.Settings.Prompt.Modal.Title}\r\n onClose={() => props.onClose?.()}\r\n actions={[\r\n <IconButton\r\n key=\"add\"\r\n onClick={() => {\r\n const promptId = promptStore.add({\r\n id: nanoid(),\r\n createdAt: Date.now(),\r\n title: \"Empty Prompt\",\r\n content: \"Empty Prompt Content\",\r\n });\r\n setEditingPromptId(promptId);\r\n }}\r\n icon={<AddIcon />}\r\n bordered\r\n text={Locale.Settings.Prompt.Modal.Add}\r\n />,\r\n ]}\r\n >\r\n <div className={styles[\"user-prompt-modal\"]}>\r\n <input\r\n type=\"text\"\r\n className={styles[\"user-prompt-search\"]}\r\n placeholder={Locale.Settings.Prompt.Modal.Search}\r\n value={searchInput}\r\n onInput={(e) => setSearchInput(e.currentTarget.value)}\r\n ></input>\r\n\r\n <div className={styles[\"user-prompt-list\"]}>\r\n {prompts.map((v, _) => (\r\n <div className={styles[\"user-prompt-item\"]} key={v.id ?? v.title}>\r\n <div className={styles[\"user-prompt-header\"]}>\r\n <div className={styles[\"user-prompt-title\"]}>{v.title}</div>\r\n <div className={styles[\"user-prompt-content\"] + \" one-line\"}>\r\n {v.content}\r\n </div>\r\n </div>\r\n\r\n <div className={styles[\"user-prompt-buttons\"]}>\r\n {v.isUser && (\r\n <IconButton\r\n icon={<ClearIcon />}\r\n className={styles[\"user-prompt-button\"]}\r\n onClick={() => promptStore.remove(v.id!)}\r\n />\r\n )}\r\n {v.isUser ? (\r\n <IconButton\r\n icon={<EditIcon />}\r\n className={styles[\"user-prompt-button\"]}\r\n onClick={() => setEditingPromptId(v.id)}\r\n />\r\n ) : (\r\n <IconButton\r\n icon={<EyeIcon />}\r\n className={styles[\"user-prompt-button\"]}\r\n onClick={() => setEditingPromptId(v.id)}\r\n />\r\n )}\r\n <IconButton\r\n icon={<CopyIcon />}\r\n className={styles[\"user-prompt-button\"]}\r\n onClick={() => copyToClipboard(v.content)}\r\n />\r\n </div>\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n </Modal>\r\n\r\n {editingPromptId !== undefined && (\r\n <EditPromptModal\r\n id={editingPromptId!}\r\n onClose={() => setEditingPromptId(undefined)}\r\n />\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nfunction DangerItems() {\r\n const chatStore = useChatStore();\r\n const appConfig = useAppConfig();\r\n\r\n return (\r\n <List>\r\n <ListItem\r\n title={Locale.Settings.Danger.Reset.Title}\r\n subTitle={Locale.Settings.Danger.Reset.SubTitle}\r\n >\r\n <IconButton\r\n aria={Locale.Settings.Danger.Reset.Title}\r\n text={Locale.Settings.Danger.Reset.Action}\r\n onClick={async () => {\r\n if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {\r\n appConfig.reset();\r\n }\r\n }}\r\n type=\"danger\"\r\n />\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Danger.Clear.Title}\r\n subTitle={Locale.Settings.Danger.Clear.SubTitle}\r\n >\r\n <IconButton\r\n aria={Locale.Settings.Danger.Clear.Title}\r\n text={Locale.Settings.Danger.Clear.Action}\r\n onClick={async () => {\r\n if (await showConfirm(Locale.Settings.Danger.Clear.Confirm)) {\r\n chatStore.clearAllData();\r\n }\r\n }}\r\n type=\"danger\"\r\n />\r\n </ListItem>\r\n </List>\r\n );\r\n}\r\n\r\nfunction CheckButton() {\r\n const syncStore = useSyncStore();\r\n\r\n const couldCheck = useMemo(() => {\r\n return syncStore.cloudSync();\r\n }, [syncStore]);\r\n\r\n const [checkState, setCheckState] = useState<\r\n \"none\" | \"checking\" | \"success\" | \"failed\"\r\n >(\"none\");\r\n\r\n async function check() {\r\n setCheckState(\"checking\");\r\n const valid = await syncStore.check();\r\n setCheckState(valid ? \"success\" : \"failed\");\r\n }\r\n\r\n if (!couldCheck) return null;\r\n\r\n return (\r\n <IconButton\r\n text={Locale.Settings.Sync.Config.Modal.Check}\r\n bordered\r\n onClick={check}\r\n icon={\r\n checkState === \"none\" ? (\r\n <ConnectionIcon />\r\n ) : checkState === \"checking\" ? (\r\n <LoadingIcon />\r\n ) : checkState === \"success\" ? (\r\n <CloudSuccessIcon />\r\n ) : checkState === \"failed\" ? (\r\n <CloudFailIcon />\r\n ) : (\r\n <ConnectionIcon />\r\n )\r\n }\r\n ></IconButton>\r\n );\r\n}\r\n\r\nfunction SyncConfigModal(props: { onClose?: () => void }) {\r\n const syncStore = useSyncStore();\r\n\r\n return (\r\n <div className=\"modal-mask\">\r\n <Modal\r\n title={Locale.Settings.Sync.Config.Modal.Title}\r\n onClose={() => props.onClose?.()}\r\n actions={[\r\n <CheckButton key=\"check\" />,\r\n <IconButton\r\n key=\"confirm\"\r\n onClick={props.onClose}\r\n icon={<ConfirmIcon />}\r\n bordered\r\n text={Locale.UI.Confirm}\r\n />,\r\n ]}\r\n >\r\n <List>\r\n <ListItem\r\n title={Locale.Settings.Sync.Config.SyncType.Title}\r\n subTitle={Locale.Settings.Sync.Config.SyncType.SubTitle}\r\n >\r\n <select\r\n value={syncStore.provider}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) =>\r\n (config.provider = e.target.value as ProviderType),\r\n );\r\n }}\r\n >\r\n {Object.entries(ProviderType).map(([k, v]) => (\r\n <option value={v} key={k}>\r\n {k}\r\n </option>\r\n ))}\r\n </select>\r\n </ListItem>\r\n\r\n <ListItem\r\n title={Locale.Settings.Sync.Config.Proxy.Title}\r\n subTitle={Locale.Settings.Sync.Config.Proxy.SubTitle}\r\n >\r\n <input\r\n type=\"checkbox\"\r\n checked={syncStore.useProxy}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) => (config.useProxy = e.currentTarget.checked),\r\n );\r\n }}\r\n ></input>\r\n </ListItem>\r\n {syncStore.useProxy ? (\r\n <ListItem\r\n title={Locale.Settings.Sync.Config.ProxyUrl.Title}\r\n subTitle={Locale.Settings.Sync.Config.ProxyUrl.SubTitle}\r\n >\r\n <input\r\n type=\"text\"\r\n value={syncStore.proxyUrl}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) => (config.proxyUrl = e.currentTarget.value),\r\n );\r\n }}\r\n ></input>\r\n </ListItem>\r\n ) : null}\r\n </List>\r\n\r\n {syncStore.provider === ProviderType.WebDAV && (\r\n <>\r\n <List>\r\n <ListItem title={Locale.Settings.Sync.Config.WebDav.Endpoint}>\r\n <input\r\n type=\"text\"\r\n value={syncStore.webdav.endpoint}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) =>\r\n (config.webdav.endpoint = e.currentTarget.value),\r\n );\r\n }}\r\n ></input>\r\n </ListItem>\r\n\r\n <ListItem title={Locale.Settings.Sync.Config.WebDav.UserName}>\r\n <input\r\n type=\"text\"\r\n value={syncStore.webdav.username}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) =>\r\n (config.webdav.username = e.currentTarget.value),\r\n );\r\n }}\r\n ></input>\r\n </ListItem>\r\n <ListItem title={Locale.Settings.Sync.Config.WebDav.Password}>\r\n <PasswordInput\r\n value={syncStore.webdav.password}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) =>\r\n (config.webdav.password = e.currentTarget.value),\r\n );\r\n }}\r\n ></PasswordInput>\r\n </ListItem>\r\n </List>\r\n </>\r\n )}\r\n\r\n {syncStore.provider === ProviderType.UpStash && (\r\n <List>\r\n <ListItem title={Locale.Settings.Sync.Config.UpStash.Endpoint}>\r\n <input\r\n type=\"text\"\r\n value={syncStore.upstash.endpoint}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) =>\r\n (config.upstash.endpoint = e.currentTarget.value),\r\n );\r\n }}\r\n ></input>\r\n </ListItem>\r\n\r\n <ListItem title={Locale.Settings.Sync.Config.UpStash.UserName}>\r\n <input\r\n type=\"text\"\r\n value={syncStore.upstash.username}\r\n placeholder={STORAGE_KEY}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) =>\r\n (config.upstash.username = e.currentTarget.value),\r\n );\r\n }}\r\n ></input>\r\n </ListItem>\r\n <ListItem title={Locale.Settings.Sync.Config.UpStash.Password}>\r\n <PasswordInput\r\n value={syncStore.upstash.apiKey}\r\n onChange={(e) => {\r\n syncStore.update(\r\n (config) => (config.upstash.apiKey = e.currentTarget.value),\r\n );\r\n }}\r\n ></PasswordInput>\r\n </ListItem>\r\n </List>\r\n )}\r\n </Modal>\r\n </div>\r\n );\r\n}\r\n\r\nfunction SyncItems() {\r\n const syncStore = useSyncStore();\r\n const chatStore = useChatStore();\r\n const promptStore = usePromptStore();\r\n const maskStore = useMaskStore();\r\n const couldSync = useMemo(() => {\r\n return syncStore.cloudSync();\r\n }, [syncStore]);\r\n\r\n const [showSyncConfigModal, setShowSyncConfigModal] = useState(false);\r\n\r\n const stateOverview = useMemo(() => {\r\n const sessions = chatStore.sessions;\r\n const messageCount = sessions.reduce((p, c) => p + c.messages.length, 0);\r\n\r\n return {\r\n chat: sessions.length,\r\n message: messageCount,\r\n prompt: Object.keys(promptStore.prompts).length,\r\n mask: Object.keys(maskStore.masks).length,\r\n };\r\n }, [chatStore.sessions, maskStore.masks, promptStore.prompts]);\r\n\r\n return (\r\n <>\r\n <List>\r\n <ListItem\r\n title={Locale.Settings.Sync.LocalState}\r\n subTitle={Locale.Settings.Sync.Overview(stateOverview)}\r\n >\r\n <div style={{ display: \"flex\" }}>\r\n <IconButton\r\n aria={Locale.Settings.Sync.LocalState + Locale.UI.Export}\r\n icon={<UploadIcon />}\r\n text={Locale.UI.Export}\r\n onClick={() => {\r\n syncStore.export();\r\n }}\r\n />\r\n <IconButton\r\n aria={Locale.Settings.Sync.LocalState + Locale.UI.Import}\r\n icon={<DownloadIcon />}\r\n text={Locale.UI.Import}\r\n onClick={() => {\r\n syncStore.import();\r\n }}\r\n />\r\n </div>\r\n </ListItem>\r\n </List>\r\n\r\n {showSyncConfigModal && (\r\n <SyncConfigModal onClose={() => setShowSyncConfigModal(false)} />\r\n )}\r\n </>\r\n );\r\n}\r\n\r\nexport function Settings() {\r\n const navigate = useNavigate();\r\n const [showEmojiPicker, setShowEmojiPicker] = useState(false);\r\n const config = useAppConfig();\r\n const updateConfig = config.update;\r\n\r\n const updateStore = useUpdateStore();\r\n const [checkingUpdate, setCheckingUpdate] = useState(false);\r\n const currentVersion = updateStore.formatVersion(updateStore.version);\r\n const remoteId = updateStore.formatVersion(updateStore.remoteVersion);\r\n const hasNewVersion = semverCompare(currentVersion, remoteId) === -1;\r\n const updateUrl = getClientConfig()?.isApp ? RELEASE_URL : UPDATE_URL;\r\n\r\n function checkUpdate(force = false) {\r\n setCheckingUpdate(true);\r\n updateStore.getLatestVersion(force).then(() => {\r\n setCheckingUpdate(false);\r\n });\r\n\r\n console.log(\"[Update] local version \", updateStore.version);\r\n console.log(\"[Update] remote version \", updateStore.remoteVersion);\r\n }\r\n\r\n const accessStore = useAccessStore();\r\n const shouldHideBalanceQuery = useMemo(() => {\r\n const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);\r\n\r\n return (\r\n accessStore.hideBalanceQuery ||\r\n isOpenAiUrl ||\r\n accessStore.provider === ServiceProvider.Azure\r\n );\r\n }, [\r\n accessStore.hideBalanceQuery,\r\n accessStore.openaiUrl,\r\n accessStore.provider,\r\n ]);\r\n\r\n const usage = {\r\n used: updateStore.used,\r\n subscription: updateStore.subscription,\r\n };\r\n const [loadingUsage, setLoadingUsage] = useState(false);\r\n function checkUsage(force = false) {\r\n if (shouldHideBalanceQuery) {\r\n return;\r\n }\r\n\r\n setLoadingUsage(true);\r\n updateStore.updateUsage(force).finally(() => {\r\n setLoadingUsage(false);\r\n });\r\n }\r\n\r\n const enabledAccessControl = useMemo(\r\n () => accessStore.enabledAccessControl(),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [],\r\n );\r\n\r\n const promptStore = usePromptStore();\r\n const builtinCount = SearchService.count.builtin;\r\n const customCount = promptStore.getUserPrompts().length ?? 0;\r\n const [shouldShowPromptModal, setShowPromptModal] = useState(false);\r\n\r\n const showUsage = accessStore.isAuthorized();\r\n useEffect(() => {\r\n // checks per minutes\r\n checkUpdate();\r\n showUsage && checkUsage();\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n useEffect(() => {\r\n const keydownEvent = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\") {\r\n navigate(Path.Home);\r\n }\r\n };\r\n if (clientConfig?.isApp) {\r\n // Force to set custom endpoint to true if it's app\r\n accessStore.update((state) => {\r\n state.useCustomConfig = true;\r\n });\r\n }\r\n document.addEventListener(\"keydown\", keydownEvent);\r\n return () => {\r\n document.removeEventListener(\"keydown\", keydownEvent);\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n const clientConfig = useMemo(() => getClientConfig(), []);\r\n const showAccessCode = enabledAccessControl && !clientConfig?.isApp;\r\n\r\n const accessCodeComponent = showAccessCode && (\r\n <ListItem\r\n title={Locale.Settings.Access.AccessCode.Title}\r\n subTitle={Locale.Settings.Access.AccessCode.SubTitle}\r\n >\r\n <PasswordInput\r\n value={accessStore.accessCode}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.AccessCode.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.accessCode = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n );\r\n\r\n const saasStartComponent = (\r\n <ListItem\r\n className={styles[\"subtitle-button\"]}\r\n title={\r\n Locale.Settings.Access.SaasStart.Title +\r\n `${Locale.Settings.Access.SaasStart.Label}`\r\n }\r\n subTitle={Locale.Settings.Access.SaasStart.SubTitle}\r\n >\r\n <IconButton\r\n aria={\r\n Locale.Settings.Access.SaasStart.Title +\r\n Locale.Settings.Access.SaasStart.ChatNow\r\n }\r\n icon={<FireIcon />}\r\n type={\"primary\"}\r\n text={Locale.Settings.Access.SaasStart.ChatNow}\r\n onClick={() => {\r\n trackSettingsPageGuideToCPaymentClick();\r\n window.location.href = SAAS_CHAT_URL;\r\n }}\r\n />\r\n </ListItem>\r\n );\r\n\r\n const useCustomConfigComponent = // Conditionally render the following ListItem based on clientConfig.isApp\r\n !clientConfig?.isApp && ( // only show if isApp is false\r\n <ListItem\r\n title={Locale.Settings.Access.CustomEndpoint.Title}\r\n subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.CustomEndpoint.Title}\r\n type=\"checkbox\"\r\n checked={accessStore.useCustomConfig}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.useCustomConfig = e.currentTarget.checked),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n );\r\n\r\n const openAIConfigComponent = accessStore.provider ===\r\n ServiceProvider.OpenAI && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.OpenAI.Endpoint.Title}\r\n subTitle={Locale.Settings.Access.OpenAI.Endpoint.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.OpenAI.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.openaiUrl}\r\n placeholder={OPENAI_BASE_URL}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.openaiUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.OpenAI.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria={Locale.Settings.ShowPassword}\r\n aria-label={Locale.Settings.Access.OpenAI.ApiKey.Title}\r\n value={accessStore.openaiApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.openaiApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const azureConfigComponent = accessStore.provider ===\r\n ServiceProvider.Azure && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Azure.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.Azure.Endpoint.SubTitle + Azure.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Azure.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.azureUrl}\r\n placeholder={Azure.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.azureUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Azure.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Azure.ApiKey.Title}\r\n value={accessStore.azureApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Azure.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.azureApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Azure.ApiVerion.Title}\r\n subTitle={Locale.Settings.Access.Azure.ApiVerion.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Azure.ApiVerion.Title}\r\n type=\"text\"\r\n value={accessStore.azureApiVersion}\r\n placeholder=\"2023-08-01-preview\"\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.azureApiVersion = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const googleConfigComponent = accessStore.provider ===\r\n ServiceProvider.Google && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Google.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.Google.Endpoint.SubTitle +\r\n Google.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Google.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.googleUrl}\r\n placeholder={Google.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.googleUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Google.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Google.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Google.ApiKey.Title}\r\n value={accessStore.googleApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.googleApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Google.ApiVersion.Title}\r\n subTitle={Locale.Settings.Access.Google.ApiVersion.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Google.ApiVersion.Title}\r\n type=\"text\"\r\n value={accessStore.googleApiVersion}\r\n placeholder=\"2023-08-01-preview\"\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.googleApiVersion = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Google.GoogleSafetySettings.Title}\r\n subTitle={Locale.Settings.Access.Google.GoogleSafetySettings.SubTitle}\r\n >\r\n <Select\r\n aria-label={Locale.Settings.Access.Google.GoogleSafetySettings.Title}\r\n value={accessStore.googleSafetySettings}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) =>\r\n (access.googleSafetySettings = e.target\r\n .value as GoogleSafetySettingsThreshold),\r\n );\r\n }}\r\n >\r\n {Object.entries(GoogleSafetySettingsThreshold).map(([k, v]) => (\r\n <option value={v} key={k}>\r\n {k}\r\n </option>\r\n ))}\r\n </Select>\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const anthropicConfigComponent = accessStore.provider ===\r\n ServiceProvider.Anthropic && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Anthropic.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.Anthropic.Endpoint.SubTitle +\r\n Anthropic.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Anthropic.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.anthropicUrl}\r\n placeholder={Anthropic.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.anthropicUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Anthropic.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Anthropic.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Anthropic.ApiKey.Title}\r\n value={accessStore.anthropicApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Anthropic.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.anthropicApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Anthropic.ApiVerion.Title}\r\n subTitle={Locale.Settings.Access.Anthropic.ApiVerion.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Anthropic.ApiVerion.Title}\r\n type=\"text\"\r\n value={accessStore.anthropicApiVersion}\r\n placeholder={Anthropic.Vision}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.anthropicApiVersion = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const baiduConfigComponent = accessStore.provider ===\r\n ServiceProvider.Baidu && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Baidu.Endpoint.Title}\r\n subTitle={Locale.Settings.Access.Baidu.Endpoint.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Baidu.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.baiduUrl}\r\n placeholder={Baidu.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.baiduUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Baidu.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Baidu.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Baidu.ApiKey.Title}\r\n value={accessStore.baiduApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Baidu.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.baiduApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Baidu.SecretKey.Title}\r\n subTitle={Locale.Settings.Access.Baidu.SecretKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Baidu.SecretKey.Title}\r\n value={accessStore.baiduSecretKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Baidu.SecretKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.baiduSecretKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const tencentConfigComponent = accessStore.provider ===\r\n ServiceProvider.Tencent && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Tencent.Endpoint.Title}\r\n subTitle={Locale.Settings.Access.Tencent.Endpoint.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Tencent.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.tencentUrl}\r\n placeholder={Tencent.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.tencentUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Tencent.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Tencent.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Tencent.ApiKey.Title}\r\n value={accessStore.tencentSecretId}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Tencent.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.tencentSecretId = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Tencent.SecretKey.Title}\r\n subTitle={Locale.Settings.Access.Tencent.SecretKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Tencent.SecretKey.Title}\r\n value={accessStore.tencentSecretKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Tencent.SecretKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.tencentSecretKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const byteDanceConfigComponent = accessStore.provider ===\r\n ServiceProvider.ByteDance && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.ByteDance.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.ByteDance.Endpoint.SubTitle +\r\n ByteDance.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.ByteDance.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.bytedanceUrl}\r\n placeholder={ByteDance.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.bytedanceUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.ByteDance.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.ByteDance.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.ByteDance.ApiKey.Title}\r\n value={accessStore.bytedanceApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.ByteDance.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.bytedanceApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const alibabaConfigComponent = accessStore.provider ===\r\n ServiceProvider.Alibaba && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Alibaba.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.Alibaba.Endpoint.SubTitle +\r\n Alibaba.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Alibaba.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.alibabaUrl}\r\n placeholder={Alibaba.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.alibabaUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Alibaba.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Alibaba.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Alibaba.ApiKey.Title}\r\n value={accessStore.alibabaApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Alibaba.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.alibabaApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const moonshotConfigComponent = accessStore.provider ===\r\n ServiceProvider.Moonshot && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Moonshot.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.Moonshot.Endpoint.SubTitle +\r\n Moonshot.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Moonshot.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.moonshotUrl}\r\n placeholder={Moonshot.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.moonshotUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Moonshot.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Moonshot.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Moonshot.ApiKey.Title}\r\n value={accessStore.moonshotApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Moonshot.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.moonshotApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const deepseekConfigComponent = accessStore.provider ===\r\n ServiceProvider.DeepSeek && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.DeepSeek.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.DeepSeek.Endpoint.SubTitle +\r\n DeepSeek.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.DeepSeek.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.deepseekUrl}\r\n placeholder={DeepSeek.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.deepseekUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.DeepSeek.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.DeepSeek.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.DeepSeek.ApiKey.Title}\r\n value={accessStore.deepseekApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.DeepSeek.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.deepseekApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const XAIConfigComponent = accessStore.provider === ServiceProvider.XAI && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.XAI.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.XAI.Endpoint.SubTitle + XAI.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.XAI.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.xaiUrl}\r\n placeholder={XAI.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.xaiUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.XAI.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.XAI.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.XAI.ApiKey.Title}\r\n value={accessStore.xaiApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.XAI.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.xaiApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const chatglmConfigComponent = accessStore.provider ===\r\n ServiceProvider.ChatGLM && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.ChatGLM.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.ChatGLM.Endpoint.SubTitle +\r\n ChatGLM.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.ChatGLM.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.chatglmUrl}\r\n placeholder={ChatGLM.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.chatglmUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.ChatGLM.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.ChatGLM.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.ChatGLM.ApiKey.Title}\r\n value={accessStore.chatglmApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.ChatGLM.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.chatglmApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n const siliconflowConfigComponent = accessStore.provider ===\r\n ServiceProvider.SiliconFlow && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.SiliconFlow.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.SiliconFlow.Endpoint.SubTitle +\r\n SiliconFlow.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.SiliconFlow.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.siliconflowUrl}\r\n placeholder={SiliconFlow.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.siliconflowUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.SiliconFlow.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.SiliconFlow.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.SiliconFlow.ApiKey.Title}\r\n value={accessStore.siliconflowApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.SiliconFlow.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.siliconflowApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n const stabilityConfigComponent = accessStore.provider ===\r\n ServiceProvider.Stability && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Stability.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.Stability.Endpoint.SubTitle +\r\n Stability.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Stability.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.stabilityUrl}\r\n placeholder={Stability.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.stabilityUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Stability.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Stability.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Stability.ApiKey.Title}\r\n value={accessStore.stabilityApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Stability.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.stabilityApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n const lflytekConfigComponent = accessStore.provider ===\r\n ServiceProvider.Iflytek && (\r\n <>\r\n <ListItem\r\n title={Locale.Settings.Access.Iflytek.Endpoint.Title}\r\n subTitle={\r\n Locale.Settings.Access.Iflytek.Endpoint.SubTitle +\r\n Iflytek.ExampleEndpoint\r\n }\r\n >\r\n <input\r\n aria-label={Locale.Settings.Access.Iflytek.Endpoint.Title}\r\n type=\"text\"\r\n value={accessStore.iflytekUrl}\r\n placeholder={Iflytek.ExampleEndpoint}\r\n onChange={(e) =>\r\n accessStore.update(\r\n (access) => (access.iflytekUrl = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Settings.Access.Iflytek.ApiKey.Title}\r\n subTitle={Locale.Settings.Access.Iflytek.ApiKey.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Iflytek.ApiKey.Title}\r\n value={accessStore.iflytekApiKey}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Iflytek.ApiKey.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.iflytekApiKey = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n\r\n <ListItem\r\n title={Locale.Settings.Access.Iflytek.ApiSecret.Title}\r\n subTitle={Locale.Settings.Access.Iflytek.ApiSecret.SubTitle}\r\n >\r\n <PasswordInput\r\n aria-label={Locale.Settings.Access.Iflytek.ApiSecret.Title}\r\n value={accessStore.iflytekApiSecret}\r\n type=\"text\"\r\n placeholder={Locale.Settings.Access.Iflytek.ApiSecret.Placeholder}\r\n onChange={(e) => {\r\n accessStore.update(\r\n (access) => (access.iflytekApiSecret = e.currentTarget.value),\r\n );\r\n }}\r\n />\r\n </ListItem>\r\n </>\r\n );\r\n\r\n return (\r\n <ErrorBoundary>\r\n <div className=\"window-header\" data-tauri-drag-region>\r\n <div className=\"window-header-title\">\r\n <div className=\"window-header-main-title\">\r\n {Locale.Settings.Title}\r\n </div>\r\n <div className=\"window-header-sub-title\">\r\n {Locale.Settings.SubTitle}\r\n </div>\r\n </div>\r\n <div className=\"window-actions\">\r\n <div className=\"window-action-button\"></div>\r\n <div className=\"window-action-button\"></div>\r\n <div className=\"window-action-button\">\r\n <IconButton\r\n aria={Locale.UI.Close}\r\n icon={<CloseIcon />}\r\n onClick={() => navigate(Path.Home)}\r\n bordered\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n <div className={styles[\"settings\"]}>\r\n <List>\r\n <ListItem title={Locale.Settings.Avatar}>\r\n <Popover\r\n onClose={() => setShowEmojiPicker(false)}\r\n content={\r\n <AvatarPicker\r\n onEmojiClick={(avatar: string) => {\r\n updateConfig((config) => (config.avatar = avatar));\r\n setShowEmojiPicker(false);\r\n }}\r\n />\r\n }\r\n open={showEmojiPicker}\r\n >\r\n <div\r\n aria-label={Locale.Settings.Avatar}\r\n tabIndex={0}\r\n className={styles.avatar}\r\n onClick={() => {\r\n setShowEmojiPicker(!showEmojiPicker);\r\n }}\r\n >\r\n <Avatar avatar={config.avatar} />\r\n </div>\r\n </Popover>\r\n </ListItem>\r\n\r\n <ListItem\r\n title={Locale.Settings.Update.Version(currentVersion ?? \"unknown\")}\r\n subTitle={\r\n checkingUpdate\r\n ? Locale.Settings.Update.IsChecking\r\n : hasNewVersion\r\n ? Locale.Settings.Update.FoundUpdate(remoteId ?? \"ERROR\")\r\n : Locale.Settings.Update.IsLatest\r\n }\r\n >\r\n {checkingUpdate ? (\r\n <LoadingIcon />\r\n ) : hasNewVersion ? (\r\n clientConfig?.isApp ? (\r\n <IconButton\r\n icon={<ResetIcon></ResetIcon>}\r\n text={Locale.Settings.Update.GoToUpdate}\r\n onClick={() => clientUpdate()}\r\n />\r\n ) : (\r\n <Link href={updateUrl} target=\"_blank\" className=\"link\">\r\n {Locale.Settings.Update.GoToUpdate}\r\n </Link>\r\n )\r\n ) : (\r\n <IconButton\r\n icon={<ResetIcon></ResetIcon>}\r\n text={Locale.Settings.Update.CheckUpdate}\r\n onClick={() => checkUpdate(true)}\r\n />\r\n )}\r\n </ListItem>\r\n\r\n <ListItem title={Locale.Settings.SendKey}>\r\n <Select\r\n aria-label={Locale.Settings.SendKey}\r\n value={config.submitKey}\r\n onChange={(e) => {\r\n updateConfig(\r\n (config) =>\r\n (config.submitKey = e.target.value as any as SubmitKey),\r\n );\r\n }}\r\n >\r\n {Object.values(SubmitKey).map((v) => (\r\n <option value={v} key={v}>\r\n {v}\r\n </option>\r\n ))}\r\n </Select>\r\n </ListItem>\r\n\r\n <ListItem title={Locale.Settings.Theme}>\r\n <Select\r\n aria-label={Locale.Settings.Theme}\r\n value={config.theme}\r\n onChange={(e) => {\r\n updateConfig(\r\n (config) => (config.theme = e.target.value as any as Theme),\r\n );\r\n }}\r\n >\r\n {Object.values(Theme).map((v) => (\r\n <option value={v} key={v}>\r\n {v}\r\n </option>\r\n ))}\r\n </Select>\r\n </ListItem>\r\n\r\n <ListItem title={Locale.Settings.Lang.Name}>\r\n <Select\r\n aria-label={Locale.Settings.Lang.Name}\r\n value={getLang()}\r\n onChange={(e) => {\r\n changeLang(e.target.value as any);\r\n }}\r\n >\r\n {AllLangs.map((lang) => (\r\n <option value={lang} key={lang}>\r\n {ALL_LANG_OPTIONS[lang]}\r\n </option>\r\n ))}\r\n </Select>\r\n </ListItem>\r\n\r\n <ListItem\r\n title={Locale.Settings.FontSize.Title}\r\n subTitle={Locale.Settings.FontSize.SubTitle}\r\n >\r\n <InputRange\r\n aria={Locale.Settings.FontSize.Title}\r\n title={`${config.fontSize ?? 14}px`}\r\n value={config.fontSize}\r\n min=\"12\"\r\n max=\"40\"\r\n step=\"1\"\r\n onChange={(e) =>\r\n updateConfig(\r\n (config) =>\r\n (config.fontSize = Number.parseInt(e.currentTarget.value)),\r\n )\r\n }\r\n ></InputRange>\r\n </ListItem>\r\n\r\n <ListItem\r\n title={Locale.Settings.FontFamily.Title}\r\n subTitle={Locale.Settings.FontFamily.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.FontFamily.Title}\r\n type=\"text\"\r\n value={config.fontFamily}\r\n placeholder={Locale.Settings.FontFamily.Placeholder}\r\n onChange={(e) =>\r\n updateConfig(\r\n (config) => (config.fontFamily = e.currentTarget.value),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n\r\n <ListItem\r\n title={Locale.Settings.AutoGenerateTitle.Title}\r\n subTitle={Locale.Settings.AutoGenerateTitle.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.AutoGenerateTitle.Title}\r\n type=\"checkbox\"\r\n checked={config.enableAutoGenerateTitle}\r\n onChange={(e) =>\r\n updateConfig(\r\n (config) =>\r\n (config.enableAutoGenerateTitle = e.currentTarget.checked),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n\r\n <ListItem\r\n title={Locale.Settings.SendPreviewBubble.Title}\r\n subTitle={Locale.Settings.SendPreviewBubble.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Settings.SendPreviewBubble.Title}\r\n type=\"checkbox\"\r\n checked={config.sendPreviewBubble}\r\n onChange={(e) =>\r\n updateConfig(\r\n (config) =>\r\n (config.sendPreviewBubble = e.currentTarget.checked),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n\r\n <ListItem\r\n title={Locale.Mask.Config.Artifacts.Title}\r\n subTitle={Locale.Mask.Config.Artifacts.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Mask.Config.Artifacts.Title}\r\n type=\"checkbox\"\r\n checked={config.enableArtifacts}\r\n onChange={(e) =>\r\n updateConfig(\r\n (config) =>\r\n (config.enableArtifacts = e.currentTarget.checked),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n <ListItem\r\n title={Locale.Mask.Config.CodeFold.Title}\r\n subTitle={Locale.Mask.Config.CodeFold.SubTitle}\r\n >\r\n <input\r\n aria-label={Locale.Mask.Config.CodeFold.Title}\r\n type=\"checkbox\"\r\n checked={config.enableCodeFold}\r\n data-testid=\"enable-code-fold-checkbox\"\r\n onChange={(e) =>\r\n updateConfig(\r\n (config) => (config.enableCodeFold = e.currentTarget.checked),\r\n )\r\n }\r\n ></input>\r\n </ListItem>\r\n </List>\r\n\r\n <SyncItems />\r\n\r\n <List>\r\n <ModelConfigList\r\n modelConfig={config.modelConfig}\r\n updateConfig={(updater) => {\r\n const modelConfig = { ...config.modelConfig };\r\n updater(modelConfig);\r\n config.update((config) => (config.modelConfig = modelConfig));\r\n }}\r\n />\r\n </List>\r\n\r\n <DangerItems />\r\n </div>\r\n </ErrorBoundary>\r\n );\r\n}\r\n","D:\\NextWeb\\app\\components\\sidebar.tsx",["767","768"],[],"import React, { Fragment, useEffect, useMemo, useRef, useState } from \"react\";\r\n\r\nimport styles from \"./home.module.scss\";\r\n\r\nimport { IconButton } from \"./button\";\r\nimport SettingsIcon from \"../icons/settings.svg\";\r\nimport GithubIcon from \"../icons/github.svg\";\r\nimport ChatGptIcon from \"../icons/chatgpt.svg\";\r\nimport AddIcon from \"../icons/add.svg\";\r\nimport DeleteIcon from \"../icons/delete.svg\";\r\nimport MaskIcon from \"../icons/mask.svg\";\r\nimport DragIcon from \"../icons/drag.svg\";\r\n\r\nimport Locale from \"../locales\";\r\n\r\nimport { useAppConfig, useChatStore } from \"../store\";\r\n\r\nimport {\r\n DEFAULT_SIDEBAR_WIDTH,\r\n MAX_SIDEBAR_WIDTH,\r\n MIN_SIDEBAR_WIDTH,\r\n NARROW_SIDEBAR_WIDTH,\r\n Path,\r\n REPO_URL,\r\n} from \"../constant\";\r\n\r\nimport { Link, useNavigate } from \"react-router-dom\";\r\nimport { isIOS, useMobileScreen } from \"../utils\";\r\nimport dynamic from \"next/dynamic\";\r\nimport { Selector, showConfirm } from \"./ui-lib\";\r\nimport clsx from \"clsx\";\r\nimport { isMcpEnabled } from \"../mcp/actions\";\r\n\r\nconst DISCOVERY = [\r\n { name: Locale.Plugin.Name, path: Path.Plugins },\r\n { name: \"Stable Diffusion\", path: Path.Sd },\r\n { name: Locale.SearchChat.Page.Title, path: Path.SearchChat },\r\n];\r\n\r\nconst ChatList = dynamic(async () => (await import(\"./chat-list\")).ChatList, {\r\n loading: () => null,\r\n});\r\n\r\nexport function useHotKey() {\r\n const chatStore = useChatStore();\r\n\r\n useEffect(() => {\r\n const onKeyDown = (e: KeyboardEvent) => {\r\n if (e.altKey || e.ctrlKey) {\r\n if (e.key === \"ArrowUp\") {\r\n chatStore.nextSession(-1);\r\n } else if (e.key === \"ArrowDown\") {\r\n chatStore.nextSession(1);\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener(\"keydown\", onKeyDown);\r\n return () => window.removeEventListener(\"keydown\", onKeyDown);\r\n });\r\n}\r\n\r\nexport function useDragSideBar() {\r\n const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);\r\n\r\n const config = useAppConfig();\r\n const startX = useRef(0);\r\n const startDragWidth = useRef(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);\r\n const lastUpdateTime = useRef(Date.now());\r\n\r\n const toggleSideBar = () => {\r\n config.update((config) => {\r\n if (config.sidebarWidth < MIN_SIDEBAR_WIDTH) {\r\n config.sidebarWidth = DEFAULT_SIDEBAR_WIDTH;\r\n } else {\r\n config.sidebarWidth = NARROW_SIDEBAR_WIDTH;\r\n }\r\n });\r\n };\r\n\r\n const onDragStart = (e: MouseEvent) => {\r\n // Remembers the initial width each time the mouse is pressed\r\n startX.current = e.clientX;\r\n startDragWidth.current = config.sidebarWidth;\r\n const dragStartTime = Date.now();\r\n\r\n const handleDragMove = (e: MouseEvent) => {\r\n if (Date.now() < lastUpdateTime.current + 20) {\r\n return;\r\n }\r\n lastUpdateTime.current = Date.now();\r\n const d = e.clientX - startX.current;\r\n const nextWidth = limit(startDragWidth.current + d);\r\n config.update((config) => {\r\n if (nextWidth < MIN_SIDEBAR_WIDTH) {\r\n config.sidebarWidth = NARROW_SIDEBAR_WIDTH;\r\n } else {\r\n config.sidebarWidth = nextWidth;\r\n }\r\n });\r\n };\r\n\r\n const handleDragEnd = () => {\r\n // In useRef the data is non-responsive, so `config.sidebarWidth` can't get the dynamic sidebarWidth\r\n window.removeEventListener(\"pointermove\", handleDragMove);\r\n window.removeEventListener(\"pointerup\", handleDragEnd);\r\n\r\n // if user click the drag icon, should toggle the sidebar\r\n const shouldFireClick = Date.now() - dragStartTime < 300;\r\n if (shouldFireClick) {\r\n toggleSideBar();\r\n }\r\n };\r\n\r\n window.addEventListener(\"pointermove\", handleDragMove);\r\n window.addEventListener(\"pointerup\", handleDragEnd);\r\n };\r\n\r\n const isMobileScreen = useMobileScreen();\r\n const shouldNarrow =\r\n !isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH;\r\n\r\n useEffect(() => {\r\n const barWidth = shouldNarrow\r\n ? NARROW_SIDEBAR_WIDTH\r\n : limit(config.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH);\r\n const sideBarWidth = isMobileScreen ? \"100vw\" : `${barWidth}px`;\r\n document.documentElement.style.setProperty(\"--sidebar-width\", sideBarWidth);\r\n }, [config.sidebarWidth, isMobileScreen, shouldNarrow]);\r\n\r\n return {\r\n onDragStart,\r\n shouldNarrow,\r\n };\r\n}\r\n\r\nexport function SideBarContainer(props: {\r\n children: React.ReactNode;\r\n onDragStart: (e: MouseEvent) => void;\r\n shouldNarrow: boolean;\r\n className?: string;\r\n}) {\r\n const isMobileScreen = useMobileScreen();\r\n const isIOSMobile = useMemo(\r\n () => isIOS() && isMobileScreen,\r\n [isMobileScreen],\r\n );\r\n const { children, className, onDragStart, shouldNarrow } = props;\r\n return (\r\n <div\r\n className={clsx(styles.sidebar, className, {\r\n [styles[\"narrow-sidebar\"]]: shouldNarrow,\r\n })}\r\n style={{\r\n // #3016 disable transition on ios mobile screen\r\n transition: isMobileScreen && isIOSMobile ? \"none\" : undefined,\r\n }}\r\n >\r\n {children}\r\n <div\r\n className={styles[\"sidebar-drag\"]}\r\n onPointerDown={(e) => onDragStart(e as any)}\r\n >\r\n <DragIcon />\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport function SideBarHeader(props: {\r\n title?: string | React.ReactNode;\r\n subTitle?: string | React.ReactNode;\r\n logo?: React.ReactNode;\r\n children?: React.ReactNode;\r\n shouldNarrow?: boolean;\r\n}) {\r\n const { title, subTitle, logo, children, shouldNarrow } = props;\r\n return (\r\n <Fragment>\r\n <div\r\n className={clsx(styles[\"sidebar-header\"], {\r\n [styles[\"sidebar-header-narrow\"]]: shouldNarrow,\r\n })}\r\n data-tauri-drag-region\r\n >\r\n </div>\r\n {children}\r\n </Fragment>\r\n );\r\n}\r\n\r\nexport function SideBarBody(props: {\r\n children: React.ReactNode;\r\n onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;\r\n}) {\r\n const { onClick, children } = props;\r\n return (\r\n <div className={styles[\"sidebar-body\"]} onClick={onClick}>\r\n {children}\r\n </div>\r\n );\r\n}\r\n\r\nexport function SideBarTail(props: {\r\n primaryAction?: React.ReactNode;\r\n secondaryAction?: React.ReactNode;\r\n}) {\r\n const { primaryAction, secondaryAction } = props;\r\n\r\n return (\r\n <div className={styles[\"sidebar-tail\"]}>\r\n <div className={styles[\"sidebar-actions\"]}>{primaryAction}</div>\r\n <div className={styles[\"sidebar-actions\"]}>{secondaryAction}</div>\r\n </div>\r\n );\r\n}\r\n\r\nexport function SideBar(props: { className?: string }) {\r\n useHotKey();\r\n const { onDragStart, shouldNarrow } = useDragSideBar();\r\n const [showDiscoverySelector, setshowDiscoverySelector] = useState(false);\r\n const navigate = useNavigate();\r\n const config = useAppConfig();\r\n const chatStore = useChatStore();\r\n const [mcpEnabled, setMcpEnabled] = useState(false);\r\n\r\n useEffect(() => {\r\n // 检查 MCP 是否启用\r\n const checkMcpStatus = async () => {\r\n const enabled = await isMcpEnabled();\r\n setMcpEnabled(enabled);\r\n console.log(\"[SideBar] MCP enabled:\", enabled);\r\n };\r\n checkMcpStatus();\r\n }, []);\r\n\r\n return (\r\n <SideBarContainer\r\n onDragStart={onDragStart}\r\n shouldNarrow={shouldNarrow}\r\n {...props}\r\n >\r\n <SideBarHeader\r\n title=\"NextChat\"\r\n subTitle=\"Build your own AI assistant.\"\r\n logo={<ChatGptIcon />}\r\n shouldNarrow={shouldNarrow}\r\n >\r\n <div className={styles[\"sidebar-header-bar\"]}>\r\n <IconButton\r\n icon={<AddIcon />}\r\n text={shouldNarrow ? undefined : Locale.Home.NewChat}\r\n className={styles[\"sidebar-bar-button\"]}\r\n onClick={() => {\r\n if (config.dontShowMaskSplashScreen) {\r\n chatStore.newSession();\r\n navigate(Path.Chat);\r\n } else {\r\n navigate(Path.NewChat);\r\n }\r\n }}\r\n shadow\r\n />\r\n </div>\r\n {showDiscoverySelector && (\r\n <Selector\r\n items={[\r\n ...DISCOVERY.map((item) => {\r\n return {\r\n title: item.name,\r\n value: item.path,\r\n };\r\n }),\r\n ]}\r\n onClose={() => setshowDiscoverySelector(false)}\r\n onSelection={(s) => {\r\n navigate(s[0], { state: { fromHome: true } });\r\n }}\r\n />\r\n )}\r\n </SideBarHeader>\r\n <SideBarBody\r\n onClick={(e) => {\r\n if (e.target === e.currentTarget) {\r\n navigate(Path.Home);\r\n }\r\n }}\r\n >\r\n <ChatList narrow={shouldNarrow} />\r\n </SideBarBody>\r\n <SideBarTail\r\n primaryAction={\r\n <>\r\n <div className={clsx(styles[\"sidebar-action\"], styles.mobile)}>\r\n <IconButton\r\n icon={<DeleteIcon />}\r\n onClick={async () => {\r\n if (await showConfirm(Locale.Home.DeleteChat)) {\r\n chatStore.deleteSession(chatStore.currentSessionIndex);\r\n }\r\n }}\r\n />\r\n </div>\r\n <div className={styles[\"sidebar-action\"]}>\r\n <Link to={Path.Settings}>\r\n <IconButton\r\n aria={Locale.Settings.Title}\r\n icon={<SettingsIcon />}\r\n shadow\r\n />\r\n </Link>\r\n </div>\r\n </>\r\n }\r\n secondaryAction={\r\n <IconButton\r\n icon={<MaskIcon />}\r\n text={shouldNarrow ? undefined : Locale.Mask.Name}\r\n onClick={() => {\r\n if (config.dontShowMaskSplashScreen !== true) {\r\n navigate(Path.NewChat, { state: { fromHome: true } });\r\n } else {\r\n navigate(Path.Masks, { state: { fromHome: true } });\r\n }\r\n }}\r\n shadow\r\n />\r\n }\r\n />\r\n </SideBarContainer>\r\n );\r\n}\r\n","D:\\NextWeb\\app\\components\\tts-config.tsx",[],[],"D:\\NextWeb\\app\\components\\ui-lib.tsx",[],["769","770"],"D:\\NextWeb\\app\\components\\voice-print\\index.ts",[],[],"D:\\NextWeb\\app\\components\\voice-print\\voice-print.tsx",[],[],"D:\\NextWeb\\app\\config\\build.ts",[],[],"D:\\NextWeb\\app\\config\\client.ts",[],[],"D:\\NextWeb\\app\\config\\server.ts",[],[],"D:\\NextWeb\\app\\constant.ts",[],[],"D:\\NextWeb\\app\\global.d.ts",[],[],"D:\\NextWeb\\app\\layout.tsx",[],[],"D:\\NextWeb\\app\\lib\\audio.ts",[],[],"D:\\NextWeb\\app\\locales\\ar.ts",[],[],"D:\\NextWeb\\app\\locales\\bn.ts",[],[],"D:\\NextWeb\\app\\locales\\cn.ts",[],[],"D:\\NextWeb\\app\\locales\\cs.ts",[],[],"D:\\NextWeb\\app\\locales\\de.ts",[],[],"D:\\NextWeb\\app\\locales\\en.ts",[],[],"D:\\NextWeb\\app\\locales\\es.ts",[],[],"D:\\NextWeb\\app\\locales\\fr.ts",[],[],"D:\\NextWeb\\app\\locales\\id.ts",[],[],"D:\\NextWeb\\app\\locales\\index.ts",[],[],"D:\\NextWeb\\app\\locales\\it.ts",[],[],"D:\\NextWeb\\app\\locales\\jp.ts",[],[],"D:\\NextWeb\\app\\locales\\ko.ts",[],[],"D:\\NextWeb\\app\\locales\\no.ts",[],[],"D:\\NextWeb\\app\\locales\\pt.ts",[],[],"D:\\NextWeb\\app\\locales\\ru.ts",[],[],"D:\\NextWeb\\app\\locales\\sk.ts",[],[],"D:\\NextWeb\\app\\locales\\tr.ts",[],[],"D:\\NextWeb\\app\\locales\\tw.ts",[],[],"D:\\NextWeb\\app\\locales\\vi.ts",[],[],"D:\\NextWeb\\app\\masks\\build.ts",[],[],"D:\\NextWeb\\app\\masks\\cn.ts",[],[],"D:\\NextWeb\\app\\masks\\en.ts",[],[],"D:\\NextWeb\\app\\masks\\index.ts",[],[],"D:\\NextWeb\\app\\masks\\tw.ts",[],[],"D:\\NextWeb\\app\\masks\\typing.ts",[],[],"D:\\NextWeb\\app\\mcp\\actions.ts",[],[],"D:\\NextWeb\\app\\mcp\\client.ts",[],[],"D:\\NextWeb\\app\\mcp\\logger.ts",[],[],"D:\\NextWeb\\app\\mcp\\types.ts",[],[],"D:\\NextWeb\\app\\mcp\\utils.ts",[],[],"D:\\NextWeb\\app\\page.tsx",[],[],"D:\\NextWeb\\app\\polyfill.ts",[],[],"D:\\NextWeb\\app\\store\\access.ts",[],[],"D:\\NextWeb\\app\\store\\chat.ts",[],[],"D:\\NextWeb\\app\\store\\config.ts",[],[],"D:\\NextWeb\\app\\store\\index.ts",[],[],"D:\\NextWeb\\app\\store\\mask.ts",[],[],"D:\\NextWeb\\app\\store\\plugin.ts",[],[],"D:\\NextWeb\\app\\store\\prompt.ts",[],[],"D:\\NextWeb\\app\\store\\sd.ts",[],[],"D:\\NextWeb\\app\\store\\sync.ts",[],[],"D:\\NextWeb\\app\\store\\update.ts",[],[],"D:\\NextWeb\\app\\typing.ts",[],[],"D:\\NextWeb\\app\\utils\\audio.ts",[],[],"D:\\NextWeb\\app\\utils\\auth-settings-events.ts",[],[],"D:\\NextWeb\\app\\utils\\baidu.ts",[],[],"D:\\NextWeb\\app\\utils\\chat.ts",[],[],"D:\\NextWeb\\app\\utils\\clone.ts",[],[],"D:\\NextWeb\\app\\utils\\cloud\\index.ts",[],[],"D:\\NextWeb\\app\\utils\\cloud\\upstash.ts",[],[],"D:\\NextWeb\\app\\utils\\cloud\\webdav.ts",[],[],"D:\\NextWeb\\app\\utils\\cloudflare.ts",[],[],"D:\\NextWeb\\app\\utils\\format.ts",[],[],"D:\\NextWeb\\app\\utils\\hmac.ts",[],[],"D:\\NextWeb\\app\\utils\\hooks.ts",[],[],"D:\\NextWeb\\app\\utils\\indexedDB-storage.ts",[],[],"D:\\NextWeb\\app\\utils\\merge.ts",[],[],"D:\\NextWeb\\app\\utils\\model.ts",[],[],"D:\\NextWeb\\app\\utils\\ms_edge_tts.ts",[],[],"D:\\NextWeb\\app\\utils\\object.ts",[],[],"D:\\NextWeb\\app\\utils\\store.ts",[],[],"D:\\NextWeb\\app\\utils\\stream.ts",[],[],"D:\\NextWeb\\app\\utils\\sync.ts",[],[],"D:\\NextWeb\\app\\utils\\tencent.ts",[],[],"D:\\NextWeb\\app\\utils\\token.ts",[],[],"D:\\NextWeb\\app\\utils.ts",[],[],{"ruleId":"771","severity":1,"message":"772","line":32,"column":6,"nodeType":"773","endLine":32,"endColumn":30,"suggestions":"774","suppressions":"775"},{"ruleId":"771","severity":1,"message":"776","line":47,"column":6,"nodeType":"773","endLine":47,"endColumn":8,"suggestions":"777","suppressions":"778"},{"ruleId":"779","severity":1,"message":"780","line":23,"column":8,"nodeType":"781","messageId":"782","endLine":23,"endColumn":18,"fix":"783"},{"ruleId":"779","severity":1,"message":"784","line":24,"column":8,"nodeType":"781","messageId":"782","endLine":24,"endColumn":16,"fix":"785"},{"ruleId":"779","severity":1,"message":"786","line":29,"column":8,"nodeType":"781","messageId":"782","endLine":29,"endColumn":17,"fix":"787"},{"ruleId":"779","severity":1,"message":"788","line":38,"column":8,"nodeType":"781","messageId":"782","endLine":38,"endColumn":17,"fix":"789"},{"ruleId":"779","severity":1,"message":"790","line":39,"column":8,"nodeType":"781","messageId":"782","endLine":39,"endColumn":16,"fix":"791"},{"ruleId":"779","severity":1,"message":"792","line":40,"column":8,"nodeType":"781","messageId":"782","endLine":40,"endColumn":16,"fix":"793"},{"ruleId":"779","severity":1,"message":"794","line":48,"column":8,"nodeType":"781","messageId":"782","endLine":48,"endColumn":23,"fix":"795"},{"ruleId":"771","severity":1,"message":"796","line":597,"column":6,"nodeType":"773","endLine":597,"endColumn":48,"suggestions":"797"},{"ruleId":"771","severity":1,"message":"798","line":968,"column":6,"nodeType":"773","endLine":968,"endColumn":40,"suggestions":"799"},{"ruleId":"800","severity":1,"message":"801","line":1937,"column":31,"nodeType":"802","endLine":1941,"endColumn":33},{"ruleId":"800","severity":1,"message":"801","line":1956,"column":39,"nodeType":"802","endLine":1965,"endColumn":41},{"ruleId":"771","severity":1,"message":"803","line":359,"column":6,"nodeType":"773","endLine":359,"endColumn":41,"suggestions":"804","suppressions":"805"},{"ruleId":"771","severity":1,"message":"806","line":1016,"column":3,"nodeType":"781","endLine":1016,"endColumn":12,"suggestions":"807","suppressions":"808"},{"ruleId":"771","severity":1,"message":"809","line":1123,"column":6,"nodeType":"773","endLine":1123,"endColumn":15,"suggestions":"810","suppressions":"811"},{"ruleId":"771","severity":1,"message":"812","line":1458,"column":6,"nodeType":"773","endLine":1458,"endColumn":8,"suggestions":"813","suppressions":"814"},{"ruleId":"771","severity":1,"message":"796","line":288,"column":6,"nodeType":"773","endLine":288,"endColumn":8,"suggestions":"815","suppressions":"816"},{"ruleId":"800","severity":1,"message":"801","line":413,"column":7,"nodeType":"802","endLine":419,"endColumn":9,"suppressions":"817"},{"ruleId":"800","severity":1,"message":"801","line":592,"column":19,"nodeType":"802","endLine":597,"endColumn":21,"suppressions":"818"},{"ruleId":"800","severity":1,"message":"801","line":609,"column":23,"nodeType":"802","endLine":614,"endColumn":25,"suppressions":"819"},{"ruleId":"771","severity":1,"message":"820","line":234,"column":6,"nodeType":"773","endLine":234,"endColumn":8,"suggestions":"821","suppressions":"822"},{"ruleId":"771","severity":1,"message":"823","line":131,"column":6,"nodeType":"773","endLine":131,"endColumn":8,"suggestions":"824"},{"ruleId":"771","severity":1,"message":"825","line":131,"column":6,"nodeType":"773","endLine":131,"endColumn":8,"suggestions":"826","suppressions":"827"},{"ruleId":"771","severity":1,"message":"828","line":144,"column":6,"nodeType":"773","endLine":144,"endColumn":28,"suggestions":"829","suppressions":"830"},{"ruleId":"771","severity":1,"message":"831","line":72,"column":6,"nodeType":"773","endLine":72,"endColumn":8,"suggestions":"832","suppressions":"833"},{"ruleId":"771","severity":1,"message":"834","line":285,"column":6,"nodeType":"773","endLine":285,"endColumn":8,"suggestions":"835"},{"ruleId":"771","severity":1,"message":"836","line":104,"column":6,"nodeType":"773","endLine":104,"endColumn":25,"suggestions":"837"},{"ruleId":"800","severity":1,"message":"801","line":165,"column":25,"nodeType":"802","endLine":181,"endColumn":27},{"ruleId":"771","severity":1,"message":"838","line":68,"column":6,"nodeType":"773","endLine":68,"endColumn":8,"suggestions":"839"},{"ruleId":"779","severity":1,"message":"840","line":16,"column":8,"nodeType":"781","messageId":"782","endLine":16,"endColumn":18,"fix":"841"},{"ruleId":"779","severity":1,"message":"842","line":32,"column":3,"nodeType":"781","messageId":"782","endLine":32,"endColumn":12,"fix":"843"},{"ruleId":"779","severity":1,"message":"844","line":70,"column":3,"nodeType":"781","messageId":"782","endLine":70,"endColumn":9,"fix":"845"},{"ruleId":"779","severity":1,"message":"846","line":89,"column":10,"nodeType":"781","messageId":"782","endLine":89,"endColumn":23,"fix":"847"},{"ruleId":"779","severity":1,"message":"848","line":90,"column":10,"nodeType":"781","messageId":"782","endLine":90,"endColumn":28,"fix":"849"},{"ruleId":"771","severity":1,"message":"850","line":603,"column":5,"nodeType":"773","endLine":603,"endColumn":7,"suggestions":"851","suppressions":"852"},{"ruleId":"771","severity":1,"message":"853","line":617,"column":6,"nodeType":"773","endLine":617,"endColumn":8,"suggestions":"854","suppressions":"855"},{"ruleId":"771","severity":1,"message":"856","line":636,"column":6,"nodeType":"773","endLine":636,"endColumn":8,"suggestions":"857","suppressions":"858"},{"ruleId":"779","severity":1,"message":"859","line":7,"column":8,"nodeType":"781","messageId":"782","endLine":7,"endColumn":18,"fix":"860"},{"ruleId":"779","severity":1,"message":"861","line":24,"column":3,"nodeType":"781","messageId":"782","endLine":24,"endColumn":11,"fix":"862"},{"ruleId":"771","severity":1,"message":"796","line":136,"column":6,"nodeType":"773","endLine":136,"endColumn":8,"suggestions":"863","suppressions":"864"},{"ruleId":"800","severity":1,"message":"801","line":463,"column":9,"nodeType":"802","endLine":471,"endColumn":10,"suppressions":"865"},"react-hooks/exhaustive-deps","React Hook useEffect has a missing dependency: 'setSearchParams'. Either include it or remove the dependency array.","ArrayExpression",["866"],["867"],"React Hook useEffect has a missing dependency: 'navigate'. Either include it or remove the dependency array.",["868"],["869"],"unused-imports/no-unused-imports","'PromptIcon' is defined but never used.","Identifier","unusedVar",{"range":"870","text":"871"},"'MaskIcon' is defined but never used.",{"range":"872","text":"871"},"'BreakIcon' is defined but never used.",{"range":"873","text":"871"},"'LightIcon' is defined but never used.",{"range":"874","text":"871"},"'DarkIcon' is defined but never used.",{"range":"875","text":"871"},"'AutoIcon' is defined but never used.",{"range":"876","text":"871"},"'ShortcutkeyIcon' is defined but never used.",{"range":"877","text":"871"},"React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array. However, 'props' will change when *any* prop changes, so the preferred fix is to destructure the 'props' object outside of the useEffect call and refer to those specific props inside useEffect.",["878"],"React Hook useMemo has an unnecessary dependency: 'scrollRef.current.scrollHeight'. Either exclude it or remove the dependency array.",["879"],"@next/next/no-img-element","Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","JSXOpeningElement","React Hook useEffect has missing dependencies: 'noPrompts' and 'props'. Either include them or remove the dependency array. However, 'props' will change when *any* prop changes, so the preferred fix is to destructure the 'props' object outside of the useEffect call and refer to those specific props inside useEffect.",["880"],["881"],"React Hook useEffect has a missing dependency: 'measure'. Either include it or remove the dependency array.",["882"],["883"],"React Hook useEffect has missing dependencies: 'chatStore' and 'config.modelConfig'. Either include them or remove the dependency array.",["884"],["885"],"React Hook useEffect has missing dependencies: 'session.id' and 'userInput.length'. Either include them or remove the dependency array.",["886"],["887"],["888"],["889"],["890"],["891"],["892"],"React Hook useEffect has missing dependencies: 'api.llm' and 'config'. Either include them or remove the dependency array.",["893"],["894"],"React Hook useEffect has a missing dependency: 'renderArtifacts'. Either include it or remove the dependency array.",["895"],"React Hook useEffect has missing dependencies: 'props.defaultSelectAll' and 'selectAll'. Either include them or remove the dependency array.",["896"],["897"],"React Hook useEffect has missing dependencies: 'messages' and 'props'. Either include them or remove the dependency array. However, 'props' will change when *any* prop changes, so the preferred fix is to destructure the 'props' object outside of the useEffect call and refer to those specific props inside useEffect.",["898"],["899"],"React Hook useEffect has a missing dependency: 'masks'. Either include it or remove the dependency array.",["900"],["901"],"React Hook useEffect has missing dependencies: 'handleConnect', 'isRecording', and 'toggleRecording'. Either include them or remove the dependency array.",["902"],"React Hook useEffect has a missing dependency: 'sdStore.draw'. Either include it or remove the dependency array.",["903"],"React Hook useCallback has a missing dependency: 'sessions'. Either include it or remove the dependency array.",["904"],"'ConfigIcon' is defined but never used.",{"range":"905","text":"871"},"'showToast' is defined but never used.",{"range":"906","text":"871"},"'SlotID' is defined but never used.",{"range":"907","text":"871"},"'TTSConfigList' is defined but never used.",{"range":"908","text":"871"},"'RealtimeConfigList' is defined but never used.",{"range":"909","text":"910"},"React Hook useMemo has a missing dependency: 'accessStore'. Either include it or remove the dependency array.",["911"],["912"],"React Hook useEffect has missing dependencies: 'checkUpdate', 'checkUsage', and 'showUsage'. Either include them or remove the dependency array.",["913"],["914"],"React Hook useEffect has missing dependencies: 'accessStore', 'clientConfig?.isApp', and 'navigate'. Either include them or remove the dependency array.",["915"],["916"],"'GithubIcon' is defined but never used.",{"range":"917","text":"871"},"'REPO_URL' is defined but never used.",{"range":"918","text":"871"},["919"],["920"],["921"],{"desc":"922","fix":"923"},{"kind":"924","justification":"871"},{"desc":"925","fix":"926"},{"kind":"924","justification":"871"},[716,763],"",[763,806],[981,1026],[1357,1402],[1402,1445],[1445,1488],[1805,1862],{"desc":"927","fix":"928"},{"desc":"929","fix":"930"},{"desc":"931","fix":"932"},{"kind":"924","justification":"871"},{"desc":"933","fix":"934"},{"kind":"924","justification":"871"},{"desc":"935","fix":"936"},{"kind":"924","justification":"871"},{"desc":"937","fix":"938"},{"kind":"924","justification":"871"},{"desc":"939","fix":"940"},{"kind":"924","justification":"871"},{"kind":"924","justification":"871"},{"kind":"924","justification":"871"},{"kind":"924","justification":"871"},{"desc":"941","fix":"942"},{"kind":"924","justification":"871"},{"desc":"943","fix":"944"},{"desc":"945","fix":"946"},{"kind":"924","justification":"871"},{"desc":"947","fix":"948"},{"kind":"924","justification":"871"},{"desc":"949","fix":"950"},{"kind":"924","justification":"871"},{"desc":"951","fix":"952"},{"desc":"953","fix":"954"},{"desc":"955","fix":"956"},[602,649],[1063,1077],[1754,1765],[2378,2425],[2425,2498],"\n",{"desc":"957","fix":"958"},{"kind":"924","justification":"871"},{"desc":"959","fix":"960"},{"kind":"924","justification":"871"},{"desc":"961","fix":"962"},{"kind":"924","justification":"871"},[217,264],[694,707],{"desc":"939","fix":"963"},{"kind":"924","justification":"871"},{"kind":"924","justification":"871"},"Update the dependencies array to be: [searchParams, commands, setSearchParams]",{"range":"964","text":"965"},"directive","Update the dependencies array to be: [navigate]",{"range":"966","text":"967"},"Update the dependencies array to be: [chatStore, currentModel, models, props, session]",{"range":"968","text":"969"},"Update the dependencies array to be: []",{"range":"970","text":"971"},"Update the dependencies array to be: [noPrompts, props, props.prompts.length, selectIndex]",{"range":"972","text":"973"},"Update the dependencies array to be: [measure]",{"range":"974","text":"975"},"Update the dependencies array to be: [chatStore, config.modelConfig, session]",{"range":"976","text":"977"},"Update the dependencies array to be: [session.id, userInput.length]",{"range":"978","text":"979"},"Update the dependencies array to be: [props]",{"range":"980","text":"981"},"Update the dependencies array to be: [api.llm, config]",{"range":"982","text":"983"},"Update the dependencies array to be: [renderArtifacts]",{"range":"984","text":"985"},"Update the dependencies array to be: [props.defaultSelectAll, selectAll]",{"range":"986","text":"987"},"Update the dependencies array to be: [startIndex, endIndex, props, messages]",{"range":"988","text":"989"},"Update the dependencies array to be: [masks]",{"range":"990","text":"991"},"Update the dependencies array to be: [handleConnect, isRecording, toggleRecording]",{"range":"992","text":"993"},"Update the dependencies array to be: [sdStore.currentId, sdStore.draw]",{"range":"994","text":"995"},"Update the dependencies array to be: [sessions]",{"range":"996","text":"997"},"Update the dependencies array to be: [accessStore]",{"range":"998","text":"999"},"Update the dependencies array to be: [checkUpdate, checkUsage, showUsage]",{"range":"1000","text":"1001"},"Update the dependencies array to be: [accessStore, clientConfig?.isApp, navigate]",{"range":"1002","text":"1003"},{"range":"1004","text":"981"},[880,904],"[searchParams, commands, setSearchParams]",[1596,1598],"[navigate]",[18316,18358],"[chatStore, currentModel, models, props, session]",[30141,30175],"[]",[10845,10880],"[noPrompts, props, props.prompts.length, selectIndex]",[31660,31671],"[measure]",[35009,35018],"[chatStore, config.modelConfig, session]",[45353,45355],"[session.id, userInput.length]",[8182,8184],"[props]",[6651,6653],"[api.llm, config]",[4020,4022],"[renderArtifacts]",[3812,3814],"[props.defaultSelectAll, selectAll]",[4218,4240],"[startIndex, endIndex, props, messages]",[2391,2393],"[masks]",[9417,9419],"[handleConnect, isRecording, toggleRecording]",[3305,3324],"[sdStore.currentId, sdStore.draw]",[2169,2171],"[sessions]",[18718,18720],"[accessStore]",[19179,19181],"[checkUpdate, checkUsage, showUsage]",[19738,19740],"[accessStore, clientConfig?.isApp, navigate]",[3409,3411]]