Browse Source

Merge pull request #407 from anuraagdjain/feat/parallel-model-downloads

feat: parallel model downloads
Timothy Jaeryang Baek 1 year ago
parent
commit
ed4b3e0b32
4 changed files with 146 additions and 546 deletions
  1. 11 441
      package-lock.json
  2. 1 0
      package.json
  3. 19 2
      src/lib/apis/ollama/index.ts
  4. 115 103
      src/lib/components/chat/SettingsModal.svelte

+ 11 - 441
package-lock.json

@@ -9,6 +9,7 @@
 			"version": "0.0.1",
 			"dependencies": {
 				"@sveltejs/adapter-node": "^1.3.1",
+				"async": "^3.2.5",
 				"dayjs": "^1.11.10",
 				"file-saver": "^2.0.5",
 				"highlight.js": "^11.9.0",
@@ -76,51 +77,6 @@
 				"node": ">=6.0.0"
 			}
 		},
-		"node_modules/@esbuild/android-arm": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
-			"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
-			"cpu": [
-				"arm"
-			],
-			"optional": true,
-			"os": [
-				"android"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/android-arm64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
-			"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
-			"cpu": [
-				"arm64"
-			],
-			"optional": true,
-			"os": [
-				"android"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/android-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
-			"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
-			"cpu": [
-				"x64"
-			],
-			"optional": true,
-			"os": [
-				"android"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
 		"node_modules/@esbuild/darwin-arm64": {
 			"version": "0.18.20",
 			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
@@ -136,276 +92,6 @@
 				"node": ">=12"
 			}
 		},
-		"node_modules/@esbuild/darwin-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
-			"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
-			"cpu": [
-				"x64"
-			],
-			"optional": true,
-			"os": [
-				"darwin"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/freebsd-arm64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
-			"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
-			"cpu": [
-				"arm64"
-			],
-			"optional": true,
-			"os": [
-				"freebsd"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/freebsd-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
-			"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
-			"cpu": [
-				"x64"
-			],
-			"optional": true,
-			"os": [
-				"freebsd"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-arm": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
-			"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
-			"cpu": [
-				"arm"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-arm64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
-			"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
-			"cpu": [
-				"arm64"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-ia32": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
-			"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
-			"cpu": [
-				"ia32"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-loong64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
-			"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
-			"cpu": [
-				"loong64"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-mips64el": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
-			"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
-			"cpu": [
-				"mips64el"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-ppc64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
-			"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
-			"cpu": [
-				"ppc64"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-riscv64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
-			"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
-			"cpu": [
-				"riscv64"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-s390x": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
-			"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
-			"cpu": [
-				"s390x"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/linux-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
-			"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
-			"cpu": [
-				"x64"
-			],
-			"optional": true,
-			"os": [
-				"linux"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/netbsd-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
-			"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
-			"cpu": [
-				"x64"
-			],
-			"optional": true,
-			"os": [
-				"netbsd"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/openbsd-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
-			"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
-			"cpu": [
-				"x64"
-			],
-			"optional": true,
-			"os": [
-				"openbsd"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/sunos-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
-			"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
-			"cpu": [
-				"x64"
-			],
-			"optional": true,
-			"os": [
-				"sunos"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/win32-arm64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
-			"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
-			"cpu": [
-				"arm64"
-			],
-			"optional": true,
-			"os": [
-				"win32"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/win32-ia32": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
-			"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
-			"cpu": [
-				"ia32"
-			],
-			"optional": true,
-			"os": [
-				"win32"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
-		"node_modules/@esbuild/win32-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
-			"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
-			"cpu": [
-				"x64"
-			],
-			"optional": true,
-			"os": [
-				"win32"
-			],
-			"engines": {
-				"node": ">=12"
-			}
-		},
 		"node_modules/@eslint-community/eslint-utils": {
 			"version": "4.4.0",
 			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1250,6 +936,11 @@
 				"node": ">=8"
 			}
 		},
+		"node_modules/async": {
+			"version": "3.2.5",
+			"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
+			"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
+		},
 		"node_modules/autoprefixer": {
 			"version": "10.4.16",
 			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
@@ -4022,138 +3713,12 @@
 				"@jridgewell/trace-mapping": "^0.3.9"
 			}
 		},
-		"@esbuild/android-arm": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
-			"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
-			"optional": true
-		},
-		"@esbuild/android-arm64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
-			"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
-			"optional": true
-		},
-		"@esbuild/android-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
-			"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
-			"optional": true
-		},
 		"@esbuild/darwin-arm64": {
 			"version": "0.18.20",
 			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
 			"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
 			"optional": true
 		},
-		"@esbuild/darwin-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
-			"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
-			"optional": true
-		},
-		"@esbuild/freebsd-arm64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
-			"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
-			"optional": true
-		},
-		"@esbuild/freebsd-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
-			"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
-			"optional": true
-		},
-		"@esbuild/linux-arm": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
-			"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
-			"optional": true
-		},
-		"@esbuild/linux-arm64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
-			"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
-			"optional": true
-		},
-		"@esbuild/linux-ia32": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
-			"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
-			"optional": true
-		},
-		"@esbuild/linux-loong64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
-			"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
-			"optional": true
-		},
-		"@esbuild/linux-mips64el": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
-			"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
-			"optional": true
-		},
-		"@esbuild/linux-ppc64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
-			"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
-			"optional": true
-		},
-		"@esbuild/linux-riscv64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
-			"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
-			"optional": true
-		},
-		"@esbuild/linux-s390x": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
-			"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
-			"optional": true
-		},
-		"@esbuild/linux-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
-			"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
-			"optional": true
-		},
-		"@esbuild/netbsd-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
-			"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
-			"optional": true
-		},
-		"@esbuild/openbsd-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
-			"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
-			"optional": true
-		},
-		"@esbuild/sunos-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
-			"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
-			"optional": true
-		},
-		"@esbuild/win32-arm64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
-			"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
-			"optional": true
-		},
-		"@esbuild/win32-ia32": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
-			"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
-			"optional": true
-		},
-		"@esbuild/win32-x64": {
-			"version": "0.18.20",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
-			"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
-			"optional": true
-		},
 		"@eslint-community/eslint-utils": {
 			"version": "4.4.0",
 			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -4735,6 +4300,11 @@
 			"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
 			"dev": true
 		},
