mirror of
https://github.com/open-webui/open-webui
synced 2024-11-06 16:59:42 +00:00
feat: add code execution status to chat messages.
This adds `code_executions` as an array of code execution statuses to chat messages. The intent of this data is to be displayed in a similar manner as citations: at the bottom of the message, with buttons that open a modal for more info. However, code execution data doesn't fit well in citation modals, because they fundamentally differ in their formatting. Code execution status includes the code that was run (which benefits from being syntax-highlighted), and the output and generated files. This differs from citations which are just list of document names and links. Additionally, code execution is a process, whereas citations are only emitted once. This is why code execution data uses an ID-based approach, where each code execution instance is identified by a unique ID and can be updated by emitting a new `code_execution` message with the same ID. This allows the code execution status to be updated as code runs.
This commit is contained in:
parent
5c48fce382
commit
9fbff16a08
@ -175,10 +175,30 @@
|
||||
message.statusHistory = [data];
|
||||
}
|
||||
} else if (type === 'citation') {
|
||||
if (message?.citations) {
|
||||
message.citations.push(data);
|
||||
if (data?.type === 'code_execution') {
|
||||
// Code execution; update existing code execution by UUID,
|
||||
// otherwise append.
|
||||
if (!message?.code_executions) {
|
||||
message.code_executions = [];
|
||||
}
|
||||
let is_update = false;
|
||||
for (let i = 0; i < message.code_executions.length; i++) {
|
||||
if (message.code_executions[i].uuid === data.uuid) {
|
||||
message.code_executions[i] = data;
|
||||
is_update = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!is_update) {
|
||||
message.code_executions.push(data);
|
||||
}
|
||||
} else {
|
||||
message.citations = [data];
|
||||
// Regular citation.
|
||||
if (message?.citations) {
|
||||
message.citations.push(data);
|
||||
} else {
|
||||
message.citations = [data];
|
||||
}
|
||||
}
|
||||
} else if (type === 'message') {
|
||||
message.content += data.content;
|
||||
|
@ -23,6 +23,7 @@
|
||||
export let token;
|
||||
export let lang = '';
|
||||
export let code = '';
|
||||
export let allow_execution = true;
|
||||
|
||||
let _code = '';
|
||||
$: if (code) {
|
||||
@ -319,7 +320,7 @@ __builtins__.input = input`);
|
||||
{#if lang.toLowerCase() === 'python' || lang.toLowerCase() === 'py' || (lang === '' && checkPythonCode(code))}
|
||||
{#if executing}
|
||||
<div class="run-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
|
||||
{:else}
|
||||
{:else if allow_execution}
|
||||
<button
|
||||
class="run-code-button bg-none border-none bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-md px-1.5 py-0.5"
|
||||
on:click={async () => {
|
||||
|
115
src/lib/components/chat/Messages/CodeExecutionModal.svelte
Normal file
115
src/lib/components/chat/Messages/CodeExecutionModal.svelte
Normal file
@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import CodeBlock from './CodeBlock.svelte';
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let show = false;
|
||||
export let code_execution = null;
|
||||
</script>
|
||||
|
||||
<Modal size="lg" bind:show>
|
||||
<div>
|
||||
<div class="flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
|
||||
<div class="text-lg font-medium self-center capitalize">
|
||||
{#if code_execution?.status == 'OK'}
|
||||
✅ <!-- Checkmark -->
|
||||
{:else if code_execution?.status == 'ERROR'}
|
||||
❌ <!-- X mark -->
|
||||
{:else if code_execution?.status == 'PENDING'}
|
||||
⏳ <!-- Hourglass -->
|
||||
{:else}
|
||||
⁉️ <!-- Interrobang -->
|
||||
{/if}
|
||||
{#if code_execution?.name}
|
||||
{$i18n.t('Code execution')}: {code_execution?.name}
|
||||
{:else}
|
||||
{$i18n.t('Code execution')}
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
show = false;
|
||||
code_execution = null;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full px-6 pb-5 md:space-x-4">
|
||||
<div
|
||||
class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-hidden"
|
||||
>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="text-sm font-medium dark:text-gray-300">
|
||||
{$i18n.t('Code')}
|
||||
</div>
|
||||
|
||||
<CodeBlock
|
||||
id="codeexec-{code_execution?.uuid}-code"
|
||||
lang={code_execution?.language}
|
||||
code={code_execution?.code}
|
||||
allow_execution={false}
|
||||
/>
|
||||
</div>
|
||||
{#if code_execution?.error}
|
||||
<div class="flex flex-col w-full">
|
||||
<hr class=" dark:border-gray-850 my-3" />
|
||||
<div class="text-sm dark:text-gray-400">
|
||||
{$i18n.t('Error')}
|
||||
</div>
|
||||
|
||||
<CodeBlock
|
||||
id="codeexec-{code_execution?.uuid}-error"
|
||||
lang=""
|
||||
code={code_execution?.error}
|
||||
allow_execution={false}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if code_execution?.output}
|
||||
<div class="flex flex-col w-full">
|
||||
<hr class=" dark:border-gray-850 my-3" />
|
||||
<div class="text-sm dark:text-gray-400">
|
||||
{$i18n.t('Output')}
|
||||
</div>
|
||||
|
||||
<CodeBlock
|
||||
id="codeexec-{code_execution?.uuid}-output"
|
||||
lang=""
|
||||
code={code_execution?.output}
|
||||
allow_execution={false}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if code_execution?.files && code_execution?.files.length > 0}
|
||||
<div class="flex flex-col w-full">
|
||||
<hr class=" dark:border-gray-850 my-3" />
|
||||
<div class=" text-sm font-medium dark:text-gray-300">
|
||||
{$i18n.t('Files')}
|
||||
</div>
|
||||
<ul class="mt-1 list-disc pl-4 text-xs">
|
||||
{#each code_execution?.files as file}
|
||||
<li>
|
||||
💾 <!-- Floppy disk -->
|
||||
<a href={file.url} target="_blank">{file.name}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
95
src/lib/components/chat/Messages/CodeExecutions.svelte
Normal file
95
src/lib/components/chat/Messages/CodeExecutions.svelte
Normal file
@ -0,0 +1,95 @@
|
||||
<script lang="ts">
|
||||
import CodeExecutionModal from './CodeExecutionModal.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
|
||||
export let code_executions = [];
|
||||
|
||||
let _code_executions = [];
|
||||
|
||||
$: _code_executions = code_executions.reduce((acc, code_execution) => {
|
||||
let error = null;
|
||||
let output = null;
|
||||
let files = [];
|
||||
let status = 'PENDING';
|
||||
if (code_execution.result) {
|
||||
output = code_execution.result.output;
|
||||
if (code_execution.result.error) {
|
||||
status = 'ERROR';
|
||||
error = code_execution.result.error;
|
||||
} else {
|
||||
status = 'OK';
|
||||
}
|
||||
if (code_execution.result.files) {
|
||||
files = code_execution.result.files;
|
||||
}
|
||||
}
|
||||
acc.push({
|
||||
uuid: code_execution.uuid,
|
||||
name: code_execution.name,
|
||||
code: code_execution.code,
|
||||
language: code_execution.language || '',
|
||||
status: status,
|
||||
error: error,
|
||||
output: output,
|
||||
files: files
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
let selectedCodeExecution = null;
|
||||
let showCodeExecutionModal = false;
|
||||
</script>
|
||||
|
||||
<CodeExecutionModal bind:show={showCodeExecutionModal} code_execution={selectedCodeExecution} />
|
||||
|
||||
{#if _code_executions.length > 0}
|
||||
<div class="mt-1 mb-2 w-full flex gap-1 items-center flex-wrap">
|
||||
{#each _code_executions as code_execution}
|
||||
<div class="flex gap-1 text-xs font-semibold">
|
||||
<button
|
||||
class="flex dark:text-gray-300 py-1 px-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-xl max-w-96"
|
||||
on:click={() => {
|
||||
selectedCodeExecution = code_execution;
|
||||
showCodeExecutionModal = true;
|
||||
}}
|
||||
>
|
||||
<div class="bg-white dark:bg-gray-700 rounded-full size-4">
|
||||
{#if code_execution.status == 'OK'}
|
||||
✅ <!-- Checkmark -->
|
||||
{:else if code_execution.status == 'ERROR'}
|
||||
❌ <!-- X mark -->
|
||||
{:else if code_execution.status == 'PENDING'}
|
||||
<Spinner className="size-4" />
|
||||
{:else}
|
||||
⁉️ <!-- Interrobang -->
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="flex-1 mx-2 line-clamp-1 code-execution-name {code_execution.status == 'PENDING'
|
||||
? 'pulse'
|
||||
: ''}"
|
||||
>
|
||||
{code_execution.name}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.pulse {
|
||||
opacity: 1;
|
||||
animation: pulse 1.5s ease;
|
||||
}
|
||||
</style>
|
@ -35,6 +35,7 @@
|
||||
import Markdown from './Markdown.svelte';
|
||||
import Error from './Error.svelte';
|
||||
import Citations from './Citations.svelte';
|
||||
import CodeExecutions from './CodeExecutions.svelte';
|
||||
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { i18n as i18nType } from 'i18next';
|
||||
@ -64,6 +65,17 @@
|
||||
done: boolean;
|
||||
error?: boolean | { content: string };
|
||||
citations?: string[];
|
||||
code_executions?: {
|
||||
uuid: string;
|
||||
name: string;
|
||||
code: string;
|
||||
language?: string;
|
||||
result?: {
|
||||
error?: string;
|
||||
output?: string;
|
||||
files?: { name: string; url: string }[];
|
||||
};
|
||||
}[];
|
||||
info?: {
|
||||
openai?: boolean;
|
||||
prompt_tokens?: number;
|
||||
@ -516,6 +528,9 @@
|
||||
{#if message.citations}
|
||||
<Citations citations={message.citations} />
|
||||
{/if}
|
||||
{#if message.code_executions}
|
||||
<CodeExecutions code_executions={message.code_executions} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user