diff --git a/backend/open_webui/models/feedbacks.py b/backend/open_webui/models/feedbacks.py
index aa6c7bdca..f930739f6 100644
--- a/backend/open_webui/models/feedbacks.py
+++ b/backend/open_webui/models/feedbacks.py
@@ -215,6 +215,13 @@ class FeedbackTable:
query = db.query(Feedback, User).join(User, Feedback.user_id == User.id)
if filter:
+ # Apply model_id filter (exact match)
+ model_id = filter.get('model_id')
+ if model_id:
+ query = query.filter(
+ Feedback.data['model_id'].as_string() == model_id
+ )
+
order_by = filter.get('order_by')
direction = filter.get('direction')
@@ -288,6 +295,17 @@ class FeedbackTable:
.all()
]
+ def get_distinct_model_ids(self, db: Optional[Session] = None) -> list[str]:
+ """Get distinct model_ids from feedback data for filter dropdowns."""
+ with get_db_context(db) as db:
+ rows = (
+ db.query(Feedback.data['model_id'].as_string())
+ .filter(Feedback.data['model_id'].as_string().isnot(None))
+ .distinct()
+ .all()
+ )
+ return sorted([row[0] for row in rows if row[0]])
+
def get_feedbacks_for_leaderboard(self, db: Optional[Session] = None) -> list[LeaderboardFeedbackData]:
"""Fetch only id and data for leaderboard computation (excludes snapshot/meta)."""
with get_db_context(db) as db:
diff --git a/backend/open_webui/routers/evaluations.py b/backend/open_webui/routers/evaluations.py
index de97e172f..a743f8f9f 100644
--- a/backend/open_webui/routers/evaluations.py
+++ b/backend/open_webui/routers/evaluations.py
@@ -291,6 +291,11 @@ async def update_config(
}
+@router.get('/feedbacks/models', response_model=list[str])
+async def get_feedback_model_ids(user=Depends(get_admin_user), db: Session = Depends(get_session)):
+ return Feedbacks.get_distinct_model_ids(db=db)
+
+
@router.get('/feedbacks/all', response_model=list[FeedbackResponse])
async def get_all_feedbacks(user=Depends(get_admin_user), db: Session = Depends(get_session)):
feedbacks = Feedbacks.get_all_feedbacks(db=db)
@@ -309,8 +314,17 @@ async def delete_all_feedbacks(user=Depends(get_admin_user), db: Session = Depen
@router.get('/feedbacks/all/export', response_model=list[FeedbackModel])
-async def export_all_feedbacks(user=Depends(get_admin_user), db: Session = Depends(get_session)):
+async def export_all_feedbacks(
+ model_id: Optional[str] = None,
+ user=Depends(get_admin_user),
+ db: Session = Depends(get_session),
+):
feedbacks = Feedbacks.get_all_feedbacks(db=db)
+ if model_id:
+ feedbacks = [
+ f for f in feedbacks
+ if f.data and f.data.get('model_id') == model_id
+ ]
return feedbacks
@@ -334,6 +348,7 @@ async def get_feedbacks(
order_by: Optional[str] = None,
direction: Optional[str] = None,
page: Optional[int] = 1,
+ model_id: Optional[str] = None,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
):
@@ -347,6 +362,8 @@ async def get_feedbacks(
filter['order_by'] = order_by
if direction:
filter['direction'] = direction
+ if model_id:
+ filter['model_id'] = model_id
result = Feedbacks.get_feedback_items(filter=filter, skip=skip, limit=limit, db=db)
return result
diff --git a/src/lib/apis/evaluations/index.ts b/src/lib/apis/evaluations/index.ts
index a3af6e80b..925329511 100644
--- a/src/lib/apis/evaluations/index.ts
+++ b/src/lib/apis/evaluations/index.ts
@@ -161,13 +161,42 @@ export const getModelHistory = async (token: string = '', modelId: string, days:
return res;
};
-export const getFeedbackItems = async (token: string = '', orderBy, direction, page) => {
+export const getFeedbackModelIds = async (token: string = '') => {
+ let error = null;
+
+ const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedbacks/models`, {
+ 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();
+ })
+ .catch((err) => {
+ error = err.detail;
+ console.error(err);
+ return null;
+ });
+
+ if (error) {
+ throw error;
+ }
+
+ return res;
+};
+
+export const getFeedbackItems = async (token: string = '', orderBy, direction, page, modelId: string = '') => {
let error = null;
const searchParams = new URLSearchParams();
if (orderBy) searchParams.append('order_by', orderBy);
if (direction) searchParams.append('direction', direction);
if (page) searchParams.append('page', page.toString());
+ if (modelId) searchParams.append('model_id', modelId);
const res = await fetch(
`${WEBUI_API_BASE_URL}/evaluations/feedbacks/list?${searchParams.toString()}`,
@@ -200,10 +229,13 @@ export const getFeedbackItems = async (token: string = '', orderBy, direction, p
return res;
};
-export const exportAllFeedbacks = async (token: string = '') => {
+export const exportAllFeedbacks = async (token: string = '', modelId: string = '') => {
let error = null;
- const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedbacks/all/export`, {
+ const searchParams = new URLSearchParams();
+ if (modelId) searchParams.append('model_id', modelId);
+
+ const res = await fetch(`${WEBUI_API_BASE_URL}/evaluations/feedbacks/all/export?${searchParams.toString()}`, {
method: 'GET',
headers: {
Accept: 'application/json',
diff --git a/src/lib/components/admin/Evaluations/Feedbacks.svelte b/src/lib/components/admin/Evaluations/Feedbacks.svelte
index 1dedb94f0..0e47aaba0 100644
--- a/src/lib/components/admin/Evaluations/Feedbacks.svelte
+++ b/src/lib/components/admin/Evaluations/Feedbacks.svelte
@@ -10,7 +10,12 @@
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
- import { deleteFeedbackById, exportAllFeedbacks, getFeedbackItems } from '$lib/apis/evaluations';
+ import {
+ deleteFeedbackById,
+ exportAllFeedbacks,
+ getFeedbackItems,
+ getFeedbackModelIds
+ } from '$lib/apis/evaluations';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Download from '$lib/components/icons/Download.svelte';
@@ -26,6 +31,8 @@
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { config } from '$lib/stores';
import Spinner from '$lib/components/common/Spinner.svelte';
+ import Select from '$lib/components/common/Select.svelte';
+ import Check from '$lib/components/icons/Check.svelte';
let page = 1;
let items = null;
@@ -34,6 +41,9 @@
let orderBy: string = 'updated_at';
let direction: 'asc' | 'desc' = 'desc';
+ let selectedModelId: string = '';
+ let modelIds: string[] = [];
+
const setSortKey = (key) => {
if (orderBy === key) {
direction = direction === 'asc' ? 'desc' : 'asc';
@@ -64,12 +74,16 @@
const getFeedbacks = async () => {
try {
- const res = await getFeedbackItems(localStorage.token, orderBy, direction, page).catch(
- (error) => {
- toast.error(`${error}`);
- return null;
- }
- );
+ const res = await getFeedbackItems(
+ localStorage.token,
+ orderBy,
+ direction,
+ page,
+ selectedModelId
+ ).catch((error) => {
+ toast.error(`${error}`);
+ return null;
+ });
if (res) {
items = res.items;
@@ -80,10 +94,21 @@
}
};
- $: if (orderBy && direction && page) {
+ $: if (orderBy && direction && page !== undefined) {
getFeedbacks();
}
+ const loadModelIds = async () => {
+ try {
+ const res = await getFeedbackModelIds(localStorage.token);
+ if (res) {
+ modelIds = res;
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
const deleteFeedbackHandler = async (feedbackId: string) => {
const response = await deleteFeedbackById(localStorage.token, feedbackId).catch((err) => {
toast.error(err);
@@ -124,10 +149,12 @@
};
const exportHandler = async () => {
- const _feedbacks = await exportAllFeedbacks(localStorage.token).catch((err) => {
- toast.error(err);
- return null;
- });
+ const _feedbacks = await exportAllFeedbacks(localStorage.token, selectedModelId).catch(
+ (err) => {
+ toast.error(err);
+ return null;
+ }
+ );
if (_feedbacks) {
let blob = new Blob([JSON.stringify(_feedbacks)], {
@@ -136,6 +163,10 @@
saveAs(blob, `feedback-history-export-${Date.now()}.json`);
}
};
+
+ onMount(() => {
+ loadModelIds();
+ });