mirror of
https://github.com/open-webui/open-webui
synced 2025-05-16 19:43:42 +00:00
enh: feedback exports
This commit is contained in:
parent
58d929fd65
commit
8b61b39c75
@ -5,6 +5,7 @@ from pydantic import BaseModel
|
|||||||
from open_webui.apps.webui.models.users import Users, UserModel
|
from open_webui.apps.webui.models.users import Users, UserModel
|
||||||
from open_webui.apps.webui.models.feedbacks import (
|
from open_webui.apps.webui.models.feedbacks import (
|
||||||
FeedbackModel,
|
FeedbackModel,
|
||||||
|
FeedbackResponse,
|
||||||
FeedbackForm,
|
FeedbackForm,
|
||||||
Feedbacks,
|
Feedbacks,
|
||||||
)
|
)
|
||||||
@ -55,27 +56,15 @@ async def update_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/feedbacks", response_model=list[FeedbackModel])
|
class FeedbackUserResponse(FeedbackResponse):
|
||||||
async def get_feedbacks(user=Depends(get_verified_user)):
|
|
||||||
feedbacks = Feedbacks.get_feedbacks_by_user_id(user.id)
|
|
||||||
return feedbacks
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/feedbacks", response_model=bool)
|
|
||||||
async def delete_feedbacks(user=Depends(get_verified_user)):
|
|
||||||
success = Feedbacks.delete_feedbacks_by_user_id(user.id)
|
|
||||||
return success
|
|
||||||
|
|
||||||
|
|
||||||
class FeedbackUserModel(FeedbackModel):
|
|
||||||
user: Optional[UserModel] = None
|
user: Optional[UserModel] = None
|
||||||
|
|
||||||
|
|
||||||
@router.get("/feedbacks/all", response_model=list[FeedbackUserModel])
|
@router.get("/feedbacks/all", response_model=list[FeedbackUserResponse])
|
||||||
async def get_all_feedbacks(user=Depends(get_admin_user)):
|
async def get_all_feedbacks(user=Depends(get_admin_user)):
|
||||||
feedbacks = Feedbacks.get_all_feedbacks()
|
feedbacks = Feedbacks.get_all_feedbacks()
|
||||||
return [
|
return [
|
||||||
FeedbackUserModel(
|
FeedbackUserResponse(
|
||||||
**feedback.model_dump(), user=Users.get_user_by_id(feedback.user_id)
|
**feedback.model_dump(), user=Users.get_user_by_id(feedback.user_id)
|
||||||
)
|
)
|
||||||
for feedback in feedbacks
|
for feedback in feedbacks
|
||||||
@ -88,6 +77,29 @@ async def delete_all_feedbacks(user=Depends(get_admin_user)):
|
|||||||
return success
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/feedbacks/all/export", response_model=list[FeedbackModel])
|
||||||
|
async def get_all_feedbacks(user=Depends(get_admin_user)):
|
||||||
|
feedbacks = Feedbacks.get_all_feedbacks()
|
||||||
|
return [
|
||||||
|
FeedbackModel(
|
||||||
|
**feedback.model_dump(), user=Users.get_user_by_id(feedback.user_id)
|
||||||
|
)
|
||||||
|
for feedback in feedbacks
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/feedbacks/user", response_model=list[FeedbackUserResponse])
|
||||||
|
async def get_feedbacks(user=Depends(get_verified_user)):
|
||||||
|
feedbacks = Feedbacks.get_feedbacks_by_user_id(user.id)
|
||||||
|
return feedbacks
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/feedbacks", response_model=bool)
|
||||||
|
async def delete_feedbacks(user=Depends(get_verified_user)):
|
||||||
|
success = Feedbacks.delete_feedbacks_by_user_id(user.id)
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
@router.post("/feedback", response_model=FeedbackModel)
|
@router.post("/feedback", response_model=FeedbackModel)
|
||||||
async def create_feedback(
|
async def create_feedback(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
@ -93,6 +93,37 @@ export const getAllFeedbacks = async (token: string = '') => {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exportAllFeedbacks = async (token: string = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedbacks/all/export`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then((json) => {
|
||||||
|
return json;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
error = err.detail;
|
||||||
|
console.log(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const createNewFeedback = async (token: string, feedback: object) => {
|
export const createNewFeedback = async (token: string, feedback: object) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import fileSaver from 'file-saver';
|
||||||
|
const { saveAs } = fileSaver;
|
||||||
|
|
||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext } from 'svelte';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
@ -12,7 +15,7 @@
|
|||||||
let model = null;
|
let model = null;
|
||||||
|
|
||||||
import { models } from '$lib/stores';
|
import { models } from '$lib/stores';
|
||||||
import { deleteFeedbackById, getAllFeedbacks } from '$lib/apis/evaluations';
|
import { deleteFeedbackById, exportAllFeedbacks, getAllFeedbacks } from '$lib/apis/evaluations';
|
||||||
|
|
||||||
import FeedbackMenu from './Evaluations/FeedbackMenu.svelte';
|
import FeedbackMenu from './Evaluations/FeedbackMenu.svelte';
|
||||||
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
|
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
|
||||||
@ -24,6 +27,9 @@
|
|||||||
import CloudArrowUp from '../icons/CloudArrowUp.svelte';
|
import CloudArrowUp from '../icons/CloudArrowUp.svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import Spinner from '../common/Spinner.svelte';
|
import Spinner from '../common/Spinner.svelte';
|
||||||
|
import DocumentArrowUpSolid from '../icons/DocumentArrowUpSolid.svelte';
|
||||||
|
import DocumentArrowDown from '../icons/DocumentArrowDown.svelte';
|
||||||
|
import ArrowDownTray from '../icons/ArrowDownTray.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
@ -300,6 +306,20 @@
|
|||||||
window.addEventListener('message', messageHandler, false);
|
window.addEventListener('message', messageHandler, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportHandler = async () => {
|
||||||
|
const _feedbacks = await exportAllFeedbacks(localStorage.token).catch((err) => {
|
||||||
|
toast.error(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_feedbacks) {
|
||||||
|
let blob = new Blob([JSON.stringify(_feedbacks)], {
|
||||||
|
type: 'application/json'
|
||||||
|
});
|
||||||
|
saveAs(blob, `feedback-history-export-${Date.now()}.json`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadEmbeddingModel = async () => {
|
const loadEmbeddingModel = async () => {
|
||||||
// Check if the tokenizer and model are already loaded and stored in the window object
|
// Check if the tokenizer and model are already loaded and stored in the window object
|
||||||
if (!window.tokenizer) {
|
if (!window.tokenizer) {
|
||||||
@ -483,6 +503,21 @@
|
|||||||
|
|
||||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{feedbacks.length}</span>
|
<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{feedbacks.length}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Tooltip content={$i18n.t('Export')}>
|
||||||
|
<button
|
||||||
|
class=" p-2 rounded-xl hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition font-medium text-sm flex items-center space-x-1"
|
||||||
|
on:click={() => {
|
||||||
|
exportHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowDownTray className="size-3" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -626,18 +661,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" self-center">
|
<div class=" self-center">
|
||||||
<svg
|
<CloudArrowUp className="size-3" strokeWidth="3" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-3.5 h-3.5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
19
src/lib/components/icons/DocumentArrowDown.svelte
Normal file
19
src/lib/components/icons/DocumentArrowDown.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let className = 'size-4';
|
||||||
|
export let strokeWidth = '1.5';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
stroke="currentColor"
|
||||||
|
class={className}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m.75 12 3 3m0 0 3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
Loading…
Reference in New Issue
Block a user