mirror of
https://github.com/open-webui/open-webui
synced 2024-11-06 16:59:42 +00:00
Merge pull request #5955 from EtiennePerot/code-execution-message
feat: add code execution status to chat messages.
This commit is contained in:
commit
adb1bfcaa8
@ -175,10 +175,30 @@
|
|||||||
message.statusHistory = [data];
|
message.statusHistory = [data];
|
||||||
}
|
}
|
||||||
} else if (type === 'citation') {
|
} else if (type === 'citation') {
|
||||||
if (message?.citations) {
|
if (data?.type === 'code_execution') {
|
||||||
message.citations.push(data);
|
// 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 {
|
} else {
|
||||||
message.citations = [data];
|
// Regular citation.
|
||||||
|
if (message?.citations) {
|
||||||
|
message.citations.push(data);
|
||||||
|
} else {
|
||||||
|
message.citations = [data];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (type === 'message') {
|
} else if (type === 'message') {
|
||||||
message.content += data.content;
|
message.content += data.content;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
export let token;
|
export let token;
|
||||||
export let lang = '';
|
export let lang = '';
|
||||||
export let code = '';
|
export let code = '';
|
||||||
|
export let allow_execution = true;
|
||||||
|
|
||||||
let _code = '';
|
let _code = '';
|
||||||
$: if (code) {
|
$: if (code) {
|
||||||
@ -319,7 +320,7 @@ __builtins__.input = input`);
|
|||||||
{#if lang.toLowerCase() === 'python' || lang.toLowerCase() === 'py' || (lang === '' && checkPythonCode(code))}
|
{#if lang.toLowerCase() === 'python' || lang.toLowerCase() === 'py' || (lang === '' && checkPythonCode(code))}
|
||||||
{#if executing}
|
{#if executing}
|
||||||
<div class="run-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
|
<div class="run-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
|
||||||
{:else}
|
{:else if allow_execution}
|
||||||
<button
|
<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"
|
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 () => {
|
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 Markdown from './Markdown.svelte';
|
||||||
import Error from './Error.svelte';
|
import Error from './Error.svelte';
|
||||||
import Citations from './Citations.svelte';
|
import Citations from './Citations.svelte';
|
||||||
|
import CodeExecutions from './CodeExecutions.svelte';
|
||||||
|
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
import type { i18n as i18nType } from 'i18next';
|
import type { i18n as i18nType } from 'i18next';
|
||||||
@ -64,6 +65,17 @@
|
|||||||
done: boolean;
|
done: boolean;
|
||||||
error?: boolean | { content: string };
|
error?: boolean | { content: string };
|
||||||
citations?: string[];
|
citations?: string[];
|
||||||
|
code_executions?: {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
language?: string;
|
||||||
|
result?: {
|
||||||
|
error?: string;
|
||||||
|
output?: string;
|
||||||
|
files?: { name: string; url: string }[];
|
||||||
|
};
|
||||||
|
}[];
|
||||||
info?: {
|
info?: {
|
||||||
openai?: boolean;
|
openai?: boolean;
|
||||||
prompt_tokens?: number;
|
prompt_tokens?: number;
|
||||||
@ -516,6 +528,9 @@
|
|||||||
{#if message.citations}
|
{#if message.citations}
|
||||||
<Citations citations={message.citations} />
|
<Citations citations={message.citations} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if message.code_executions}
|
||||||
|
<CodeExecutions code_executions={message.code_executions} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user