浏览代码

enh: tools user info

Timothy Jaeryang Baek 5 月之前
父节点
当前提交
c50b678dce

+ 14 - 4
backend/open_webui/apps/webui/models/prompts.py

@@ -2,7 +2,7 @@ import time
 from typing import Optional
 from typing import Optional
 
 
 from open_webui.apps.webui.internal.db import Base, get_db
 from open_webui.apps.webui.internal.db import Base, get_db
-from open_webui.apps.webui.models.groups import Groups
+from open_webui.apps.webui.models.users import Users, UserResponse
 
 
 from pydantic import BaseModel, ConfigDict
 from pydantic import BaseModel, ConfigDict
 from sqlalchemy import BigInteger, Column, String, Text, JSON
 from sqlalchemy import BigInteger, Column, String, Text, JSON
@@ -57,6 +57,10 @@ class PromptModel(BaseModel):
 ####################
 ####################
 
 
 
 
+class PromptUserResponse(PromptModel):
+    user: Optional[UserResponse] = None
+
+
 class PromptForm(BaseModel):
 class PromptForm(BaseModel):
     command: str
     command: str
     title: str
     title: str
@@ -97,15 +101,21 @@ class PromptsTable:
         except Exception:
         except Exception:
             return None
             return None
 
 
-    def get_prompts(self) -> list[PromptModel]:
+    def get_prompts(self) -> list[PromptUserResponse]:
         with get_db() as db:
         with get_db() as db:
             return [
             return [
-                PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()
+                PromptUserResponse.model_validate(
+                    {
+                        **PromptModel.model_validate(prompt).model_dump(),
+                        "user": Users.get_user_by_id(prompt.user_id).model_dump(),
+                    }
+                )
+                for prompt in db.query(Prompt).all()
             ]
             ]
 
 
     def get_prompts_by_user_id(
     def get_prompts_by_user_id(
         self, user_id: str, permission: str = "write"
         self, user_id: str, permission: str = "write"
-    ) -> list[PromptModel]:
+    ) -> list[PromptUserResponse]:
         prompts = self.get_prompts()
         prompts = self.get_prompts()
 
 
         return [
         return [

+ 16 - 4
backend/open_webui/apps/webui/models/tools.py

@@ -3,7 +3,7 @@ import time
 from typing import Optional
 from typing import Optional
 
 
 from open_webui.apps.webui.internal.db import Base, JSONField, get_db
 from open_webui.apps.webui.internal.db import Base, JSONField, get_db
-from open_webui.apps.webui.models.users import Users
+from open_webui.apps.webui.models.users import Users, UserResponse
 from open_webui.env import SRC_LOG_LEVELS
 from open_webui.env import SRC_LOG_LEVELS
 from pydantic import BaseModel, ConfigDict
 from pydantic import BaseModel, ConfigDict
 from sqlalchemy import BigInteger, Column, String, Text, JSON
 from sqlalchemy import BigInteger, Column, String, Text, JSON
@@ -86,6 +86,10 @@ class ToolResponse(BaseModel):
     created_at: int  # timestamp in epoch
     created_at: int  # timestamp in epoch
 
 
 
 
+class ToolUserResponse(ToolResponse):
+    user: Optional[UserResponse] = None
+
+
 class ToolForm(BaseModel):
 class ToolForm(BaseModel):
     id: str
     id: str
     name: str
     name: str
@@ -134,13 +138,21 @@ class ToolsTable:
         except Exception:
         except Exception:
             return None
             return None
 
 
-    def get_tools(self) -> list[ToolModel]:
+    def get_tools(self) -> list[ToolUserResponse]:
         with get_db() as db:
         with get_db() as db:
-            return [ToolModel.model_validate(tool) for tool in db.query(Tool).all()]
+            return [
+                ToolUserResponse.model_validate(
+                    {
+                        **ToolModel.model_validate(tool).model_dump(),
+                        "user": Users.get_user_by_id(tool.user_id).model_dump(),
+                    }
+                )
+                for tool in db.query(Tool).order_by(Tool.updated_at.desc()).all()
+            ]
 
 
     def get_tools_by_user_id(
     def get_tools_by_user_id(
         self, user_id: str, permission: str = "write"
         self, user_id: str, permission: str = "write"
-    ) -> list[ToolModel]:
+    ) -> list[ToolUserResponse]:
         tools = self.get_tools()
         tools = self.get_tools()
 
 
         return [
         return [

+ 7 - 2
backend/open_webui/apps/webui/routers/prompts.py

@@ -1,6 +1,11 @@
 from typing import Optional
 from typing import Optional
 
 
-from open_webui.apps.webui.models.prompts import PromptForm, PromptModel, Prompts
+from open_webui.apps.webui.models.prompts import (
+    PromptForm,
+    PromptUserResponse,
+    PromptModel,
+    Prompts,
+)
 from open_webui.constants import ERROR_MESSAGES
 from open_webui.constants import ERROR_MESSAGES
 from fastapi import APIRouter, Depends, HTTPException, status, Request
 from fastapi import APIRouter, Depends, HTTPException, status, Request
 from open_webui.utils.utils import get_admin_user, get_verified_user
 from open_webui.utils.utils import get_admin_user, get_verified_user
@@ -23,7 +28,7 @@ async def get_prompts(user=Depends(get_verified_user)):
     return prompts
     return prompts
 
 
 
 
-@router.get("/list", response_model=list[PromptModel])
+@router.get("/list", response_model=list[PromptUserResponse])
 async def get_prompt_list(user=Depends(get_verified_user)):
 async def get_prompt_list(user=Depends(get_verified_user)):
     if user.role == "admin":
     if user.role == "admin":
         prompts = Prompts.get_prompts()
         prompts = Prompts.get_prompts()

+ 9 - 3
backend/open_webui/apps/webui/routers/tools.py

@@ -2,7 +2,13 @@ import os
 from pathlib import Path
 from pathlib import Path
 from typing import Optional
 from typing import Optional
 
 
-from open_webui.apps.webui.models.tools import ToolForm, ToolModel, ToolResponse, Tools
+from open_webui.apps.webui.models.tools import (
+    ToolForm,
+    ToolModel,
+    ToolResponse,
+    ToolUserResponse,
+    Tools,
+)
 from open_webui.apps.webui.utils import load_tools_module_by_id, replace_imports
 from open_webui.apps.webui.utils import load_tools_module_by_id, replace_imports
 from open_webui.config import CACHE_DIR, DATA_DIR
 from open_webui.config import CACHE_DIR, DATA_DIR
 from open_webui.constants import ERROR_MESSAGES
 from open_webui.constants import ERROR_MESSAGES
@@ -19,7 +25,7 @@ router = APIRouter()
 ############################
 ############################
 
 
 
 
-@router.get("/", response_model=list[ToolResponse])
+@router.get("/", response_model=list[ToolUserResponse])
 async def get_tools(user=Depends(get_verified_user)):
 async def get_tools(user=Depends(get_verified_user)):
     if user.role == "admin":
     if user.role == "admin":
         tools = Tools.get_tools()
         tools = Tools.get_tools()
@@ -33,7 +39,7 @@ async def get_tools(user=Depends(get_verified_user)):
 ############################
 ############################
 
 
 
 
-@router.get("/list", response_model=list[ToolResponse])
+@router.get("/list", response_model=list[ToolUserResponse])
 async def get_tool_list(user=Depends(get_verified_user)):
 async def get_tool_list(user=Depends(get_verified_user)):
     if user.role == "admin":
     if user.role == "admin":
         tools = Tools.get_tools()
         tools = Tools.get_tools()

+ 4 - 4
src/lib/components/workspace/Knowledge.svelte

@@ -84,7 +84,7 @@
 		}}
 		}}
 	/>
 	/>
 
 