+		"async": {
+			"version": "3.2.5",
+			"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
+			"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
+		},
 		"autoprefixer": {
 			"version": "10.4.16",
 			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",

+ 1 - 0
package.json

@@ -40,6 +40,7 @@
 	"type": "module",
 	"dependencies": {
 		"@sveltejs/adapter-node": "^1.3.1",
+		"async": "^3.2.5",
 		"dayjs": "^1.11.10",
 		"file-saver": "^2.0.5",
 		"highlight.js": "^11.9.0",

+ 19 - 2
src/lib/apis/ollama/index.ts

@@ -299,13 +299,30 @@ export const pullModel = async (token: string, tagName: string) => {
 			name: tagName
 		})
 	}).catch((err) => {
+		console.log(err);
 		error = err;
+
+		if ('detail' in err) {
+			error = err.detail;
+		}
+
 		return null;
 	});
-
 	if (error) {
 		throw error;
 	}
-
 	return res;
 };
+
+// export const pullModel = async (token: string, tagName: string) => {
+// 	return await fetch(`${OLLAMA_API_BASE_URL}/pull`, {
+// 		method: 'POST',
+// 		headers: {
+// 			'Content-Type': 'text/event-stream',
+// 			Authorization: `Bearer ${token}`
+// 		},
+// 		body: JSON.stringify({
+// 			name: tagName
+// 		})
+// 	});
+// };

+ 115 - 103
src/lib/components/chat/SettingsModal.svelte

@@ -1,11 +1,11 @@
 <script lang="ts">
 	import toast from 'svelte-french-toast';
+	import queue from 'async/queue';
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
 
+	import { goto } from '$app/navigation';
 	import { onMount } from 'svelte';
-	import { config, models, settings, user, chats } from '$lib/stores';
-	import { splitStream, getGravatarURL } from '$lib/utils';
 
 	import {
 		getOllamaVersion,
@@ -16,14 +16,16 @@
 		createModel,
 		deleteModel
 	} from '$lib/apis/ollama';
