mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-10 03:27:57 +00:00
refac
This commit is contained in:
parent
16849284ff
commit
27b48b70df
8 changed files with 104 additions and 7 deletions
|
|
@ -4,8 +4,9 @@ import time
|
|||
import uuid
|
||||
|
||||
from open_webui.internal.db import Base, get_async_db_context
|
||||
from fastapi import HTTPException
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from sqlalchemy import JSON, BigInteger, Column, Text, select
|
||||
from sqlalchemy import JSON, BigInteger, Column, Text, select, update
|
||||
|
||||
|
||||
def model_snapshot(model) -> dict:
|
||||
|
|
@ -51,6 +52,35 @@ class ModelHistoryModel(ModelHistoryResponse):
|
|||
|
||||
|
||||
class ModelHistoryTable:
|
||||
async def delete_history_entry(self, model_id, history_id, db=None):
|
||||
from open_webui.models.models import Model
|
||||
|
||||
async with get_async_db_context(db) as session:
|
||||
try:
|
||||
# Serialize with production switches on both SQLite and PostgreSQL.
|
||||
await session.execute(update(Model).where(Model.id == model_id).values(version_id=Model.version_id))
|
||||
model = await session.get(Model, model_id, populate_existing=True)
|
||||
if not model:
|
||||
return False
|
||||
if model.version_id == history_id:
|
||||
raise HTTPException(400, 'Cannot delete the current version')
|
||||
entry = (
|
||||
await session.execute(select(ModelHistory).filter_by(id=history_id, model_id=model_id))
|
||||
).scalar_one_or_none()
|
||||
if not entry:
|
||||
return False
|
||||
await session.execute(
|
||||
update(ModelHistory)
|
||||
.where(ModelHistory.model_id == model_id, ModelHistory.parent_id == history_id)
|
||||
.values(parent_id=entry.parent_id)
|
||||
)
|
||||
await session.delete(entry)
|
||||
await session.commit()
|
||||
return True
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
def new_entry(self, model_id, snapshot, user_id, parent_id=None, commit_message=None):
|
||||
return ModelHistory(
|
||||
id=str(uuid.uuid4()),
|
||||
|
|
|
|||
|
|
@ -281,6 +281,10 @@ class ModelsTable:
|
|||
)
|
||||
session.add_all([current, entry])
|
||||
else:
|
||||
if production_version_id is not None:
|
||||
# Serialize with history deletion before reading the selected snapshot.
|
||||
await session.execute(update(Model).where(Model.id == current.id).values(version_id=Model.version_id))
|
||||
await session.refresh(current)
|
||||
values = {key: value for key, value in data.items() if key != 'id'}
|
||||
# Omitted operational state must not reset a disabled model.
|
||||
if 'is_active' not in form.model_fields_set:
|
||||
|
|
|
|||
|
|
@ -901,6 +901,19 @@ async def get_model_history_entry(
|
|||
return entry
|
||||
|
||||
|
||||
@router.delete('/model/history/{history_id}', response_model=bool)
|
||||
async def delete_model_history_entry(
|
||||
id: str,
|
||||
history_id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
await authorized_model_history(id, user, db)
|
||||
if not await ModelHistories.delete_history_entry(id, history_id, db):
|
||||
raise HTTPException(404, 'Model version not found')
|
||||
return True
|
||||
|
||||
|
||||
async def _verify_version_dependencies(request, form, user, db):
|
||||
from open_webui.models.functions import Functions
|
||||
from open_webui.models.knowledge import Knowledges
|
||||
|
|
|
|||
|
|
@ -515,6 +515,18 @@ export const getModelHistoryEntry = async (
|
|||
return response.json();
|
||||
};
|
||||
|
||||
export const deleteModelHistoryVersion = async (token: string, id: string, versionId: string) => {
|
||||
const response = await fetch(
|
||||
`${WEBUI_API_BASE_URL}/models/model/history/${encodeURIComponent(versionId)}?${new URLSearchParams({ id })}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: { authorization: `Bearer ${token}` }
|
||||
}
|
||||
);
|
||||
if (!response.ok) throw await response.json();
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const setProductionModelVersion = async (token: string, id: string, versionId: string) => {
|
||||
const response = await fetch(
|
||||
`${WEBUI_API_BASE_URL}/models/model/update/version?${new URLSearchParams({ id })}`,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import VersionMenuItem from '../common/VersionMenuItem.svelte';
|
||||
import {
|
||||
getModelHistory,
|
||||
deleteModelHistoryVersion,
|
||||
getModelHistoryEntry,
|
||||
setProductionModelVersion,
|
||||
type ModelHistoryEntry,
|
||||
|
|
@ -29,6 +30,9 @@
|
|||
export let promoting = false;
|
||||
let error = '';
|
||||
let confirmPromotion = false;
|
||||
let showDeleteVersion = false;
|
||||
let deleteVersionId = '';
|
||||
let deleting = false;
|
||||
let selection = 0;
|
||||
const message = (error: any) =>
|
||||
typeof error?.detail === 'string'
|
||||
|
|
@ -71,13 +75,32 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function deleteVersion() {
|
||||
if (deleting || promoting || !deleteVersionId || deleteVersionId === model.version_id) return;
|
||||
deleting = true;
|
||||
try {
|
||||
await deleteModelHistoryVersion(localStorage.token, model.id, deleteVersionId);
|
||||
// Invalidate an in-flight preview response for the deleted version.
|
||||
selection++;
|
||||
selecting = false;
|
||||
if (selected?.id === deleteVersionId) selected = null;
|
||||
page = 1;
|
||||
await loadHistory();
|
||||
toast.success($i18n.t('Version deleted'));
|
||||
} catch (e) {
|
||||
toast.error(message(e));
|
||||
} finally {
|
||||
deleting = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function requestPromotion() {
|
||||
if (dirty) confirmPromotion = true;
|
||||
else promote();
|
||||
}
|
||||
|
||||
async function promote() {
|
||||
if (!selected || promoting) return;
|
||||
if (!selected || promoting || deleting) return;
|
||||
promoting = true;
|
||||
try {
|
||||
const result = await setProductionModelVersion(localStorage.token, model.id, selected.id);
|
||||
|
|
@ -102,13 +125,23 @@
|
|||
on:confirm={promote}
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:show={showDeleteVersion}
|
||||
title={$i18n.t('Delete Version')}
|
||||
message={$i18n.t(
|
||||
"Are you sure you want to delete this version? Child versions will be relinked to this version's parent."
|
||||
)}
|
||||
confirmLabel={$i18n.t('Delete')}
|
||||
onConfirm={deleteVersion}
|
||||
/>
|
||||
|
||||
<div class="flex shrink-0 items-center">
|
||||
<Dropdown bind:show align="start">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={$i18n.t('Select version')}
|
||||
class="flex min-w-0 items-center gap-1.5 text-xs text-gray-500 hover:text-gray-900 dark:hover:text-gray-100"
|
||||
disabled={promoting}
|
||||
disabled={promoting || deleting}
|
||||
on:click={() => {
|
||||
page = 1;
|
||||
loadHistory();
|
||||
|
|
@ -138,6 +171,11 @@
|
|||
{entry}
|
||||
selected={entry.id === selected?.id}
|
||||
onSelect={() => selectVersion(entry.id)}
|
||||
onDelete={() => {
|
||||
deleteVersionId = entry.id;
|
||||
show = false;
|
||||
showDeleteVersion = true;
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{#if page > 1 || history.length === 20}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@
|
|||
bind:this={inputElement}
|
||||
bind:value
|
||||
id={`${id}-input`}
|
||||
class="{className} text-sm bg-transparent outline-hidden"
|
||||
class="{className} block h-5 py-0 text-xs leading-5 bg-transparent outline-hidden"
|
||||
type="text"
|
||||
{placeholder}
|
||||
role="combobox"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
|
||||
<select
|
||||
class="w-full text-sm bg-transparent outline-hidden cursor-pointer"
|
||||
class="block h-5 w-full py-0 text-xs leading-5 bg-transparent outline-hidden cursor-pointer"
|
||||
bind:value={terminalId}
|
||||
>
|
||||
<option value="">{$i18n.t('None')}</option>
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@
|
|||
|
||||
<div class="my-3 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">{$i18n.t('Voice avatar')}</div>
|
||||
<div class="text-sm mt-0.5">{$i18n.t(value ? 'Custom avatar' : 'Default orb')}</div>
|
||||
<div class="mb-1 text-xs text-gray-500">{$i18n.t('Voice avatar')}</div>
|
||||
<div class="text-xs leading-5">{$i18n.t(value ? 'Custom avatar' : 'Default orb')}</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue