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

create_online.tmpl 26 kB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. {{template "base/head" .}}
  2. <link rel="stylesheet" href="/self/ztree/css/zTreeStyle/zTreeStyle.css" type="text/css">
  3. <style>
  4. #newmodel .header {
  5. height: 45px;
  6. border: 1px solid #d4d4d5;
  7. border-radius: 5px 5px 0 0;
  8. font-size: 14px;
  9. background: #f0f0f0;
  10. display: flex;
  11. align-items: center;
  12. }
  13. #newmodel .content {
  14. margin-top: -1px;
  15. border: 1px solid #d4d4d5;
  16. border-top: none;
  17. }
  18. .inline.fields .right.aligned label{
  19. width: 100% !important;
  20. text-align: right;
  21. }
  22. .inline .ui.dropdown .text {
  23. color: rgba(0, 0, 0, .87) !important;
  24. max-width: 360px;
  25. }
  26. .newtext{
  27. margin-left: 12px !important
  28. }
  29. .menuContent{
  30. position: absolute;
  31. background: #ffffff;
  32. left: 0;
  33. right: 26px;
  34. top: 36px;
  35. z-index:999;
  36. border: 1px solid #96c8da;
  37. border-top: 0;
  38. border-bottom-right-radius: 4px;
  39. border-bottom-left-radius: 4px;
  40. box-shadow: 0 2px 3px 0 rgb(34 36 38 / 15%);
  41. }
  42. </style>
  43. <div id="mask">
  44. <div id="loadingPage">
  45. <div class="rect1"></div>
  46. <div class="rect2"></div>
  47. <div class="rect3"></div>
  48. <div class="rect4"></div>
  49. <div class="rect5"></div>
  50. </div>
  51. </div>
  52. {{$repository := .Repository.ID}}
  53. <div class="repository release dataset-list view">
  54. {{template "repo/header" .}}
  55. <div class="ui container">
  56. <div id="newmodel">
  57. <div class="ui second">
  58. <div class="header" style="padding: 1rem;background-color: rgba(240, 240, 240, 100);">
  59. <h4 id="model_header">{{.i18n.Tr "repo.model.manage.import_online_model"}}</h4>
  60. </div>
  61. <div class="content content-padding">
  62. <form id="formId" class="ui form dirty">
  63. <input class="ays-ignore" type="hidden" name="initModel" value="{{$.MODEL_COUNT}}">
  64. <div class="ui error message"></div>
  65. <input class="ays-ignore" type="hidden" name="_csrf" value="">
  66. <div class="inline fields">
  67. <div class="required two wide field right aligned">
  68. <label for="jobId">{{.i18n.Tr "repo.model.manage.select.trainjob"}}</label>
  69. </div>
  70. <div class="required thirteen wide inline field">
  71. <div class="ui dropdown selection search loading" id="choice_model">
  72. <input class="ays-ignore" type="hidden" id="jobId" name="jobId" required>
  73. <div class="default text">{{.i18n.Tr "repo.model.manage.select.trainjob"}}</div>
  74. <i class="dropdown icon"></i>
  75. <div class="menu" id="job-name">
  76. </div>
  77. </div>
  78. <label for="versionName">{{.i18n.Tr "repo.model.manage.version"}}</label>
  79. <span>&nbsp;</span>
  80. <div class="ui dropdown selection search" id="choice_version">
  81. <input class="ays-ignore" type="hidden" id="versionName" name="versionName" required>
  82. <div class="default text">{{.i18n.Tr "repo.model.manage.select.version"}}</div>
  83. <i class="dropdown icon"></i>
  84. <div class="menu" id="job-version">
  85. </div>
  86. </div>
  87. </div>
  88. </div>
  89. <div class="required inline fields" id="modelname">
  90. <div class="two wide field right aligned">
  91. <label for="name">{{.i18n.Tr "repo.model.manage.model_name"}}</label>
  92. </div>
  93. <div class="eight wide field">
  94. <input class="ays-ignore" id="name" name="name" required maxlength="25" onkeyup="this.value=this.value.replace(/[, ]/g,'')">
  95. </div>
  96. </div>
  97. <div class="required inline fields" id="verionName" style="display:none;">
  98. <div class="two wide field right aligned">
  99. <label for="version">{{.i18n.Tr "repo.model.manage.version"}}</label>
  100. </div>
  101. <div class="eight wide field">
  102. <input class="ays-ignore" id="version" name="version" value="" readonly required maxlength="255">
  103. </div>
  104. </div>
  105. <div class="unite min_title inline fields required">
  106. <div class="two wide field right aligned">
  107. <label for="Engine">{{.i18n.Tr "repo.model.manage.engine"}}</label>
  108. </div>
  109. <div class="ui ten wide field dropdown selection" id="choice_Engine">
  110. <input class="ays-ignore" type="hidden" id="engine" name="engine" required>
  111. <div class="default text newtext">{{.i18n.Tr "repo.model.manage.select.engine"}}</div>
  112. <i class="dropdown icon"></i>
  113. <div class="menu" id="job-Engine">
  114. </div>
  115. </div>
  116. </div>
  117. <div class="unite min_title inline fields required">
  118. <div class="two wide field right aligned">
  119. <label for="modelSelectedFile">{{.i18n.Tr "repo.model.manage.modelfile"}}</label>
  120. </div>
  121. <div class="thirteen wide field" style="position:relative">
  122. <input class="ays-ignore" id="modelSelectedFile" type="text" readonly required onclick="showMenu();" name="modelSelectedFile" >
  123. <div id="menuContent" class="menuContent" style="display:none;">
  124. <ul id="treeDemo" class="ztree"></ul>
  125. </div>
  126. </div>
  127. </div>
  128. <div class="inline fields">
  129. <div class="two wide field right aligned">
  130. <label for="Label">{{.i18n.Tr "repo.model.manage.modellabel"}} &nbsp</label>
  131. </div>
  132. <div class="thirteen wide field">
  133. <input class="ays-ignore" id="label" name="label" maxlength="255" placeholder='{{.i18n.Tr "repo.modelarts.train_job.label_place"}}'>
  134. </div>
  135. </div>
  136. <div class="inline fields">
  137. <div class="two wide field right aligned">
  138. <label for="description">{{.i18n.Tr "repo.model.manage.modeldesc"}} &nbsp</label>
  139. </div>
  140. <div class="thirteen wide field">
  141. <textarea id="description" class="ays-ignore" name="description" rows="3"
  142. maxlength="255" placeholder='{{.i18n.Tr "repo.modelarts.train_job.new_place"}}'
  143. onchange="this.value=this.value.substring(0, 255)"
  144. onkeydown="this.value=this.value.substring(0, 255)"
  145. onkeyup="this.value=this.value.substring(0, 256)"></textarea>
  146. </div>
  147. </div>
  148. </form>
  149. <div class="inline field" style="margin-left:140px;margin-top:28px;">
  150. <button id="submitId" type="button" class="ui create_train_job green button" onclick="submitSaveModel()"
  151. style="">
  152. {{.i18n.Tr "repo.model.manage.sava_model"}}
  153. </button>
  154. <button style="margin-left:0px;" class="ui button cancel" onclick="backToModelListPage()">{{.i18n.Tr "repo.cloudbrain.cancel"}}</button>
  155. </div>
  156. </div>
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. {{template "base/footer" .}}
  162. <script type="text/javascript" src="/self/ztree/js/jquery.ztree.core.js"></script>
  163. <script type="text/javascript" src="/self/ztree/js/jquery.ztree.excheck.js"></script>
  164. <script>
  165. ;(function() {
  166. var setting = {
  167. check: {
  168. enable: true,
  169. chkboxType: {"Y":"ps", "N":"ps"}
  170. },
  171. view: {
  172. dblClickExpand: false
  173. },
  174. callback: {
  175. beforeClick: beforeClick,
  176. onCheck: onCheck
  177. }
  178. };
  179. function beforeClick(treeId, treeNode) {
  180. var zTree = $.fn.zTree.getZTreeObj("treeDemo");
  181. zTree.checkNode(treeNode, !treeNode.checked, null, true);
  182. return false;
  183. }
  184. function onCheck(e, treeId, treeNode) {
  185. var zTree = $.fn.zTree.getZTreeObj("treeDemo"),
  186. nodes = zTree.getCheckedNodes(true),
  187. v = "";
  188. for (var i=0, l=nodes.length; i<l; i++) {
  189. if(nodes[i].isParent){
  190. continue;
  191. }
  192. var pathNodes = nodes[i].getPath();
  193. var path ="";
  194. for(var j=0;j<pathNodes.length;j++){
  195. if(j ==0){
  196. path += pathNodes[j].name;
  197. }else{
  198. path += "/" + pathNodes[j].name;
  199. }
  200. }
  201. v += path + ";";
  202. }
  203. if (v.length > 0 ) v = v.substring(0, v.length-1);
  204. var cityObj = $("#modelSelectedFile");
  205. cityObj.attr("value", v);
  206. }
  207. function showMenu() {
  208. var cityObj = $("#modelSelectedFile");
  209. var cityOffset = $("#modelSelectedFile").offset();
  210. // $("#menuContent").css({left:cityOffset.left + "px", top:cityOffset.top + cityObj.outerHeight() + "px"}).slideDown("fast");
  211. $("#menuContent").slideDown("fast");
  212. $("body").bind("mousedown", onBodyDown);
  213. }
  214. window.showMenu = showMenu;
  215. function hideMenu() {
  216. $("#menuContent").fadeOut("fast");
  217. $("body").unbind("mousedown", onBodyDown);
  218. }
  219. function onBodyDown(event) {
  220. if (!(event.target.id == "menuBtn" || event.target.id == "modelSelectedFile" || event.target.id == "menuContent" || $(event.target).parents("#menuContent").length>0)) {
  221. hideMenu();
  222. }
  223. }
  224. $(document).ready(function(){
  225. //$.fn.zTree.init($("#treeDemo"), setting, zNodes);
  226. });
  227. let repolink = {{.RepoLink }}
  228. let repoId = {{ $repository }}
  229. const { _AppSubUrl, _StaticUrlPrefix, csrf } = window.config;
  230. $('input[name="_csrf"]').val(csrf)
  231. let modelData;
  232. function createModelName() {
  233. let repoName = location.pathname.split('/')[2]
  234. let modelName = repoName + '_model_' + Math.random().toString(36).substr(2, 4)
  235. $('#name').val(modelName)
  236. $('#version').val("0.0.1")
  237. }
  238. let dirKey="isOnlyDir--:&";
  239. /*
  240. function showcreate(obj) {
  241. $('.ui.modal.second')
  242. .modal({
  243. centered: false,
  244. onShow: function () {
  245. $('input[name="version"]').addClass('model_disabled')
  246. $('.ui.dimmer').css({ "background-color": "rgb(136, 136, 136,0.7)" })
  247. $("#job-name").empty()
  248. createModelName()
  249. loadTrainList()
  250. },
  251. onHide: function () {
  252. var cityObj = $("#modelSelectedFile");
  253. cityObj.attr("value", "");
  254. document.getElementById("formId").reset();
  255. $('#choice_model').dropdown('clear')
  256. $('#choice_version').dropdown('clear')
  257. $('#choice_Engine').dropdown('clear')
  258. $('.ui.dimmer').css({ "background-color": "" })
  259. $('.ui.error.message').text()
  260. $('.ui.error.message').css('display', 'none')
  261. }
  262. })
  263. .modal('show')
  264. }
  265. */
  266. $('input[name="version"]').addClass('model_disabled')
  267. $('.ui.dimmer').css({ "background-color": "rgb(136, 136, 136,0.7)" })
  268. $("#job-name").empty()
  269. createModelName()
  270. loadTrainList()
  271. $(function () {
  272. $('#choice_model').dropdown({
  273. onChange: function (value) {
  274. $("#choice_version").addClass("loading")
  275. $('#choice_version').dropdown('clear')
  276. $("#job-version").empty()
  277. loadTrainVersion(value)
  278. }
  279. })
  280. $('#choice_version').dropdown({
  281. onChange: function (value) {
  282. console.log("model version:" + value);
  283. if (modelData != null) {
  284. for (var i = 0; i < modelData.length; i++) {
  285. if (modelData[i].VersionName == value) {
  286. setEngine(modelData[i]);
  287. loadModelFile(modelData[i]);
  288. break;
  289. }
  290. }
  291. }
  292. }
  293. })
  294. });
  295. function versionAdd(version) {
  296. let versionArray = version.split('.')
  297. if (versionArray[2] == '9') {
  298. if (versionArray[1] == '9') {
  299. versionArray[0] = String(Number(versionArray[1]) + 1)
  300. versionArray[1] = '0'
  301. } else {
  302. versionArray[1] = String(Number(versionArray[1]) + 1)
  303. }
  304. versionArray[2] = '0'
  305. } else {
  306. versionArray[2] = String(Number(versionArray[2]) + 1)
  307. }
  308. return versionArray.join('.')
  309. }
  310. function loadTrainList() {
  311. $.get(`${repolink}/modelmanage/query_train_job?repoId=${repoId}`, (data) => {
  312. const n_length = data.length
  313. if(n_length > 0){
  314. let train_html = ''
  315. for (let i = 0; i < n_length; i++) {
  316. train_html += `<div class="item" data-value="${data[i].JobID}">${data[i].DisplayJobName}</div>`
  317. train_html += '</div>'
  318. }
  319. $("#job-name").append(train_html)
  320. $("#choice_model").removeClass("loading")
  321. $('#choice_model .default.text').text(data[0].DisplayJobName)
  322. $('#choice_model input[name="jobId"]').val(data[0].JobID)
  323. loadTrainVersion()
  324. }else{
  325. $("#choice_model").removeClass("loading")
  326. }
  327. })
  328. }
  329. function loadTrainVersion(value) {
  330. let tmp = $('#choice_model input[name="jobId"]').val();
  331. let jobId = !value ? $('#choice_model input[name="jobId"]').val() : value
  332. $.get(`${repolink}/modelmanage/query_train_job_version?jobId=${jobId}`, (data) => {
  333. const n_length = data.length
  334. let train_html = '';
  335. modelData = data;
  336. for (let i = 0; i < n_length; i++) {
  337. var VersionName = data[i].VersionName || 'V0001';
  338. train_html += `<div class="item" data-value="${VersionName}">${VersionName}</div>`
  339. train_html += '</div>'
  340. }
  341. if (data.length) {
  342. $("#job-version").append(train_html)
  343. $("#choice_version").removeClass("loading")
  344. var versionName = data[0].VersionName;
  345. if (versionName == null || versionName == "") {
  346. versionName = "V0001";
  347. }
  348. $('#choice_version .default.text').text(versionName)
  349. $('#choice_version input[name="versionName"]').val(versionName)
  350. setEngine(data[0])
  351. loadModelFile(data[0])
  352. }
  353. })
  354. }
  355. function loadModelFile(trainJob){
  356. console.log("trainJob=", trainJob);
  357. $('#choice_file').dropdown('clear')
  358. $("#model-file").empty()
  359. if(trainJob ==null || trainJob ==""){
  360. console.log("trainJob is null");
  361. }else{
  362. let type = trainJob.Type;
  363. if(type == 2){
  364. if(trainJob.ComputeResource=="NPU"){
  365. type=1;
  366. }else{
  367. type=0;
  368. }
  369. }
  370. $.get(`${repolink}/modelmanage/query_train_model?jobName=${trainJob.JobName}&type=${type}&versionName=${trainJob.VersionName}`, (data) => {
  371. var cityObj = $("#modelSelectedFile");
  372. cityObj.attr("value", "");
  373. const n_length = data.length
  374. let file_html=''
  375. let firstFileName =''
  376. var zNodes=[];
  377. var nodesMap={};
  378. for (let i=0;i<n_length;i++){
  379. var parentNodeMap = nodesMap;
  380. var fileSplits = data[i].FileName.split("/");
  381. for(let j=0;j < fileSplits.length;j++){
  382. if(fileSplits[j] == ""){
  383. break;
  384. }
  385. if(parentNodeMap[fileSplits[j]] == null){
  386. parentNodeMap[fileSplits[j]] = {};
  387. }
  388. parentNodeMap = parentNodeMap[fileSplits[j]];
  389. }
  390. }
  391. for (let i=0;i<n_length;i++){
  392. var parentNodeMap = nodesMap;
  393. var fileSplits = data[i].FileName.split("/");
  394. for(let j=0;j < fileSplits.length;j++){
  395. if(fileSplits[j] == ""){
  396. if(data[i].FileName[data[i].FileName.length -1] =="/"){
  397. if(Object.keys(parentNodeMap).length ==0){
  398. parentNodeMap[dirKey]="true";
  399. }
  400. }
  401. break;
  402. }
  403. parentNodeMap = parentNodeMap[fileSplits[j]];
  404. }
  405. }
  406. convertToNode(zNodes,nodesMap);
  407. $.fn.zTree.init($("#treeDemo"), setting, zNodes);
  408. })
  409. }
  410. }
  411. function convertToNode(nodeList,nodesMap){
  412. var keyList = Object.keys(nodesMap);
  413. keyList.sort(function(a,b){
  414. return a-b;
  415. });
  416. var isFirst = true;
  417. for(var i=0; i<keyList.length;i++){
  418. var node = {};
  419. node["name"] = keyList[i];
  420. nodeList.push(node);
  421. if(nodesMap[keyList[i]] != null && Object.keys(nodesMap[keyList[i]]).length >0){
  422. if(nodesMap[keyList[i]][dirKey] != null){
  423. node["open"] = false;
  424. node["isParent"] = true;
  425. }else{
  426. node["children"]=[];
  427. if(isFirst){
  428. node["open"] = true;
  429. isFirst= false;
  430. }
  431. convertToNode(node["children"],nodesMap[keyList[i]]);
  432. }
  433. }
  434. }
  435. }
  436. function setEngine(trainJob) {
  437. console.log("trainJob=", trainJob);
  438. $('#choice_Engine').dropdown('clear')
  439. $("#job-Engine").empty()
  440. if (trainJob.EngineName != null && trainJob.EngineName != "") {
  441. srcEngine = trainJob.EngineName.split('-')[0]
  442. srcEngine = srcEngine.trim().toLowerCase();
  443. let selectedText = "PyTorch";
  444. let selectedValue = 0;
  445. let itemHtml = "<option class=\"item\" data-value=\"0\">PyTorch</option>";
  446. if (srcEngine == 'tensorflow') {
  447. selectedText = "TensorFlow";
  448. selectedValue = 1;
  449. itemHtml += "<option class=\"active item\" data-value=\"1\">TensorFlow</option>";
  450. } else {
  451. itemHtml += "<option class=\"item\" data-value=\"1\">TensorFlow</option>";
  452. }
  453. if (srcEngine == 'mindspore') {
  454. selectedText = "MindSpore";
  455. selectedValue = 2;
  456. itemHtml += "<option class=\"active item\" data-value=\"2\">MindSpore</option>";
  457. } else {
  458. itemHtml += "<option class=\"item\" data-value=\"2\">MindSpore</option>";
  459. }
  460. itemHtml += "<option class=\"item\" data-value=\"4\">PaddlePaddle</option>"
  461. itemHtml += "<option class=\"item\" data-value=\"5\">OneFlow</option>"
  462. itemHtml += "<option class=\"item\" data-value=\"6\">MXNet</option>"
  463. itemHtml += "<option class=\"item\" data-value=\"3\">Other</option>"
  464. $('#choice_Engine .default.text').text(selectedText)
  465. $('#choice_Engine input[name="engine"]').val(selectedValue)
  466. $("#job-Engine").append(itemHtml);
  467. $("#choice_Engine").removeClass('disabled');
  468. } else {
  469. let itemHtml = "<option class=\"active item\" data-value=\"0\">PyTorch</option>";
  470. itemHtml += "<option class=\"item\" data-value=\"1\">TensorFlow</option>"
  471. itemHtml += "<option class=\"item\" data-value=\"2\">MindSpore</option>"
  472. itemHtml += "<option class=\"item\" data-value=\"4\">PaddlePaddle</option>"
  473. itemHtml += "<option class=\"item\" data-value=\"5\">OneFlow</option>"
  474. itemHtml += "<option class=\"item\" data-value=\"6\">MXNet</option>"
  475. itemHtml += "<option class=\"item\" data-value=\"3\">Other</option>"
  476. $('#choice_Engine .default.text').text("PyTorch");
  477. $('#choice_Engine input[name="engine"]').val(0)
  478. $("#job-Engine").append(itemHtml);
  479. $("#choice_Engine").removeClass('disabled');
  480. }
  481. }
  482. function check() {
  483. let jobid = document.getElementById("jobId").value;
  484. let versionname = document.getElementById("versionName").value;
  485. let name = document.getElementById("name").value;
  486. let version = document.getElementById("version").value;
  487. let modelSelectedFile = document.getElementById("modelSelectedFile").value;
  488. if (name == "") {
  489. $("#modelname").closest('.required').addClass("error");
  490. return false;
  491. } else {
  492. $("#modelname").closest('.required').removeClass("error");
  493. }
  494. if (versionname == "") {
  495. $("#verionname").closest('.required').addClass("error");
  496. return false;
  497. } else {
  498. $("#verionname").closest('.required').removeClass("error");
  499. }
  500. if (jobid == "") {
  501. $("#jobId").closest('.required').addClass("error");
  502. return false;
  503. } else {
  504. $("#jobId").closest('.required').removeClass("error");
  505. }
  506. if (modelSelectedFile == "") {
  507. $("#modelSelectedFile").closest('.required').addClass("error");
  508. return false;
  509. } else {
  510. $("#modelSelectedFile").closest('.required').removeClass("error");
  511. }
  512. if (versionname == "") {
  513. $("#versionName").closest('.required').addClass("error");
  514. return false;
  515. } else {
  516. $("#versionName").closest('.required').removeClass("error");
  517. }
  518. return true;
  519. }
  520. function submitSaveModel() {
  521. let flag = check();
  522. if (!flag) return false;
  523. $(".ui.error.message").hide();
  524. let cName = $("input[name='name']").val();
  525. let version = $("input[name='version']").val();
  526. let data = $("#formId").serialize();
  527. const initModel = $("input[name='initModel']").val();
  528. let url_href = location.href.split("create_online_model")[0] + 'create_new_model';
  529. $("#mask").css({ display: "block", "z-index": "9999" });
  530. $.ajax({
  531. url: url_href,
  532. type: "POST",
  533. data: data,
  534. success: function (res) {
  535. backToModelListPage();
  536. },
  537. error: function (xhr) {
  538. // 隐藏 loading
  539. // 只有请求不正常(状态码不为200)才会执行
  540. $(".ui.error.message").text(xhr.responseText);
  541. $(".ui.error.message").show();
  542. },
  543. complete: function (xhr) {
  544. $("#mask").css({ display: "none", "z-index": "1" });
  545. },
  546. });
  547. }
  548. function backToModelListPage() {
  549. let url_href = location.href.split("create_online_model")[0] + 'show_model';
  550. window.location.href = url_href;
  551. }
  552. window.submitSaveModel = submitSaveModel;
  553. window.backToModelListPage = backToModelListPage;
  554. })();
  555. </script>