+	import { updateUserPassword } from '$lib/apis/auths';
 	import { createNewChat, deleteAllChats, getAllChats, getChatList } from '$lib/apis/chats';
 	import { WEB_UI_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
 
+	import { config, models, settings, user, chats } from '$lib/stores';
+	import { splitStream, getGravatarURL } from '$lib/utils';
+
 	import Advanced from './Settings/Advanced.svelte';
 	import Modal from '../common/Modal.svelte';
-	import { updateUserPassword } from '$lib/apis/auths';
-	import { goto } from '$app/navigation';
-	import Page from '../../../routes/(app)/+page.svelte';
+
 	import {
 		getOpenAIKey,
 		getOpenAIModels,
@@ -71,8 +73,15 @@
 	};
 
 	// Models
-	let modelTransferring = false;
+	const MAX_PARALLEL_DOWNLOADS = 3;
+	const modelDownloadQueue = queue(
+		(task: { modelName: string }, cb) =>
+			pullModelHandlerProcessor({ modelName: task.modelName, callback: cb }),
+		MAX_PARALLEL_DOWNLOADS
+	);
+	let modelDownloadStatus: Record<string, any> = {};
 
+	let modelTransferring = false;
 	let modelTag = '';
 	let digest = '';
 	let pullProgress = null;
@@ -87,7 +96,6 @@
 	let deleteModelTag = '';
 
 	// External
-
 	let OPENAI_API_KEY = '';
 	let OPENAI_API_BASE_URL = '';
 
@@ -104,21 +112,18 @@
 	let importFiles;
 	let showDeleteConfirm = false;
 
-	const importChats = async (_chats) => {
-		for (const chat of _chats) {
-			console.log(chat);
-			await createNewChat(localStorage.token, chat);
-		}
+	// Auth
+	let authEnabled = false;
+	let authType = 'Basic';
+	let authContent = '';
 
-		await chats.set(await getChatList(localStorage.token));
-	};
+	// Account
+	let currentPassword = '';
+	let newPassword = '';
+	let newPasswordConfirm = '';
 
-	const exportChats = async () => {
-		let blob = new Blob([JSON.stringify(await getAllChats(localStorage.token))], {
-			type: 'application/json'
-		});
-		saveAs(blob, `chat-export-${Date.now()}.json`);
-	};
+	// About
+	let ollamaVersion = '';
 
 	$: if (importFiles) {
 		console.log(importFiles);
@@ -133,25 +138,28 @@
 		reader.readAsText(importFiles[0]);
 	}
 
+	const importChats = async (_chats) => {
+		for (const chat of _chats) {
+			console.log(chat);
+			await createNewChat(localStorage.token, chat);
+		}
+
+		await chats.set(await getChatList(localStorage.token));
+	};
+
+	const exportChats = async () => {
+		let blob = new Blob([JSON.stringify(await getAllChats(localStorage.token))], {
+			type: 'application/json'
+		});
+		saveAs(blob, `chat-export-${Date.now()}.json`);
+	};
+
 	const deleteChats = async () => {
 		await goto('/');
 		await deleteAllChats(localStorage.token);
 		await chats.set(await getChatList(localStorage.token));
 	};
 
-	// Auth
-	let authEnabled = false;
-	let authType = 'Basic';
-	let authContent = '';
-
-	// Account
-	let currentPassword = '';
-	let newPassword = '';
-	let newPasswordConfirm = '';
-
-	// About
-	let ollamaVersion = '';
-
 	const updateOllamaAPIUrlHandler = async () => {
 		API_BASE_URL = await updateOllamaAPIUrl(localStorage.token, API_BASE_URL);
 		const _models = await getModels('ollama');
@@ -247,10 +255,11 @@
 		saveSettings({ saveChatHistory: saveChatHistory });
 	};
 
-	const pullModelHandler = async () => {
-		modelTransferring = true;
-
-		const res = await pullModel(localStorage.token, modelTag);
+	const pullModelHandlerProcessor = async (opts: { modelName: string; callback: Function }) => {
+		const res = await pullModel(localStorage.token, opts.modelName).catch((error) => {
+			opts.callback({ success: false, error, modelName: opts.modelName });
+			return null;
+		});
 
 		if (res) {
 			const reader = res.body
@@ -259,92 +268,89 @@
 				.getReader();
 
 			while (true) {
-				const { value, done } = await reader.read();
-				if (done) break;
-
 				try {
+					const { value, done } = await reader.read();
+					if (done) break;
+
 					let lines = value.split('\n');
 
 					for (const line of lines) {
 						if (line !== '') {
-							console.log(line);
 							let data = JSON.parse(line);
-							console.log(data);
-
 							if (data.error) {
 								throw data.error;
 							}
-
 							if (data.detail) {
 								throw data.detail;
 							}
 							if (data.status) {
-								if (!data.digest) {
-									toast.success(data.status);
-
-									if (data.status === 'success') {
-										const notification = new Notification(`Ollama`, {
-											body: `Model '${modelTag}' has been successfully downloaded.`,
-											icon: '/favicon.png'
-										});
-									}
-								} else {
-									digest = data.digest;
+								if (data.digest) {
+									let downloadProgress = 0;
 									if (data.completed) {
-										pullProgress = Math.round((data.completed / data.total) * 1000) / 10;
+										downloadProgress = Math.round((data.completed / data.total) * 1000) / 10;
 									} else {
-										pullProgress = 100;
+										downloadProgress = 100;
 									}
+									modelDownloadStatus[opts.modelName] = {
+										pullProgress: downloadProgress,
+										digest: data.digest
+									};
+								} else {
+									toast.success(data.status);
 								}
 							}
 						}
 					}
 				} catch (error) {
 					console.log(error);
-					toast.error(error);
+					if (typeof error !== 'string') {
+						error = error.message;
+					}
+					opts.callback({ success: false, error, modelName: opts.modelName });
 				}
 			}
+			opts.callback({ success: true, modelName: opts.modelName });
 		}
-
-		modelTag = '';
-		modelTransferring = false;
-
-		models.set(await getModels());
 	};
 
-	const calculateSHA256 = async (file) => {
-		console.log(file);
-		// Create a FileReader to read the file asynchronously
-		const reader = new FileReader();
+	const pullModelHandler = async () => {
+		if (modelDownloadStatus[modelTag]) {
+			toast.error(`Model '${modelTag}' is already in queue for downloading.`);
+			return;
+		}
+		if (Object.keys(modelDownloadStatus).length === 3) {
+			toast.error('Maximum of 3 models can be downloaded simultaneously. Please try again later.');
+			return;
+		}
 
-		// Define a promise to handle the file reading
-		const readFile = new Promise((resolve, reject) => {
-			reader.onload = () => resolve(reader.result);
-			reader.onerror = reject;
-		});
+		modelTransferring = true;
 
-		// Read the file as an ArrayBuffer
-		reader.readAsArrayBuffer(file);
+		modelDownloadQueue.push(
+			{ modelName: modelTag },
+			async (data: { modelName: string; success: boolean; error?: Error }) => {
+				const { modelName } = data;
+				// Remove the downloaded model
+				delete modelDownloadStatus[modelName];
 
-		try {
-			// Wait for the FileReader to finish reading the file
-			const buffer = await readFile;
+				console.log(data);
 
-			// Convert the ArrayBuffer to a Uint8Array
-			const uint8Array = new Uint8Array(buffer);
+				if (!data.success) {
+					toast.error(data.error);
+				} else {
+					toast.success(`Model '${modelName}' has been successfully downloaded.`);
 
-			// Calculate the SHA-256 hash using Web Crypto API
-			const hashBuffer = await crypto.subtle.digest('SHA-256', uint8Array);
+					const notification = new Notification(`Ollama`, {
+						body: `Model '${modelName}' has been successfully downloaded.`,
+						icon: '/favicon.png'
+					});
 
-			// Convert the hash to a hexadecimal string
-			const hashArray = Array.from(new Uint8Array(hashBuffer));
-			const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, '0')).join('');
+					models.set(await getModels());
+				}
+			}
+		);
 
-			return `sha256:${hashHex}`;
-		} catch (error) {
-			console.error('Error calculating SHA-256 hash:', error);
-			throw error;
-		}
+		modelTag = '';
+		modelTransferring = false;
 	};
 
 	const uploadModelHandler = async () => {
@@ -1158,7 +1164,7 @@
 									</button>
 								</div>
 
-								<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+								<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
 									To access the available model names for downloading, <a
 										class=" text-gray-500 dark:text-gray-300 font-medium"
 										href="https://ollama.ai/library"
@@ -1166,23 +1172,29 @@
 									>
 								</div>
 
-								{#if pullProgress !== null}
-									<div class="mt-2">
-										<div class=" mb-2 text-xs">Pull Progress</div>
-										<div class="w-full rounded-full dark:bg-gray-800">
-											<div
-												class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
-												style="width: {Math.max(15, pullProgress ?? 0)}%"
-											>
-												{pullProgress ?? 0}%
+								{#if Object.keys(modelDownloadStatus).length > 0}
+									{#each Object.keys(modelDownloadStatus) as model}
+										<div class="flex flex-col">
+											<div class="font-medium mb-1">{model}</div>
+											<div class="">
+												<div
+													class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
+													style="width: {Math.max(
+														15,
+														modelDownloadStatus[model].pullProgress ?? 0
+													)}%"
+												>
+													{modelDownloadStatus[model].pullProgress ?? 0}%
+												</div>
+												<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
+													{modelDownloadStatus[model].digest}
+												</div>
 											</div>
 										</div>
-										<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
-											{digest}
-										</div>
-									</div>
+									{/each}
 								{/if}
 							</div>
+
 							<hr class=" dark:border-gray-700" />
 
 							<div>