瀏覽代碼

feat: model ordering

Timothy J. Baek 11 月之前
父節點
當前提交
fc2b314c4f
共有 4 個文件被更改,包括 84 次插入7 次删除
  1. 6 0
      package-lock.json
  2. 1 0
      package.json
  3. 19 3
      src/lib/apis/index.ts
  4. 58 4
      src/lib/components/workspace/Models.svelte

+ 6 - 0
package-lock.json

@@ -24,6 +24,7 @@
 				"katex": "^0.16.9",
 				"marked": "^9.1.0",
 				"pyodide": "^0.26.0-alpha.4",
+				"sortablejs": "^1.15.2",
 				"svelte-sonner": "^0.3.19",
 				"tippy.js": "^6.3.7",
 				"uuid": "^9.0.1"
@@ -6913,6 +6914,11 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
+		"node_modules/sortablejs": {
+			"version": "1.15.2",
+			"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz",
+			"integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA=="
+		},
 		"node_modules/source-map-js": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",

+ 1 - 0
package.json

@@ -64,6 +64,7 @@
 		"katex": "^0.16.9",
 		"marked": "^9.1.0",
 		"pyodide": "^0.26.0-alpha.4",
+		"sortablejs": "^1.15.2",
 		"svelte-sonner": "^0.3.19",
 		"tippy.js": "^6.3.7",
 		"uuid": "^9.0.1"

+ 19 - 3
src/lib/apis/index.ts

@@ -29,8 +29,24 @@ export const getModels = async (token: string = '') => {
 
 	models = models
 		.filter((models) => models)
+		// Sort the models
 		.sort((a, b) => {
-			// Compare case-insensitively
+			// Check if models have position property
+			const aHasPosition = a.info?.meta?.position !== undefined;
+			const bHasPosition = b.info?.meta?.position !== undefined;
+
+			// If both a and b have the position property
+			if (aHasPosition && bHasPosition) {
+				return a.info.meta.position - b.info.meta.position;
+			}
+
+			// If only a has the position property, it should come first
+			if (aHasPosition) return -1;
+
+			// If only b has the position property, it should come first
+			if (bHasPosition) return 1;
+
+			// Compare case-insensitively by name for models without position property
 			const lowerA = a.name.toLowerCase();
 			const lowerB = b.name.toLowerCase();
 
@@ -39,8 +55,8 @@ export const getModels = async (token: string = '') => {
 
 			// If same case-insensitively, sort by original strings,
 			// lowercase will come before uppercase due to ASCII values
-			if (a < b) return -1;
-			if (a > b) return 1;
+			if (a.name < b.name) return -1;
+			if (a.name > b.name) return 1;
 
 			return 0; // They are equal
 		});

+ 58 - 4
src/lib/components/workspace/Models.svelte

@@ -1,9 +1,11 @@
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
+	import Sortable from 'sortablejs';
+
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
 
-	import { onMount, getContext } from 'svelte';
+	import { onMount, getContext, tick } from 'svelte';
 
 	import { WEBUI_NAME, modelfiles, models, settings, user } from '$lib/stores';
 	import { addNewModel, deleteModelById, getModelInfos, updateModelById } from '$lib/apis/models';
@@ -12,6 +14,7 @@
 	import { goto } from '$app/navigation';
 
 	import { getModels } from '$lib/apis';
+
 	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
 	import ModelMenu from './Models/ModelMenu.svelte';
 
@@ -22,6 +25,9 @@
 	let importFiles;
 	let modelsImportInputElement: HTMLInputElement;
 
+	let _models = [];
+
+	let sortable = null;
 	let searchValue = '';
 
 	const deleteModelHandler = async (model) => {
@@ -42,6 +48,7 @@
 		}
 
 		await models.set(await getModels(localStorage.token));
+		_models = $models;
 	};
 
 	const cloneModelHandler = async (model) => {
@@ -109,6 +116,7 @@
 		}
 
 		await models.set(await getModels(localStorage.token));
+		_models = $models;
 	};
 
 	const downloadModels = async (models) => {
@@ -118,13 +126,58 @@
 		saveAs(blob, `models-export-${Date.now()}.json`);
 	};
 
-	onMount(() => {
+	const positionChangeHanlder = async () => {
+		// Get the new order of the models
+		const modelIds = Array.from(document.getElementById('model-list').children).map((child) =>
+			child.id.replace('model-item-', '')
+		);
+
+		// Update the position of the models
+		for (const [index, id] of modelIds.entries()) {
+			const model = $models.find((m) => m.id === id);
+			if (model) {
+				let info = model.info;
+
+				if (!info) {
+					info = {
+						id: model.id,
+						name: model.name,
+						meta: {
+							position: index
+						},
+						params: {}
+					};
+				}
+
+				info.meta = {
+					...info.meta,
+					position: index
+				};
+				await updateModelById(localStorage.token, info.id, info);
+			}
+		}
+
+		await tick();
+		await models.set(await getModels(localStorage.token));
+	};
+
+	onMount(async () => {
 		// Legacy code to sync localModelfiles with models
+		_models = $models;
 		localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
 
 		if (localModelfiles) {
 			console.log(localModelfiles);
 		}
+
+		// SortableJS
+		sortable = new Sortable(document.getElementById('model-list'), {
+			animation: 150,
+			onUpdate: async (event) => {
+				console.log(event);
+				positionChangeHanlder();
+			}
+		});
 	});
 </script>
 
@@ -202,12 +255,13 @@
 
 <hr class=" dark:border-gray-850" />
 
-<div class=" my-2 mb-5">
-	{#each $models.filter((m) => searchValue === '' || m.name
+<div class=" my-2 mb-5" id="model-list">
+	{#each _models.filter((m) => searchValue === '' || m.name
 				.toLowerCase()
 				.includes(searchValue.toLowerCase())) as model}
 		<div
 			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+			id="model-item-{model.id}"
 		>
 			<a
 				class=" flex flex-1 space-x-3.5 cursor-pointer w-full"