index.html 19 KB

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