mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-14 04:08:46 +00:00
refac
This commit is contained in:
parent
200b951491
commit
a94a2068e2
5 changed files with 117 additions and 24 deletions
|
|
@ -52,10 +52,10 @@ 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_channel_reply,
|
||||
get_app_by_id,
|
||||
get_app_response,
|
||||
has_app_access,
|
||||
send_app_mention,
|
||||
)
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.channels import extract_mentions, replace_mentions
|
||||
|
|
@ -1110,11 +1110,10 @@ async def app_channel_reply_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_channel_reply(
|
||||
await send_app_mention(
|
||||
app,
|
||||
user,
|
||||
channel_id,
|
||||
'channel.mention',
|
||||
{'text': text, 'message_id': message_id, 'parent_id': parent_id},
|
||||
operation_id=message_id,
|
||||
db=db,
|
||||
|
|
@ -1124,11 +1123,15 @@ async def app_channel_reply_handler(channel_id, message_id, user_id):
|
|||
)
|
||||
except Exception as exc:
|
||||
log.warning('App mention delivery failed: %s', type(exc).__name__)
|
||||
value = {
|
||||
'type': 'error',
|
||||
'content': 'App could not respond. Check access and connection status before retrying.',
|
||||
}
|
||||
if value.get('type') != 'ack' and value.get('visibility') != 'channel':
|
||||
status_code = getattr(exc, 'status_code', None)
|
||||
if status_code == 504:
|
||||
detail = 'App delivery was not confirmed. It may have accepted the message; check the channel before retrying.'
|
||||
elif status_code in (401, 403):
|
||||
detail = 'App access was denied. Check permissions before retrying.'
|
||||
elif status_code == 503:
|
||||
detail = 'App is unavailable or busy. The message was not accepted.'
|
||||
else:
|
||||
detail = 'App delivery failed. Check its connection and server logs before retrying.'
|
||||
await emit_to_users(
|
||||
'events:app',
|
||||
{
|
||||
|
|
@ -1137,7 +1140,7 @@ async def app_channel_reply_handler(channel_id, message_id, user_id):
|
|||
'channel_id': channel_id,
|
||||
'message_id': message_id,
|
||||
'parent_id': parent_id,
|
||||
'reply': value,
|
||||
'reply': {'type': 'error', 'content': detail},
|
||||
},
|
||||
[user_id],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -460,8 +460,8 @@ async def update_app_message(db, app, message_id, form, if_match):
|
|||
return value
|
||||
|
||||
|
||||
async def generate_app_channel_reply(
|
||||
app, user, channel_id, event_type, data, *, db: AsyncSession, source=None, operation_id=None, scope=None
|
||||
async def send_app_invocation(
|
||||
app, user, channel_id, event_type, data, *, db: AsyncSession, source=None, operation_id=None, scope=None, timeout=3
|
||||
):
|
||||
await check_app_invocation_access(app, channel_id, user, db=db)
|
||||
require_scope(app, scope)
|
||||
|
|
@ -488,11 +488,10 @@ async def generate_app_channel_reply(
|
|||
'execution_token': token,
|
||||
'source': source,
|
||||
}
|
||||
checked_key = (app.api_key, app.data['manifest']['public_key'])
|
||||
# Release the read transaction before HTTP; recheck access after the response.
|
||||
# Release the read transaction before socket delivery.
|
||||
await db.commit()
|
||||
try:
|
||||
status, raw = await send_app_event(app, envelope)
|
||||
status, raw = await send_app_event(app, envelope, timeout=timeout)
|
||||
except HTTPException as exc:
|
||||
if exc.status_code == 504:
|
||||
raise HTTPException(504, 'App response timed out; execution outcome may be unknown') from None
|
||||
|
|
@ -509,7 +508,41 @@ async def generate_app_channel_reply(
|
|||
503: 'App is busy or shutting down. The event was not accepted.',
|
||||
504: 'App processing timed out; execution outcome may be unknown.',
|
||||
}.get(status, f'App returned HTTP {status}. Check the app server logs.')
|
||||
raise HTTPException(504 if status == 504 else 502, detail)
|
||||
raise HTTPException(status if status in (401, 403, 503, 504) else 502, detail)
|
||||
return raw
|
||||
|
||||
|
||||
async def send_app_mention(app, user, channel_id, data, *, operation_id, scope, db):
|
||||
receipt = await send_app_invocation(
|
||||
app,
|
||||
user,
|
||||
channel_id,
|
||||
'channel.mention',
|
||||
data,
|
||||
operation_id=operation_id,
|
||||
scope=scope,
|
||||
db=db,
|
||||
timeout=10,
|
||||
)
|
||||
if receipt != {'received': True}:
|
||||
raise HTTPException(502, 'App returned an invalid delivery receipt')
|
||||
|
||||
|
||||
async def generate_app_channel_reply(
|
||||
app, user, channel_id, event_type, data, *, db: AsyncSession, source=None, operation_id=None, scope=None
|
||||
):
|
||||
checked_key = (app.api_key, app.data['manifest']['public_key'])
|
||||
raw = await send_app_invocation(
|
||||
app,
|
||||
user,
|
||||
channel_id,
|
||||
event_type,
|
||||
data,
|
||||
db=db,
|
||||
source=source,
|
||||
operation_id=operation_id,
|
||||
scope=scope,
|
||||
)
|
||||
app = await get_app_by_id(app.id, require_active=True, db=db)
|
||||
if (app.api_key, app.data['manifest']['public_key']) != checked_key:
|
||||
raise HTTPException(403, 'App credentials changed')
|
||||
|
|
|
|||
|
|
@ -62,7 +62,9 @@
|
|||
{#if reply?.title}<h3 class="font-medium">{reply.title}</h3>{/if}
|
||||
{#if reply?.content}<Markdown content={reply.content} allowEmbeds={false} />{/if}
|
||||
{#each reply.embeds ?? [] as embed}<AppEmbed {embed} />{/each}
|
||||
{#if reply?.type === 'error'}<p role="alert">The app could not complete this operation.</p>{/if}
|
||||
{#if reply?.type === 'error' && !reply?.content}<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 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"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import { LinkPreview } from 'bits-ui';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
import { getUserInfoById } from '$lib/apis/users';
|
||||
|
||||
import UserStatus from './UserStatus.svelte';
|
||||
|
||||
export let id: string | null = null;
|
||||
export let openPreview = false;
|
||||
export let resolvedUser = null;
|
||||
export let loadingUser = false;
|
||||
|
||||
export let side = 'top';
|
||||
export let align = 'start';
|
||||
|
|
@ -34,7 +34,10 @@
|
|||
}
|
||||
};
|
||||
|
||||
$: if (openPreview && id && id !== requestedUserId) {
|
||||
$: if (resolvedUser?.id === id) {
|
||||
user = resolvedUser;
|
||||
requestedUserId = id;
|
||||
} else if (!loadingUser && openPreview && id && id !== requestedUserId) {
|
||||
loadUser(id);
|
||||
} else if (!openPreview) {
|
||||
requestedUserId = null;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,28 @@
|
|||
<script context="module" lang="ts">
|
||||
import { getUserInfoById } from '$lib/apis/users';
|
||||
|
||||
const pendingUsers = new Map<string, ReturnType<typeof getUserInfoById>>();
|
||||
function loadMentionUser(token: string, id: string) {
|
||||
const key = `${token}:${id}`;
|
||||
if (!pendingUsers.has(key)) {
|
||||
pendingUsers.set(
|
||||
key,
|
||||
getUserInfoById(token, id).finally(() => pendingUsers.delete(key))
|
||||
);
|
||||
}
|
||||
return pendingUsers.get(key)!;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Token } from 'marked';
|
||||
import { LinkPreview } from 'bits-ui';
|
||||
|
||||
import { getContext } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { channels, models } from '$lib/stores';
|
||||
import UserStatus from '$lib/components/channel/Messages/Message/UserStatus.svelte';
|
||||
import UserStatusLinkPreview from '$lib/components/channel/Messages/Message/UserStatusLinkPreview.svelte';
|
||||
|
||||
const i18n = getContext<typeof import('$lib/i18n').default>('i18n');
|
||||
|
|
@ -19,12 +35,15 @@
|
|||
let idType = null;
|
||||
let id = '';
|
||||
let openPreview = false;
|
||||
let resolvedUser = null;
|
||||
let requestedUserId: string | null = null;
|
||||
let userUnavailable = false;
|
||||
|
||||
$: if (token && $i18n && $models && $channels) {
|
||||
init();
|
||||
init(resolvedUser, userUnavailable);
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const init = (user, unavailable) => {
|
||||
const _id = token?.id;
|
||||
triggerChar = token?.triggerChar ?? '@';
|
||||
|
||||
|
|
@ -68,7 +87,12 @@
|
|||
}
|
||||
} else if (triggerChar === '@') {
|
||||
if (idType === 'U') {
|
||||
// User
|
||||
label =
|
||||
user?.id === id
|
||||
? user.name
|
||||
: unavailable && requestedUserId === id
|
||||
? $i18n.t('Unknown')
|
||||
: label;
|
||||
} else if (idType === 'M') {
|
||||
// Model
|
||||
const model = $models.find((m) => m.id === id);
|
||||
|
|
@ -79,7 +103,30 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
browser &&
|
||||
idType === 'U' &&
|
||||
requestedUserId !== id &&
|
||||
(!token?.label || token.label === id || /^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(token.label))
|
||||
) {
|
||||
requestedUserId = id;
|
||||
resolvedUser = null;
|
||||
userUnavailable = false;
|
||||
resolveUser(id);
|
||||
}
|
||||
};
|
||||
|
||||
async function resolveUser(userId: string) {
|
||||
try {
|
||||
const user = await loadMentionUser(localStorage.token, userId);
|
||||
if (requestedUserId === userId) {
|
||||
resolvedUser = user;
|
||||
userUnavailable = !user;
|
||||
}
|
||||
} catch {
|
||||
if (requestedUserId === userId) userUnavailable = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<LinkPreview.Root openDelay={0} closeDelay={0} bind:open={openPreview}>
|
||||
|
|
@ -118,6 +165,11 @@
|
|||
</LinkPreview.Trigger>
|
||||
|
||||
{#if triggerChar === '@' && idType === 'U'}
|
||||
<UserStatusLinkPreview {id} {openPreview} />
|
||||
<UserStatusLinkPreview
|
||||
{id}
|
||||
{openPreview}
|
||||
resolvedUser={resolvedUser?.id === id ? resolvedUser : null}
|
||||
loadingUser={requestedUserId === id && !resolvedUser && !userUnavailable}
|
||||
/>
|
||||
{/if}
|
||||
</LinkPreview.Root>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue