浏览代码

feat: character card support

Timothy J. Baek 10 月之前
父节点
当前提交
ab62228877

+ 14 - 0
package-lock.json

@@ -34,6 +34,7 @@
 				"sortablejs": "^1.15.2",
 				"svelte-sonner": "^0.3.19",
 				"tippy.js": "^6.3.7",
+				"turndown": "^7.2.0",
 				"uuid": "^9.0.1"
 			},
 			"devDependencies": {
@@ -1000,6 +1001,11 @@
 				"svelte": ">=3 <5"
 			}
 		},
+		"node_modules/@mixmark-io/domino": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
+			"integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
+		},
 		"node_modules/@nodelib/fs.scandir": {
 			"version": "2.1.5",
 			"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -9077,6 +9083,14 @@
 				"node": "*"
 			}
 		},
+		"node_modules/turndown": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz",
+			"integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==",
+			"dependencies": {
+				"@mixmark-io/domino": "^2.2.0"
+			}
+		},
 		"node_modules/tweetnacl": {
 			"version": "0.14.5",
 			"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",

+ 1 - 0
package.json

@@ -74,6 +74,7 @@
 		"sortablejs": "^1.15.2",
 		"svelte-sonner": "^0.3.19",
 		"tippy.js": "^6.3.7",
+		"turndown": "^7.2.0",
 		"uuid": "^9.0.1"
 	}
 }

+ 18 - 8
src/lib/components/chat/Messages/Placeholder.svelte

@@ -9,6 +9,7 @@
 
 	import Suggestions from '../MessageInput/Suggestions.svelte';
 	import { sanitizeResponseContent } from '$lib/utils';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -41,14 +42,23 @@
 							selectedModelIdx = modelIdx;
 						}}
 					>
-						<img
-							crossorigin="anonymous"
-							src={model?.info?.meta?.profile_image_url ??
-								($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
-							class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
-							alt="logo"
-							draggable="false"
-						/>
+						<Tooltip
+							content={marked.parse(
+								sanitizeResponseContent(models[selectedModelIdx]?.info?.meta?.description)
+							)}
+							placement="right"
+						>
+							<img
+								crossorigin="anonymous"
+								src={model?.info?.meta?.profile_image_url ??
+									($i18n.language === 'dg-DG'
+										? `/doge.png`
+										: `${WEBUI_BASE_URL}/static/favicon.png`)}
+								class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
+								alt="logo"
+								draggable="false"
+							/>
+						</Tooltip>
 					</button>
 				{/each}
 			</div>

+ 1 - 1
src/lib/components/layout/Sidebar/ChatItem.svelte

@@ -126,7 +126,7 @@
 			: selected
 			? 'from-gray-100 dark:from-gray-950'
 			: 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
-            absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
+            absolute right-[10px] top-[6px] py-1 pr-2 pl-5 bg-gradient-to-l from-80%
 
               to-transparent"
 		on:mouseenter={(e) => {

+ 30 - 3
src/routes/(app)/workspace/models/create/+page.svelte

@@ -4,6 +4,8 @@
 	import { goto } from '$app/navigation';
 	import { settings, user, config, models, tools } from '$lib/stores';
 
+	import TurndownService from 'turndown';
+
 	import { onMount, tick, getContext } from 'svelte';
 	import { addNewModel, getModelById, getModelInfos } from '$lib/apis/models';
 	import { getModels } from '$lib/apis';
@@ -14,6 +16,7 @@
 	import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte';
 	import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte';
 	import { stringify } from 'postcss';
+	import { parseFile } from '$lib/utils/characters';
 
 	const i18n = getContext('i18n');
 
@@ -60,7 +63,10 @@
 	let knowledge = [];
 
 	$: if (name) {
-		id = name.replace(/\s+/g, '-').toLowerCase();
+		id = name
+			.replace(/\s+/g, '-')
+			.replace(/[^a-zA-Z0-9-]/g, '')
+			.toLowerCase();
 	}
 
 	const addUsage = (base_model_id) => {
@@ -213,9 +219,29 @@
 		accept="image/*"
 		on:change={() => {
 			let reader = new FileReader();
-			reader.onload = (event) => {
+			reader.onload = async (event) => {
 				let originalImageUrl = `${event.target.result}`;
 
+				let character = await parseFile(inputFiles[0]).catch((error) => {
+					return null;
+				});
+
+				if (character && character.character) {
+					character = character.character;
+					console.log(character);
+
+					name = character.name;
+
+					const turndownService = new TurndownService();
+					info.meta.description = turndownService.turndown(character.summary);
+
+					info.params.system = `Personality: ${character.personality}${
+						character?.scenario ? `\nScenario: ${character.scenario}` : ''
+					}${character?.greeting ? `\First Message: ${character.greeting}` : ''}${
+						character?.examples ? `\nExamples: ${character.examples}` : ''
+					}`;
+				}
+
 				const img = new Image();
 				img.src = originalImageUrl;
 
@@ -408,10 +434,11 @@
 			</div>
 
 			{#if info.meta.description !== null}
-				<input
+				<textarea
 					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
 					placeholder={$i18n.t('Add a short description about what this model does')}
 					bind:value={info.meta.description}
+					row="3"
 				/>
 			{/if}
 		</div>

+ 2 - 1
src/routes/(app)/workspace/models/edit/+page.svelte

@@ -369,10 +369,11 @@
 				</div>
 
 				{#if info.meta.description !== null}
-					<input
+					<textarea
 						class="mt-1 px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
 						placeholder={$i18n.t('Add a short description about what this model does')}
 						bind:value={info.meta.description}
+						row="3"
 					/>
 				{/if}
 			</div>