mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-28 02:07:05 -04:00
refac
This commit is contained in:
@@ -3043,7 +3043,9 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||
|
||||
if direct_tool_servers:
|
||||
for tool_server in direct_tool_servers:
|
||||
system_prompt = tool_server.pop('system_prompt', None)
|
||||
if tool_server.get('is_terminal') is True and not terminal_capability:
|
||||
continue
|
||||
system_prompt = tool_server.get('system_prompt')
|
||||
if system_prompt:
|
||||
form_data['messages'] = add_or_update_system_message(
|
||||
system_prompt,
|
||||
@@ -3051,7 +3053,7 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||
append=True,
|
||||
)
|
||||
|
||||
tool_specs = tool_server.pop('specs', [])
|
||||
tool_specs = tool_server.get('specs', [])
|
||||
|
||||
for tool in tool_specs:
|
||||
tools_dict[tool['name']] = {
|
||||
@@ -3107,6 +3109,50 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||
if name not in tools_dict:
|
||||
tools_dict[name] = tool_dict
|
||||
|
||||
# Only advertise user-shell tools when the originating browser has a connected shell.
|
||||
shell_tools = {
|
||||
name: tool
|
||||
for name, tool in tools_dict.items()
|
||||
if name in {'read_user_terminal', 'send_user_terminal_input'}
|
||||
and (tool.get('type') == 'terminal' or tool.get('server', {}).get('is_terminal') is True)
|
||||
}
|
||||
selected = {
|
||||
name
|
||||
for name, tool in shell_tools.items()
|
||||
if terminal_id
|
||||
and (
|
||||
tool.get('tool_id') == f'terminal:{terminal_id}'
|
||||
or (tool.get('direct') and tool.get('server', {}).get('url') == terminal_id)
|
||||
)
|
||||
}
|
||||
connected = False
|
||||
if (
|
||||
selected
|
||||
and event_caller
|
||||
and metadata.get('session_id')
|
||||
and metadata.get('chat_id')
|
||||
and not metadata.get('automation_id')
|
||||
and not metadata.get('internal')
|
||||
):
|
||||
try:
|
||||
state = await asyncio.wait_for(
|
||||
event_caller(
|
||||
{
|
||||
'type': 'request:terminal:state',
|
||||
'data': {'terminal_id': terminal_id, 'session_id': metadata['session_id']},
|
||||
}
|
||||
),
|
||||
timeout=2,
|
||||
)
|
||||
connected = isinstance(state, dict) and state.get('connected') is True
|
||||
except Exception:
|
||||
# Old/disconnected browsers cannot confirm availability; other tools still work.
|
||||
pass
|
||||
|
||||
for name in shell_tools:
|
||||
if not connected or name not in selected:
|
||||
tools_dict.pop(name)
|
||||
|
||||
if tools_dict:
|
||||
# Always store resolved tools in metadata so downstream consumers
|
||||
# (e.g. pipe functions) can access all tools including MCP and builtins.
|
||||
|
||||
@@ -3579,7 +3579,11 @@
|
||||
(server, idx) => toolServerIds.includes(idx) || toolServerIds.includes(server?.id)
|
||||
),
|
||||
// Direct terminal servers — always included when enabled (not routed through selectedToolIds)
|
||||
...($terminalServers ?? []).filter((t) => !t.id)
|
||||
...(terminalEnabled
|
||||
? ($terminalServers ?? [])
|
||||
.filter((server) => !server.id)
|
||||
.map((server) => ({ ...server, is_terminal: true }))
|
||||
: [])
|
||||
],
|
||||
features: getFeatures(),
|
||||
variables: {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import { terminalRequest, type TerminalConnection } from '$lib/apis/terminal';
|
||||
import { connectedUserTerminals } from '$lib/stores';
|
||||
|
||||
export let connection: TerminalConnection;
|
||||
export let chatId: string | null = null;
|
||||
@@ -22,6 +23,17 @@
|
||||
let pingInterval: ReturnType<typeof setInterval>;
|
||||
let destroyed = false;
|
||||
let sessionId = '';
|
||||
const terminalOwner = Symbol();
|
||||
|
||||
function disconnect() {
|
||||
connectedUserTerminals.update((entries) => {
|
||||
entries.delete(terminalOwner);
|
||||
return entries;
|
||||
});
|
||||
connected = false;
|
||||
connecting = false;
|
||||
clearInterval(pingInterval);
|
||||
}
|
||||
|
||||
export function write(output: string) {
|
||||
if (!term || destroyed) return;
|
||||
@@ -59,9 +71,14 @@
|
||||
ws = new WebSocket(url);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onopen = () => {
|
||||
if (destroyed) return;
|
||||
ws?.send(
|
||||
JSON.stringify({ type: 'auth', token: connection.key.trim(), chat_id: chatId ?? '' })
|
||||
);
|
||||
if (connection.selector && chatId) {
|
||||
const shell = { terminalId: connection.selector, chatId };
|
||||
connectedUserTerminals.update((entries) => entries.set(terminalOwner, shell));
|
||||
}
|
||||
connected = true;
|
||||
connecting = false;
|
||||
fit();
|
||||
@@ -72,21 +89,23 @@
|
||||
}, 25000);
|
||||
};
|
||||
ws.onmessage = (event) => {
|
||||
if (destroyed) return;
|
||||
if (event.data instanceof ArrayBuffer) term?.write(new Uint8Array(event.data));
|
||||
else write(event.data);
|
||||
};
|
||||
ws.onclose = () => {
|
||||
connected = false;
|
||||
connecting = false;
|
||||
clearInterval(pingInterval);
|
||||
if (destroyed) return;
|
||||
disconnect();
|
||||
write('\r\n\x1b[90m[Connection closed]\x1b[0m\r\n');
|
||||
};
|
||||
ws.onerror = () => {
|
||||
connecting = false;
|
||||
if (destroyed) return;
|
||||
disconnect();
|
||||
write('\r\n\x1b[31m[Terminal connection failed]\x1b[0m\r\n');
|
||||
};
|
||||
} catch (error) {
|
||||
connecting = false;
|
||||
if (destroyed) return;
|
||||
disconnect();
|
||||
write(`\r\n\x1b[31m[${error}]\x1b[0m\r\n`);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +152,7 @@
|
||||
|
||||
onDestroy(() => {
|
||||
destroyed = true;
|
||||
clearInterval(pingInterval);
|
||||
disconnect();
|
||||
ws?.close();
|
||||
resizeObserver?.disconnect();
|
||||
term?.dispose();
|
||||
|
||||
@@ -146,6 +146,9 @@ export type FileNavOpenRequest = string | { path: string; page?: number | null }
|
||||
export const showFileNavPath: Writable<FileNavOpenRequest | null> = writable(null);
|
||||
export const showFileNavDir: Writable<string | null> = writable(null);
|
||||
export const selectedTerminalId: Writable<string | null> = writable(null);
|
||||
export const connectedUserTerminals = writable(
|
||||
new Map<symbol, { terminalId: string; chatId: string }>()
|
||||
);
|
||||
|
||||
export const artifactCode = writable(null);
|
||||
export const artifactContents = writable(null);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
channels,
|
||||
channelId,
|
||||
terminalServers,
|
||||
connectedUserTerminals,
|
||||
showControls,
|
||||
showFileNavPath,
|
||||
showFileNavDir,
|
||||
@@ -556,6 +557,19 @@
|
||||
};
|
||||
|
||||
const chatEventHandler = async (event, cb) => {
|
||||
// Answer this session's availability check even when another chat is active.
|
||||
if (
|
||||
event?.data?.type === 'request:terminal:state' &&
|
||||
event.data.data?.session_id === $socket?.id
|
||||
) {
|
||||
cb?.({
|
||||
connected: [...$connectedUserTerminals.values()].some(
|
||||
(shell) =>
|
||||
shell.terminalId === event.data.data?.terminal_id && shell.chatId === event.chat_id
|
||||
)
|
||||
});
|
||||
return;
|
||||
}
|
||||
const chat = $page.url.pathname.includes(`/c/${event.chat_id}`);
|
||||
|
||||
// Skip events from temporary chats that are not the current chat.
|
||||
|
||||
Reference in New Issue
Block a user