-	<div class="flex flex-col gap-1 mt-1.5 mb-2">
+	<div class="flex flex-col gap-1 my-1.5">
 		<div class="flex justify-between items-center">
 		<div class="flex justify-between items-center">
 			<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
 			<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
 				{$i18n.t('Knowledge')}
 				{$i18n.t('Knowledge')}
@@ -121,10 +121,10 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<div class="my-3 mb-5 grid lg:grid-cols-2 xl:grid-cols-3 gap-2">
+	<div class="mb-5 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
 		{#each filteredItems as item}
 		{#each filteredItems as item}
 			<button
 			<button
-				class=" flex space-x-4 cursor-pointer text-left w-full px-4 py-3 border border-gray-50 dark:border-gray-850 dark:hover:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-xl"
+				class=" flex space-x-4 cursor-pointer text-left w-full px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-xl"
 				on:click={() => {
 				on:click={() => {
 					if (item?.meta?.document) {
 					if (item?.meta?.document) {
 						toast.error(
 						toast.error(
@@ -163,7 +163,7 @@
 						</div>
 						</div>
 
 
 						<div class="mt-3 flex justify-between">
 						<div class="mt-3 flex justify-between">
-							<div class="text-xs">
+							<div class="text-xs text-gray-500">
 								<Tooltip
 								<Tooltip
 									content={item?.user?.email}
 									content={item?.user?.email}
 									className="flex shrink-0"
 									className="flex shrink-0"

+ 6 - 6
src/lib/components/workspace/Models.svelte

@@ -196,7 +196,7 @@
 		}}
 		}}
 	/>
 	/>
 
 
-	<div class="flex flex-col gap-1 mt-1.5 mb-2">
+	<div class="flex flex-col gap-1 my-1.5">
 		<div class="flex justify-between items-center">
 		<div class="flex justify-between items-center">
 			<div class="flex items-center md:self-center text-xl font-medium px-0.5">
 			<div class="flex items-center md:self-center text-xl font-medium px-0.5">
 				{$i18n.t('Models')}
 				{$i18n.t('Models')}
@@ -230,14 +230,14 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<div class=" my-2 mb-5 grid gap-2 md:grid-cols-2 lg:grid-cols-3" id="model-list">
+	<div class=" my-2 mb-5 gap-2 grid lg:grid-cols-2 xl:grid-cols-3" id="model-list">
 		{#each filteredModels as model}
 		{#each filteredModels as model}
 			<div
 			<div
 				class=" flex flex-col cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-lg transition"
 				class=" flex flex-col cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-lg transition"
 				id="model-item-{model.id}"
 				id="model-item-{model.id}"
 			>
 			>
 				<div class="flex gap-4 mt-1 mb-0.5">
 				<div class="flex gap-4 mt-1 mb-0.5">
-					<div class=" w-10">
+					<div class=" w-[44px]">
 						<div
 						<div
 							class=" rounded-full object-cover {model.is_active
 							class=" rounded-full object-cover {model.is_active
 								? ''
 								? ''
@@ -252,7 +252,7 @@
 					</div>
 					</div>
 
 
 					<a
 					<a
-						class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
+						class=" flex flex-1 cursor-pointer w-full"
 						href={`/?models=${encodeURIComponent(model.id)}`}
 						href={`/?models=${encodeURIComponent(model.id)}`}
 					>
 					>
 						<div class=" flex-1 self-center {model.is_active ? '' : 'text-gray-500'}">
 						<div class=" flex-1 self-center {model.is_active ? '' : 'text-gray-500'}">
@@ -261,7 +261,7 @@
 								className=" w-fit"
 								className=" w-fit"
 								placement="top-start"
 								placement="top-start"
 							>
 							>
-								<div class=" font-medium line-clamp-1">{model.name}</div>
+								<div class=" font-semibold line-clamp-1">{model.name}</div>
 							</Tooltip>
 							</Tooltip>
 
 
 							<div class="flex gap-1 text-xs overflow-hidden">
 							<div class="flex gap-1 text-xs overflow-hidden">
@@ -278,7 +278,7 @@
 				</div>
 				</div>
 
 
 				<div class="flex justify-between items-center">
 				<div class="flex justify-between items-center">
-					<div class=" text-xs">
+					<div class=" text-xs mt-1">
 						<Tooltip content={model?.user?.email} className="flex shrink-0" placement="top-start">
 						<Tooltip content={model?.user?.email} className="flex shrink-0" placement="top-start">
 							<div class="shrink-0 text-gray-500">
 							<div class="shrink-0 text-gray-500">
 								{$i18n.t('By {{name}}', {
 								{$i18n.t('By {{name}}', {

+ 22 - 6
src/lib/components/workspace/Prompts.svelte

@@ -21,6 +21,8 @@
 	import Plus from '../icons/Plus.svelte';
 	import Plus from '../icons/Plus.svelte';
 	import ChevronRight from '../icons/ChevronRight.svelte';
 	import ChevronRight from '../icons/ChevronRight.svelte';
 	import Spinner from '../common/Spinner.svelte';
 	import Spinner from '../common/Spinner.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import { capitalizeFirstLetter } from '$lib/utils';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 	let promptsImportInputElement: HTMLInputElement;
 	let promptsImportInputElement: HTMLInputElement;
@@ -103,7 +105,7 @@
 		</div>
 		</div>
 	</DeleteConfirmDialog>
 	</DeleteConfirmDialog>
 
 
-	<div class="flex flex-col gap-1 mt-1.5 mb-2">
+	<div class="flex flex-col gap-1 my-1.5">
 		<div class="flex justify-between items-center">
 		<div class="flex justify-between items-center">
 			<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
 			<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
 				{$i18n.t('Prompts')}
 				{$i18n.t('Prompts')}
@@ -137,19 +139,33 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<div class="mb-5">
+	<div class="mb-5 gap-2 grid lg:grid-cols-2 xl:grid-cols-3">
 		{#each filteredItems as prompt}
 		{#each filteredItems as prompt}
 			<div
 			<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"
+				class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl transition"
 			>
 			>
 				<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
 				<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
 					<a href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
 					<a href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
-						<div class=" flex-1 self-center pl-1.5">
-							<div class=" font-semibold line-clamp-1">{prompt.command}</div>
+						<div class=" flex-1 flex items-center gap-2 self-center">
+							<div class=" font-semibold line-clamp-1 capitalize">{prompt.title}</div>
 							<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
 							<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
-								{prompt.title}
+								{prompt.command}
 							</div>
 							</div>
 						</div>
 						</div>
+
+						<div class=" text-xs">
+							<Tooltip
+								content={prompt?.user?.email}
+								className="flex shrink-0"
+								placement="top-start"
+							>
+								<div class="shrink-0 text-gray-500">
+									{$i18n.t('By {{name}}', {
+										name: capitalizeFirstLetter(prompt?.user?.name ?? prompt?.user?.email)
+									})}
+								</div>
+							</Tooltip>
+						</div>
 					</a>
 					</a>
 				</div>
 				</div>
 				<div class="flex flex-row gap-0.5 self-center">
 				<div class="flex flex-row gap-0.5 self-center">

+ 35 - 20
src/lib/components/workspace/Tools.svelte

@@ -30,6 +30,7 @@
 	import Plus from '../icons/Plus.svelte';
 	import Plus from '../icons/Plus.svelte';
 	import ChevronRight from '../icons/ChevronRight.svelte';
 	import ChevronRight from '../icons/ChevronRight.svelte';
 	import Spinner from '../common/Spinner.svelte';
 	import Spinner from '../common/Spinner.svelte';
+	import { capitalizeFirstLetter } from '$lib/utils';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
@@ -172,7 +173,7 @@
 </svelte:head>
 </svelte:head>
 
 
 {#if loaded}
 {#if loaded}
-	<div class="flex flex-col gap-1 mt-1.5 mb-2">
+	<div class="flex flex-col gap-1 my-1.5">
 		<div class="flex justify-between items-center">
 		<div class="flex justify-between items-center">
 			<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
 			<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
 				{$i18n.t('Tools')}
 				{$i18n.t('Tools')}
@@ -206,10 +207,10 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<div class="mb-5">
+	<div class="mb-5 gap-2 grid lg:grid-cols-2 xl:grid-cols-3">
 		{#each filteredItems as tool}
 		{#each filteredItems as tool}
 			<div
 			<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"
+				class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl transition"
 			>
 			>
 				<a
 				<a
 					class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
 					class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
@@ -217,33 +218,47 @@
 				>
 				>
 					<div class="flex items-center text-left">
 					<div class="flex items-center text-left">
 						<div class=" flex-1 self-center pl-1">
 						<div class=" flex-1 self-center pl-1">
-							<div class=" font-semibold flex items-center gap-1.5">
-								<div
-									class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
-								>
-									TOOL
-								</div>
-
-								{#if tool?.meta?.manifest?.version}
+							<Tooltip content={tool?.meta?.description ?? ''} placement="top-start">
+								<div class=" font-semibold flex items-center gap-1.5">
 									<div
 									<div
-										class="text-xs font-bold px-1 rounded line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+										class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
 									>
 									>
-										v{tool?.meta?.manifest?.version ?? ''}
+										TOOL
 									</div>
 									</div>
-								{/if}
 
 
-								<div class="line-clamp-1">
-									{tool.name}
-								</div>
-							</div>
+									{#if tool?.meta?.manifest?.version}
+										<div
+											class="text-xs font-bold px-1 rounded line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+										>
+											v{tool?.meta?.manifest?.version ?? ''}
+										</div>
+									{/if}
+
+									<div class="line-clamp-1">
+										{tool.name}
 
 
-							<div class="flex gap-1.5 px-1">
-								<div class=" text-gray-500 text-xs font-medium flex-shrink-0">{tool.id}</div>
+										<span class=" text-gray-500 text-xs font-medium flex-shrink-0">{tool.id}</span>
+									</div>
+								</div>
+							</Tooltip>
 
 
+							<div class="flex gap-1.5 mb-0.5">
 								<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
 								<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
 									{tool.meta.description}
 									{tool.meta.description}
 								</div>
 								</div>
 							</div>
 							</div>
+
+							<div class="text-xs text-gray-500 shrink-0">
+								<Tooltip
+									content={tool?.user?.email}
+									className="flex shrink-0"
+									placement="top-start"
+								>
+									{$i18n.t('By {{name}}', {
+										name: capitalizeFirstLetter(tool?.user?.name ?? tool?.user?.email)
+									})}
+								</Tooltip>
+							</div>
 						</div>
 						</div>
 					</div>
 					</div>
 				</a>
 				</a>