mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-15 04:18:18 +00:00
refac
This commit is contained in:
parent
a9227421d8
commit
774007d728
7 changed files with 60 additions and 60 deletions
|
|
@ -124,8 +124,8 @@ class AppUpdateForm(StrictModel):
|
|||
access_grants: list[dict] | None = None
|
||||
|
||||
|
||||
class AppContextForm(StrictModel):
|
||||
context: str = Field(max_length=100000)
|
||||
class AppTokenForm(StrictModel):
|
||||
token: str = Field(max_length=100000)
|
||||
|
||||
|
||||
class AppConnectionForm(StrictModel):
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ from open_webui.models.apps import (
|
|||
AppAccessResponse,
|
||||
AppChannelMessage,
|
||||
AppConnectionForm,
|
||||
AppContextForm,
|
||||
AppForm,
|
||||
AppInteractionForm,
|
||||
AppManifestForm,
|
||||
AppResponse,
|
||||
Apps,
|
||||
AppTokenForm,
|
||||
AppUpdateForm,
|
||||
Manifest,
|
||||
)
|
||||
|
|
@ -30,7 +30,7 @@ from open_webui.utils.apps import (
|
|||
check_app_invocation_access,
|
||||
create_app_message,
|
||||
decode_app_context_token,
|
||||
generate_app_response,
|
||||
generate_app_channel_reply,
|
||||
get_app_by_id,
|
||||
get_app_encryption_cipher,
|
||||
get_app_message_data,
|
||||
|
|
@ -187,9 +187,9 @@ async def get_app_channels(app=Depends(get_verified_app), db: AsyncSession = Dep
|
|||
|
||||
@router.post('/runtime/authorize')
|
||||
async def authorize_app_execution(
|
||||
form_data: AppContextForm, app=Depends(get_verified_app), db: AsyncSession = Depends(get_async_session)
|
||||
form_data: AppTokenForm, app=Depends(get_verified_app), db: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
value = decode_app_context_token(app, 'execution', form_data.context)
|
||||
value = decode_app_context_token(app, 'execution', form_data.token)
|
||||
user = await Users.get_user_by_id(value['actor_id'], db=db)
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Actor is unavailable')
|
||||
|
|
@ -212,9 +212,9 @@ async def authorize_app_execution(
|
|||
|
||||
@router.post('/runtime/events/verify')
|
||||
async def verify_app_event(
|
||||
form_data: AppContextForm, app=Depends(get_verified_app), db: AsyncSession = Depends(get_async_session)
|
||||
form_data: AppTokenForm, app=Depends(get_verified_app), db: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
value = decode_app_context_token(app, 'event', form_data.context)
|
||||
value = decode_app_context_token(app, 'event', form_data.token)
|
||||
await check_app_channel_membership(app, value['channel']['id'], db=db)
|
||||
require_scope(app, 'messages:read')
|
||||
return {k: v for k, v in value.items() if k not in ('purpose', 'exp', 'nonce')}
|
||||
|
|
@ -450,7 +450,7 @@ async def create_app_interaction(
|
|||
action = next((item for item in actions if item['action_id'] == form_data.action_id), None)
|
||||
if not action:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Unknown action')
|
||||
return await generate_app_response(
|
||||
return await generate_app_channel_reply(
|
||||
app,
|
||||
user,
|
||||
message.channel_id,
|
||||
|
|
@ -479,11 +479,11 @@ async def get_app_operation(
|
|||
if value['actor_id'] != user.id or value['operation_id'] != operation_id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Invalid operation receipt')
|
||||
await check_app_invocation_access(app, value['channel_id'], user, db=db)
|
||||
return await generate_app_response(
|
||||
return await generate_app_channel_reply(
|
||||
app,
|
||||
user,
|
||||
value['channel_id'],
|
||||
'operation_status',
|
||||
'channel.operation.status',
|
||||
{'operation_id': operation_id},
|
||||
operation_id=operation_id,
|
||||
db=db,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ from open_webui.tasks import create_task
|
|||
from open_webui.utils.access_control import filter_allowed_access_grants, has_permission
|
||||
from open_webui.utils.apps import (
|
||||
check_app_channel_access,
|
||||
generate_app_response,
|
||||
generate_app_channel_reply,
|
||||
get_app_by_id,
|
||||
get_app_response,
|
||||
has_app_access,
|
||||
|
|
@ -1090,7 +1090,7 @@ async def send_notification(request, channel, message, active_user_ids, db=None)
|
|||
return True
|
||||
|
||||
|
||||
async def app_response_handler(channel_id, message_id, user_id):
|
||||
async def app_channel_reply_handler(channel_id, message_id, user_id):
|
||||
"""Committed human messages only; errors never cause the human POST to retry."""
|
||||
if not await Config.get('apps.enable'):
|
||||
return
|
||||
|
|
@ -1110,7 +1110,7 @@ async def app_response_handler(channel_id, message_id, user_id):
|
|||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
if not user:
|
||||
continue
|
||||
value = await generate_app_response(
|
||||
value = await generate_app_channel_reply(
|
||||
app,
|
||||
user,
|
||||
channel_id,
|
||||
|
|
@ -1135,7 +1135,7 @@ async def app_response_handler(channel_id, message_id, user_id):
|
|||
'channel_id': channel_id,
|
||||
'message_id': message_id,
|
||||
'parent_id': parent_id,
|
||||
'response': value,
|
||||
'reply': value,
|
||||
},
|
||||
[user_id],
|
||||
)
|
||||
|
|
@ -1409,7 +1409,7 @@ async def post_new_message(
|
|||
# Background tasks should manage their own short-lived sessions to avoid
|
||||
# holding database connections during slow operations (e.g., LLM calls).
|
||||
async def background_handler():
|
||||
await create_task(None, app_response_handler(channel.id, message.id, user.id))
|
||||
await create_task(None, app_channel_reply_handler(channel.id, message.id, user.id))
|
||||
await model_response_handler(request, channel, message, user)
|
||||
await send_notification(
|
||||
request,
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ async def send_app_message_event(message, kind, created_at):
|
|||
'webui_url': app.data['webui_url'],
|
||||
'channel': {'id': message['channel_id']},
|
||||
}
|
||||
envelope['event_context'] = create_app_context_token(app, 'event', envelope, expires_in=300)
|
||||
envelope['event_token'] = create_app_context_token(app, 'event', envelope, expires_in=300)
|
||||
status, _ = await send_app_event(app, envelope, timeout=10)
|
||||
if not 200 <= status < 300:
|
||||
log.warning('App %s did not acknowledge %s: HTTP %s', app_id, kind, status)
|
||||
|
|
@ -452,13 +452,13 @@ async def update_app_message(db, app, message_id, form, if_match):
|
|||
return value
|
||||
|
||||
|
||||
async def generate_app_response(
|
||||
async def generate_app_channel_reply(
|
||||
app, user, channel_id, event_type, data, *, db: AsyncSession, source=None, operation_id=None, scope=None
|
||||
):
|
||||
await check_app_invocation_access(app, channel_id, user, db=db)
|
||||
require_scope(app, scope)
|
||||
operation_id = operation_id or str(uuid.uuid4())
|
||||
context = create_app_context_token(
|
||||
token = create_app_context_token(
|
||||
app,
|
||||
'execution',
|
||||
{
|
||||
|
|
@ -477,7 +477,7 @@ async def generate_app_response(
|
|||
'actor': {'id': user.id, 'is_admin': user.role == 'admin'},
|
||||
'channel': {'id': channel_id},
|
||||
'data': data,
|
||||
'execution_context': context,
|
||||
'execution_token': token,
|
||||
'source': source,
|
||||
}
|
||||
checked_key = (app.api_key, app.data['manifest']['public_key'])
|
||||
|
|
@ -500,7 +500,7 @@ async def generate_app_response(
|
|||
except Exception:
|
||||
raise HTTPException(502, 'App returned an invalid response') from None
|
||||
await check_app_invocation_access(app, channel_id, user, db=db)
|
||||
if event_type == 'operation_status' and (reply.visibility != 'private' or reply.type == 'update'):
|
||||
if event_type == 'channel.operation.status' and (reply.visibility != 'private' or reply.type == 'update'):
|
||||
raise HTTPException(502, 'Deferred results must be private; apps publish separately through the message API')
|
||||
if reply.actions and (reply.visibility != 'channel' or reply.type not in ('message', 'update')):
|
||||
raise HTTPException(502, 'Buttons require a channel message')
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import Markdown from '$lib/components/chat/Messages/Markdown.svelte';
|
||||
export let appId: string;
|
||||
export let channelId: string;
|
||||
export let response: any;
|
||||
export let reply: any;
|
||||
export let source: any = null;
|
||||
const dispatch = createEventDispatcher();
|
||||
let busy = false,
|
||||
|
|
@ -22,12 +22,12 @@
|
|||
action_id: actionId
|
||||
});
|
||||
if (result.visibility !== 'channel' || result.type === 'error') {
|
||||
dispatch('response', {
|
||||
dispatch('reply', {
|
||||
app_id: appId,
|
||||
app_name: source.user?.name,
|
||||
channel_id: channelId,
|
||||
message_id: source.id,
|
||||
response: result
|
||||
reply: result
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -37,17 +37,17 @@
|
|||
}
|
||||
}
|
||||
async function poll() {
|
||||
if (disposed || response?.type !== 'deferred') return;
|
||||
if (disposed || reply?.type !== 'deferred') return;
|
||||
if (!document.hidden) {
|
||||
try {
|
||||
const result = await getAppOperationById(
|
||||
localStorage.token,
|
||||
appId,
|
||||
response.operation_id,
|
||||
response.receipt
|
||||
reply.operation_id,
|
||||
reply.receipt
|
||||
);
|
||||
if (disposed) return;
|
||||
response = result;
|
||||
reply = result;
|
||||
} catch (e) {
|
||||
error = String(e);
|
||||
return;
|
||||
|
|
@ -58,16 +58,16 @@
|
|||
}
|
||||
function download() {
|
||||
const url = URL.createObjectURL(
|
||||
new Blob([response.content], { type: 'text/markdown;charset=utf-8' })
|
||||
new Blob([reply.content], { type: 'text/markdown;charset=utf-8' })
|
||||
);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = response.filename;
|
||||
link.download = reply.filename;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
onMount(() => {
|
||||
if (response?.type === 'deferred') timer = setTimeout(poll, 1000);
|
||||
if (reply?.type === 'deferred') timer = setTimeout(poll, 1000);
|
||||
});
|
||||
onDestroy(() => {
|
||||
disposed = true;
|
||||
|
|
@ -76,20 +76,20 @@
|
|||
</script>
|
||||
|
||||
<div class="space-y-3 text-sm" aria-busy={busy}>
|
||||
{#if response?.title}<h3 class="font-medium">{response.title}</h3>{/if}
|
||||
{#if response?.content}<Markdown content={response.content} allowEmbeds={false} />{/if}
|
||||
{#if response?.type === 'error'}<p role="alert">
|
||||
{#if reply?.title}<h3 class="font-medium">{reply.title}</h3>{/if}
|
||||
{#if reply?.content}<Markdown content={reply.content} allowEmbeds={false} />{/if}
|
||||
{#if reply?.type === 'error'}<p role="alert">
|
||||
The app could not complete this operation.
|
||||
</p>{/if}
|
||||
{#if error}<p role="alert" class="text-red-600">{error}</p>{/if}
|
||||
{#if response?.type === 'deferred'}<p role="status" class="text-gray-500">Working…</p>{/if}
|
||||
{#if response?.type === 'download'}<button
|
||||
{#if reply?.type === 'deferred'}<p role="status" class="text-gray-500">Working…</p>{/if}
|
||||
{#if reply?.type === 'download'}<button
|
||||
class="focus-ring flex h-7 shrink-0 items-center justify-center gap-1.5 rounded-lg bg-gray-900 px-2.5 text-xs font-normal text-white transition hover:bg-black disabled:opacity-60 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-white"
|
||||
on:click={download}>Download {response.filename}</button
|
||||
on:click={download}>Download {reply.filename}</button
|
||||
>{/if}
|
||||
{#if response?.actions?.length}
|
||||
{#if reply?.actions?.length}
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
{#each response.actions as item, index}
|
||||
{#each reply.actions as item, index}
|
||||
<button
|
||||
type="button"
|
||||
disabled={busy}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
import { settings, socket, user } from '$lib/stores';
|
||||
|
||||
import Message from './Messages/Message.svelte';
|
||||
import AppResponse from './AppResponse.svelte';
|
||||
import AppChannelReply from './AppChannelReply.svelte';
|
||||
import Loader from '../common/Loader.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import {
|
||||
|
|
@ -41,24 +41,24 @@
|
|||
export let onPin: Function = () => {};
|
||||
|
||||
let messagesLoading = false;
|
||||
let appResponses: Record<string, any> = {};
|
||||
let responseLocation = '';
|
||||
$: if (responseLocation !== `${channel?.id}:${id}`) {
|
||||
responseLocation = `${channel?.id}:${id}`;
|
||||
appResponses = {};
|
||||
let appReplies: Record<string, any> = {};
|
||||
let replyLocation = '';
|
||||
$: if (replyLocation !== `${channel?.id}:${id}`) {
|
||||
replyLocation = `${channel?.id}:${id}`;
|
||||
appReplies = {};
|
||||
}
|
||||
|
||||
const receiveAppResponse = (event) => {
|
||||
const receiveAppChannelReply = (event) => {
|
||||
if (event.channel_id !== channel?.id || (event.parent_id ?? null) !== (thread ? id : null))
|
||||
return;
|
||||
const key = `${event.message_id}:${event.app_id}`;
|
||||
appResponses = { ...appResponses, [key]: event };
|
||||
appReplies = { ...appReplies, [key]: event };
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const appSocket = $socket;
|
||||
appSocket?.on('events:app', receiveAppResponse);
|
||||
return () => appSocket?.off('events:app', receiveAppResponse);
|
||||
appSocket?.on('events:app', receiveAppChannelReply);
|
||||
return () => appSocket?.off('events:app', receiveAppChannelReply);
|
||||
});
|
||||
|
||||
const loadMoreMessages = async () => {
|
||||
|
|
@ -148,8 +148,8 @@
|
|||
|
||||
{#each messageList as message, messageIdx (id ? `${id}-${message.id}` : message.id)}
|
||||
<Message
|
||||
on:response={(event) =>
|
||||
receiveAppResponse({ ...event.detail, parent_id: thread ? id : null })}
|
||||
on:reply={(event) =>
|
||||
receiveAppChannelReply({ ...event.detail, parent_id: thread ? id : null })}
|
||||
{message}
|
||||
{channel}
|
||||
{thread}
|
||||
|
|
@ -278,10 +278,10 @@
|
|||
}
|
||||
}}
|
||||
/>
|
||||
{#each Object.entries(appResponses).filter(([, event]) => event.message_id === message.id) as [key, event] (key)}
|
||||
{#each Object.entries(appReplies).filter(([, event]) => event.message_id === message.id) as [key, event] (key)}
|
||||
<div
|
||||
class="flex gap-3 px-4 pt-3 pb-2"
|
||||
data-app-response={event.app_id}
|
||||
data-app-reply={event.app_id}
|
||||
dir={$settings.chatDirection}
|
||||
>
|
||||
<span
|
||||
|
|
@ -296,11 +296,11 @@
|
|||
<span class="text-[0.65rem] text-gray-500">{$i18n.t('App')}</span>
|
||||
</div>
|
||||
<div class="[&_.markdown-prose]:text-sm! [&_.markdown-prose]:leading-[1.375rem]!">
|
||||
{#key event.response}
|
||||
<AppResponse
|
||||
{#key event.reply}
|
||||
<AppChannelReply
|
||||
appId={event.app_id}
|
||||
channelId={channel.id}
|
||||
response={event.response}
|
||||
reply={event.reply}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
|
|
@ -311,8 +311,8 @@
|
|||
type="button"
|
||||
class="focus-ring rounded hover:text-gray-800 hover:underline dark:hover:text-gray-200"
|
||||
on:click={() => {
|
||||
delete appResponses[key];
|
||||
appResponses = { ...appResponses };
|
||||
delete appReplies[key];
|
||||
appReplies = { ...appReplies };
|
||||
}}>{$i18n.t('Dismiss')}</button
|
||||
>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { getMessageData } from '$lib/apis/channels';
|
||||
|
||||
import AppResponse from '../AppResponse.svelte';
|
||||
import AppChannelReply from '../AppChannelReply.svelte';
|
||||
import Markdown from '$lib/components/chat/Messages/Markdown.svelte';
|
||||
import StructuredOutputRenderer from '$lib/components/chat/Messages/StructuredOutputRenderer.svelte';
|
||||
import { buildOutputDisplayItems } from '$lib/components/chat/Messages/structuredOutput';
|
||||
|
|
@ -589,7 +589,7 @@
|
|||
|
||||
{#if message.app_id && message?.data?.app}
|
||||
<div class="mt-2">
|
||||
<AppResponse on:response appId={message.app_id} channelId={channel.id} source={message} response={{ actions: message.data.app.actions }} />
|
||||
<AppChannelReply on:reply appId={message.app_id} channelId={channel.id} source={message} reply={{ actions: message.data.app.actions }} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue