enh: tools user info

This commit is contained in:
Timothy Jaeryang Baek 2024-11-18 06:19:34 -08:00
parent 43ffd61aeb
commit c50b678dce
8 changed files with 113 additions and 49 deletions

View File

@ -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 [

View File

@ -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 [

View File

@ -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()

View File

@ -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()

View File

@ -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"

View File

@ -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}}', {

View File

@ -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=" flex-1 flex items-center gap-2 self-center">
<div class=" font-semibold line-clamp-1">{prompt.command}</div> <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">

View File

@ -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"> <Tooltip content={tool?.meta?.description ?? ''} placement="top-start">
<div <div class=" font-semibold flex items-center gap-1.5">
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}
<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"> {#if tool?.meta?.manifest?.version}
{tool.name} <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}
<span class=" text-gray-500 text-xs font-medium flex-shrink-0">{tool.id}</span>
</div>
</div> </div>
</div> </Tooltip>
<div class="flex gap-1.5 px-1">
<div class=" text-gray-500 text-xs font-medium flex-shrink-0">{tool.id}</div>
<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>