index.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <link id="app-favicon" rel="icon" type="image/svg+xml" href="/vite.svg" />
  6. <meta
  7. name="viewport"
  8. content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
  9. />
  10. <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
  11. <meta http-equiv="Pragma" content="no-cache" />
  12. <meta http-equiv="Expires" content="0" />
  13. <title></title>
  14. <script>
  15. let globalThis = window;
  16. globalThis.modelServer = "%VITE_API%" || "";
  17. globalThis.isKnowledgeApi = "%VITE_USE_KNOWLEDGE_API%" === "true";
  18. // 新版聊天协议后端基础地址(POST /api/chat,SSE)
  19. globalThis.VITE_CHAT_API = "%VITE_CHAT_API%" || "";
  20. // DMS 数据管理服务(会话/问答记录/反馈)。token 由 vite 代理注入,前端不持有
  21. globalThis.VITE_DMS_API = "%VITE_DMS_API%" || "";
  22. globalThis.isQingpuEnv = "MODE" === "qingpu";
  23. globalThis.__ZHAOSHANG_LLM_MODELS = ["zhaoshang", "zhaoshang_staging", "zhaoshang_safe"];
  24. globalThis.__isZhaoshangLLM = function (model) {
  25. return globalThis.__ZHAOSHANG_LLM_MODELS.indexOf(model || globalThis.llmModel || "") > -1;
  26. };
  27. var cardURL = globalThis.modelServer;
  28. function getQueryObj(querykey) {
  29. if (!querykey || typeof querykey !== "string") {
  30. console.warn("Invalid querykey provided to getQueryObj");
  31. return "";
  32. }
  33. var hash = location.hash;
  34. if (!hash || hash.indexOf("?") === -1) {
  35. console.warn("No query parameters found in URL hash");
  36. return "";
  37. }
  38. var queryString = hash.split("?")[1];
  39. if (!queryString) {
  40. console.warn('No query string found after "?" in URL hash');
  41. return "";
  42. }
  43. var arr = queryString.split("#")[0].split("&");
  44. var resObj = {};
  45. arr.forEach(function (item) {
  46. if (item) {
  47. var splitItem = item.split("=");
  48. var key = splitItem[0];
  49. var value = splitItem[1] || "";
  50. if (key) {
  51. resObj[key] = decodeURIComponent(value);
  52. }
  53. }
  54. });
  55. return resObj.hasOwnProperty(querykey) ? resObj[querykey] : null;
  56. }
  57. function changeLoadingText(text) {
  58. document.querySelector("#loading-cover").classList.add("blur-effect");
  59. setTimeout(function () {
  60. var loadingCover = document.querySelector("#result-text");
  61. loadingCover.innerText = text;
  62. var circle = document.querySelector("#circle");
  63. if (circle) {
  64. circle.style.display = "none";
  65. }
  66. }, 500);
  67. }
  68. function showReloadView(text) {
  69. document.getElementById("error-info").style.display = "flex";
  70. document.querySelector("#error-tips").innerText = text;
  71. setTimeout(function () {
  72. document.getElementById("error-info").style.opacity = 1;
  73. document.getElementById("error-reload").addEventListener("click", function () {
  74. location.reload();
  75. });
  76. }, 100);
  77. }
  78. function addVersionInfo() {
  79. var version = "%VITE_VERSION%";
  80. window.humanCardVersion = version;
  81. var mode = "%MODE%";
  82. window.humanCardEnv = mode;
  83. console.log("%c@@@version===>", "color:green;font-size:15px", version, mode);
  84. document.title += " Ver. " + version;
  85. if (["development", "test"].includes(mode)) {
  86. document.title += " " + mode.toUpperCase();
  87. }
  88. }
  89. function randomString() {
  90. var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  91. var length = 32;
  92. var result = "";
  93. for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
  94. return result;
  95. }
  96. function initToken() {
  97. var openId = localStorage.getItem("openId") || randomString();
  98. localStorage.setItem("openId", openId);
  99. var requestOptions = {
  100. method: "POST",
  101. headers: {
  102. "Content-Type": "application/json",
  103. },
  104. body: JSON.stringify({
  105. openid: openId,
  106. mobile: "17",
  107. nickname: globalThis.orgTitle,
  108. app_key: "%VITE_APP_KEY%",
  109. }),
  110. };
  111. var url = globalThis.modelServer + "/open/openid_to_token";
  112. fetch(url, requestOptions)
  113. .then((response) => response.json())
  114. .then((result) => {
  115. console.log(result.ret.token);
  116. globalThis.token = result.ret.token;
  117. // globalThis.token =
  118. // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyaWQiOjMxNjA1MSwiZXhwIjoxNzM2NDE1MDAyLCJpYXQiOjE3MzU1NTEwMDIsImlzcyI6ImFpLWh1bWFuLW1pbmkiLCJzY29wZSI6IiJ9.jYvLswI5NQaeL8W6Q0TQm7sdz4EtqyLRNVjuuKFtdO0";
  119. // globalThis.mini_id = 10156;
  120. initCardInfo();
  121. })
  122. .catch((error) => console.log("error", error));
  123. }
  124. function initCardInfo() {
  125. var token = globalThis.token;
  126. var mini = globalThis.mini_id;
  127. var card_id = globalThis.card_id;
  128. // if (!token || (!card_id && !mini)) {
  129. // return;
  130. // } else {
  131. // sessionStorage.setItem("token", token);
  132. // }
  133. var url = cardURL + "/mini/info";
  134. let params = [];
  135. if (mini) {
  136. params.push("mini=" + mini);
  137. }
  138. if (card_id) {
  139. params.push("card=" + card_id);
  140. }
  141. url += "?" + params.join("&");
  142. fetch(url, {
  143. method: "get",
  144. headers: {
  145. "Content-Type": "application/x-www-form-urlencoded",
  146. Authorization: token,
  147. },
  148. })
  149. .then(function (response) {
  150. return response.json();
  151. })
  152. .then(function (result) {
  153. if (result.err_code === -1) {
  154. globalThis.showReloadView(result.err_msg);
  155. return;
  156. }
  157. if (result.err_code !== 0) {
  158. globalThis.changeLoadingText(result.err_msg);
  159. return;
  160. }
  161. if (result.ret.hasOwnProperty("card_id") && result.ret.card_id === 0) {
  162. globalThis.changeLoadingText("助手不存在,请在管理后台中创建");
  163. return;
  164. }
  165. globalThis.cardInfo = result.ret;
  166. globalThis.cardInfo.tts_args = {
  167. speed: 30,
  168. volume: 100,
  169. voice_name: "S_x8pOUDlm1",
  170. };
  171. globalThis.hasSequence = true;
  172. // globalThis.token = data.ret;
  173. document.dispatchEvent(new CustomEvent("dataLoaded"));
  174. document.dispatchEvent(new CustomEvent("sequenceLoaded"));
  175. addVersionInfo();
  176. })
  177. .catch(function (err) {
  178. console.warn(err);
  179. addVersionInfo();
  180. globalThis.changeLoadingText(err);
  181. });
  182. }
  183. function initMessageChannel() {
  184. window.addEventListener("message", () => {
  185. if (event.data && event.data.type === "initMessage") {
  186. // 这里收到父页面发来的大文本
  187. console.log("收到父页面消息:", event.data.text);
  188. localStorage.setItem("initMessage", event.data.text);
  189. // ...你的业务处理...
  190. }
  191. });
  192. window.parent.postMessage(
  193. { type: "ready" },
  194. "%VITE_PARENT_ORIGIN%" // 指定父页面的 origin,务必不要写成 '*',防止安全隐患
  195. );
  196. }
  197. function updateFavicon() {
  198. var favicon =
  199. document.querySelector("#app-favicon") || document.querySelector('link[rel="icon"]');
  200. if (!favicon) {
  201. favicon = document.createElement("link");
  202. favicon.id = "app-favicon";
  203. favicon.rel = "icon";
  204. document.head.appendChild(favicon);
  205. }
  206. var isZhaoshang = globalThis.__isZhaoshangLLM(globalThis.llmModel);
  207. favicon.type = isZhaoshang ? "image/x-icon" : "image/svg+xml";
  208. favicon.href = isZhaoshang ? "/qs_favicon.ico" : "/vite.svg";
  209. }
  210. (function initSystem() {
  211. let isLanMode = globalThis.isQingpuEnv;
  212. globalThis.isXF = false;
  213. var requestedPolicyMode = getQueryObj("mode");
  214. var isZhaoshangMode =
  215. requestedPolicyMode === "zhaoshang" ||
  216. requestedPolicyMode === "zhaoshang_staging" ||
  217. requestedPolicyMode === "zhaoshang_safe" ||
  218. /^zhaoshang_staging_\d+$/.test(requestedPolicyMode || "");
  219. globalThis.policyMode = isZhaoshangMode ? requestedPolicyMode : "zhaoshang";
  220. {
  221. globalThis.card_id = 317;
  222. globalThis.orgTitle = "营商助手";
  223. globalThis.llmModel = globalThis.policyMode;
  224. globalThis.greeting =
  225. "您好!我是青浦区营商助手”智”小青。我新上岗不久,还在努力学习提升中,请多理解包涵。请问有什么可以帮助您?";
  226. const credit_code = getQueryObj("credit_code");
  227. if (credit_code) {
  228. globalThis.transmission = { credit_code };
  229. }
  230. // 预下载完整政策列表到内存中
  231. (function () {
  232. var downloadURL = "%VITE_DOWNLOAD_URL%" + "merchant-agent/policies_public.v1.json";
  233. var localFallbackURL = "/merchant-agent/policies_public.v1.json";
  234. function applyPolicies(data, source) {
  235. // 支持 { data: [...] } 和纯数组两种格式
  236. var list = Array.isArray(data) ? data : data.data;
  237. globalThis.policiesPublic = list || [];
  238. console.log(
  239. "%c[zhaoshang] 政策列表加载完成(" +
  240. source +
  241. "):" +
  242. globalThis.policiesPublic.length +
  243. " 条",
  244. "color:green"
  245. );
  246. }
  247. function loadPolicies(source) {
  248. return fetch(source.url)
  249. .then(function (response) {
  250. if (!response.ok) throw new Error("HTTP " + response.status);
  251. return response.json();
  252. })
  253. .then(function (data) {
  254. applyPolicies(data, source.label);
  255. });
  256. }
  257. // 开发环境优先读本地副本:远端那份 327 条全都没有 data.市级政策id,
  258. // 政策详情面板的「政策名称」会因此拼不出地址、点不开;本地这份有 261/262。
  259. // 由 VITE_POLICY_LOCAL_FIRST 控制(仅 .env.development 为 true)
  260. var localFirst = "%VITE_POLICY_LOCAL_FIRST%" === "true";
  261. var sources = localFirst
  262. ? [
  263. { url: localFallbackURL, label: "local" },
  264. { url: downloadURL, label: "remote" },
  265. ]
  266. : [
  267. { url: downloadURL, label: "remote" },
  268. { url: localFallbackURL, label: "local" },
  269. ];
  270. loadPolicies(sources[0]).catch(function (err) {
  271. console.warn(
  272. "[zhaoshang] 政策列表(" + sources[0].label + ")加载失败,尝试另一个来源:",
  273. err
  274. );
  275. loadPolicies(sources[1]).catch(function (fallbackErr) {
  276. console.warn("[zhaoshang] 政策列表两个来源都加载失败:", fallbackErr);
  277. globalThis.policiesPublic = [];
  278. });
  279. });
  280. })();
  281. }
  282. document.title = globalThis.orgTitle;
  283. updateFavicon();
  284. initMessageChannel();
  285. if (globalThis.isKnowledgeApi) {
  286. document.title = globalThis.orgTitle;
  287. globalThis.cardInfo = {
  288. id: 100161,
  289. human_gender: 1,
  290. card_id: 297,
  291. card_name: "政策解读",
  292. name: "政策解读",
  293. background: "",
  294. url: "https://timg.metamaker.cn/mms_threejs_res/1c792f578384242a92f1fb2e1ad8f70d",
  295. human_avatar:
  296. "//img.metamaker.cn/resources/52116488a01573be4d6754d7de9090464a54ccc3.png",
  297. title: "1",
  298. duty: "1",
  299. org: "1",
  300. mobile: "1",
  301. wx_id: "1",
  302. logo: "",
  303. tts_args: {
  304. speed: 30,
  305. volume: 100,
  306. voice_name: "S_x8pOUDlm1",
  307. },
  308. share_conf: {
  309. qrcode: "https://img.metamaker.cn/human_card/b380126e1c138a4c1373f1b6bc8e1a4b.jpg",
  310. share_img: "https://img.metamaker.cn/human_card/3ce6f8c86bd3525d113dde450aeadbd7.jpg",
  311. share_title: "政策解读",
  312. card_img: "https://img.metamaker.cn/human_card/91c4f4e2ccf393168f01761e54802351.jpg",
  313. },
  314. func_sets: {
  315. opt_area: [
  316. {
  317. name: "拨打电话",
  318. type: 1,
  319. alias: "",
  320. check: false,
  321. },
  322. {
  323. name: "复制微信",
  324. type: 2,
  325. alias: "",
  326. check: false,
  327. },
  328. {
  329. name: "查看文档",
  330. type: 3,
  331. alias: "",
  332. check: false,
  333. },
  334. ],
  335. question_guide: {
  336. guess_ask: {
  337. check: false,
  338. value: "",
  339. },
  340. common_problem: {
  341. check: false,
  342. value: [],
  343. },
  344. },
  345. hide_name_brand: 0,
  346. human_interaction: {
  347. welcome: {
  348. check: true,
  349. value: globalThis.greeting,
  350. },
  351. },
  352. },
  353. dialog_sets: {
  354. strict: false,
  355. only_kl: false,
  356. unable_answer: {
  357. text: "很抱歉,我不太理解您的意思,请问您想了解的是什么问题呢?",
  358. check: false,
  359. amount: 1,
  360. },
  361. belief_threshold: 0.88,
  362. },
  363. };
  364. globalThis.hasSequence = true;
  365. document.dispatchEvent(new CustomEvent("dataLoaded"));
  366. document.dispatchEvent(new CustomEvent("sequenceLoaded"));
  367. addVersionInfo();
  368. } else {
  369. initToken();
  370. }
  371. })();
  372. (function () {
  373. var src = "//cdn.jsdelivr.net/npm/eruda";
  374. if (!/eruda=true/.test(window.location) && localStorage.getItem("active-eruda") != "true")
  375. return;
  376. document.write("<scr" + 'ipt src="' + src + '"></scr' + "ipt>");
  377. document.write("<scr" + "ipt>eruda.init();</scr" + "ipt>");
  378. })();
  379. //
  380. document.addEventListener("WeixinJSBridgeReady", () => {
  381. console.log("WeixinJSBridgeReady");
  382. // let video = document.getElementById("human-stream");
  383. // video && video.play();
  384. });
  385. </script>
  386. </head>
  387. <body>
  388. <div id="app"></div>
  389. <div id="loading-cover">
  390. <div id="cover">
  391. <!-- <div id="circle" data-text="&nbsp;"></div> -->
  392. <div id="result-text">正在初始化,请稍候...</div>
  393. <!-- 加载中,请稍候... -->
  394. </div>
  395. </div>
  396. <div id="error-info" style="display: none; opacity: 0">
  397. <div id="error-tips"></div>
  398. <div id="error-reload">重试</div>
  399. </div>
  400. <script>
  401. (function initLoadingAnimation() {
  402. var circle = document.querySelector("#circle");
  403. if (!circle) {
  404. return;
  405. }
  406. var text = circle.dataset.text;
  407. var characters = text.split("");
  408. var deltaAngle = 360 / characters.length;
  409. var distanceFromCenter = 110;
  410. characters.forEach(function (character, index) {
  411. var span = document.createElement("span");
  412. span.innerText = character;
  413. var rotateY = "rotateY(" + index * deltaAngle + "deg)";
  414. var translateZ = "translateZ(" + distanceFromCenter + "px)";
  415. span.style.transform = rotateY + " " + translateZ;
  416. circle.appendChild(span);
  417. });
  418. })();
  419. </script>
  420. <script>
  421. var _hmt = _hmt || [];
  422. (function () {
  423. var hm = document.createElement("script");
  424. hm.src = "https://hm.baidu.com/hm.js?d6307cb011c91628ce6be043f40d510a";
  425. var s = document.getElementsByTagName("script")[0];
  426. s.parentNode.insertBefore(hm, s);
  427. })();
  428. </script>
  429. <script type="module" src="/src/main.ts"></script>
  430. <style>
  431. body {
  432. margin: 0px;
  433. background: #fff;
  434. }
  435. #loading-cover {
  436. width: 100%;
  437. height: 100%;
  438. position: fixed;
  439. top: 0;
  440. left: 0;
  441. z-index: 100;
  442. font-size: 14px;
  443. backdrop-filter: blur(40px);
  444. -webkit-backdrop-filter: blur(40px);
  445. background: rgba(0, 0, 0, 0.1);
  446. display: flex;
  447. justify-content: center;
  448. align-items: center;
  449. color: #232323;
  450. font-family:
  451. -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
  452. "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
  453. "Noto Color Emoji";
  454. }
  455. #cover {
  456. display: flex;
  457. flex-direction: column;
  458. }
  459. #circle {
  460. position: relative;
  461. transform-style: preserve-3d;
  462. animation: rotate-animation 8s linear infinite;
  463. }
  464. #circle span {
  465. position: absolute;
  466. top: 0;
  467. left: 0;
  468. }
  469. .blur-effect {
  470. animation: add-blur-effect 1s ease-in-out forwards !important;
  471. }
  472. #error-info {
  473. display: flex;
  474. flex-direction: column;
  475. justify-content: center;
  476. align-items: center;
  477. width: 100vw;
  478. height: 100vh;
  479. position: fixed;
  480. top: 0;
  481. left: 0;
  482. z-index: 101;
  483. transition: all 0.5s ease-in-out;
  484. }
  485. #error-tips {
  486. display: inline-flex;
  487. padding: 18px 32px;
  488. justify-content: center;
  489. align-items: center;
  490. border-radius: 20px;
  491. background: rgba(11, 12, 14, 0.8);
  492. font-family: Inter;
  493. font-size: 16px;
  494. font-style: normal;
  495. font-weight: 400;
  496. line-height: normal;
  497. color: #ffffff;
  498. transform: translateY(-100%);
  499. }
  500. #error-reload {
  501. display: flex;
  502. width: 162.5px;
  503. padding: 10px 16px;
  504. justify-content: center;
  505. align-items: center;
  506. border-radius: 14px;
  507. background: linear-gradient(95deg, rgba(74, 114, 255, 0.96) -6.43%, #925eff 98.24%);
  508. color: #fff;
  509. /* title/h5_medium */
  510. font-family: PingFang HK;
  511. font-size: 16px;
  512. font-style: normal;
  513. font-weight: 500;
  514. line-height: 24px;
  515. transform: translateY(500%);
  516. }
  517. @keyframes add-blur-effect {
  518. 0% {
  519. filter: blur(0px);
  520. -webkit-filter: blur(0px);
  521. }
  522. 50% {
  523. filter: blur(20px);
  524. -webkit-filter: blur(20px);
  525. }
  526. 100% {
  527. filter: blur(0px);
  528. -webkit-filter: blur(0px);
  529. }
  530. }
  531. @keyframes rotate-animation {
  532. 0% {
  533. transform: rotateY(360deg) rotateX(-12deg) rotateZ(12deg);
  534. }
  535. 50% {
  536. transform: rotateY(180deg) rotateX(12deg) rotateZ(-12deg);
  537. }
  538. 100% {
  539. transform: rotateY(0) rotateX(-12deg) rotateZ(12deg);
  540. }
  541. }
  542. </style>
  543. </body>
  544. </html>