Merge pull request #5955 from EtiennePerot/code-execution-message

feat: add code execution status to chat messages.
This commit is contained in:
Timothy Jaeryang Baek 2024-10-13 22:58:59 -07:00 committed by GitHub
commit adb1bfcaa8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 250 additions and 4 deletions

View File

@ -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;

View File

@ -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 () => {

View 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'}
&#x2705; <!-- Checkmark -->
{:else if code_execution?.status == 'ERROR'}
&#x274C; <!-- X mark -->
{:else if code_execution?.status == 'PENDING'}
&#x23F3; <!-- Hourglass -->
{:else}
&#x2049;&#xFE0F; <!-- 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>
&#x1F4BE; <!-- Floppy disk -->
<a href={file.url} target="_blank">{file.name}</a>
</li>
{/each}
</ul>
</div>
{/if}
</div>
</div>
</div>
</Modal>

View 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'}
&#x2705; <!-- Checkmark -->
{:else if code_execution.status == 'ERROR'}
&#x274C; <!-- X mark -->
{:else if code_execution.status == 'PENDING'}
<Spinner className="size-4" />
{:else}
&#x2049;&#xFE0F; <!-- 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>

View File

@ -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>