217 lines
7.2 KiB
Vue
217 lines
7.2 KiB
Vue
<script lang="ts" setup>
|
|
import type { Entity } from '@triplit/client';
|
|
import type schema from '#triplit/schema';
|
|
|
|
const props = defineProps<{
|
|
toolCall: Readonly<Entity<typeof schema, 'tool_calls'>>;
|
|
}>();
|
|
|
|
const activeTab = ref('input');
|
|
|
|
const indicatorStyle = computed(() => {
|
|
const tabs = ['input', 'output', 'error', 'trace'];
|
|
const index = tabs.indexOf(activeTab.value);
|
|
// Each tab button is ~48px (40px height + 8px gap)
|
|
const offset = index * 48;
|
|
return {
|
|
transform: `translateY(${offset}px)`,
|
|
top: '4px',
|
|
};
|
|
});
|
|
|
|
const shiki = await getShikiHighlighter();
|
|
|
|
const html = ref('');
|
|
const lineNumberWidth = ref(1);
|
|
|
|
const input: ComputedRef<string> = computed(() => {
|
|
switch (activeTab.value) {
|
|
case 'input':
|
|
if (props.toolCall.input === null || props.toolCall.input === undefined) return '';
|
|
if (props.toolCall.input!.type === 'json') {
|
|
return JSON.stringify(JSON.parse(props.toolCall.input!.value), null, 2);
|
|
}
|
|
|
|
return props.toolCall.input!.value;
|
|
case 'output':
|
|
if (props.toolCall.output === null || props.toolCall.output === undefined) return '';
|
|
if (props.toolCall.output!.type === 'json') {
|
|
return JSON.stringify(JSON.parse(props.toolCall.output!.value), null, 2);
|
|
}
|
|
|
|
return props.toolCall.output!.value;
|
|
case 'error':
|
|
if (props.toolCall.error === null || props.toolCall.error === undefined) return '';
|
|
if (props.toolCall.error!.type === 'json') {
|
|
return JSON.stringify(JSON.parse(props.toolCall.error!.value), null, 2);
|
|
}
|
|
|
|
return props.toolCall.error!.value;
|
|
case 'trace': {
|
|
const traceObj: any = { ...props.toolCall };
|
|
if (traceObj === null) return '';
|
|
// marshall the trace object and the input, output, and error into their correct types
|
|
switch (traceObj.input?.type) {
|
|
case 'text':
|
|
traceObj.input = traceObj.input.value;
|
|
break;
|
|
case 'json':
|
|
traceObj.input = JSON.parse(traceObj.input.value);
|
|
break;
|
|
}
|
|
|
|
switch (traceObj.output?.type) {
|
|
case 'text':
|
|
traceObj.output = traceObj.output.value;
|
|
break;
|
|
case 'json':
|
|
traceObj.output = JSON.parse(traceObj.output.value);
|
|
break;
|
|
}
|
|
|
|
switch (traceObj.error?.type) {
|
|
case 'text':
|
|
traceObj.error = traceObj.error.value;
|
|
break;
|
|
case 'json':
|
|
traceObj.error = JSON.parse(traceObj.error.value);
|
|
break;
|
|
}
|
|
|
|
return JSON.stringify(traceObj, null, 2);
|
|
}
|
|
default:
|
|
return '';
|
|
}
|
|
});
|
|
|
|
watch(
|
|
input,
|
|
(newCode) => {
|
|
let lang = 'json';
|
|
try {
|
|
shiki.getLanguage(lang);
|
|
} catch (e) {
|
|
lang = 'text';
|
|
}
|
|
|
|
html.value = shiki.codeToHtml(newCode, {
|
|
lang,
|
|
themes: {
|
|
dark: 'vitesse-dark',
|
|
light: 'vitesse-light',
|
|
},
|
|
});
|
|
lineNumberWidth.value = html.value.split('\n').length.toString().length;
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="w-full border rounded-lg border-[var(--color-highlight)] flex flex-row h-80 overflow-hidden">
|
|
<div class="flex items-center gap-2 flex-col border-r border-[var(--color-highlight)] p-1 relative shrink-0">
|
|
<button @click="activeTab = 'input'" :class="[
|
|
'function-call-tab-selector',
|
|
activeTab === 'input' ? 'text-orange-6' : ''
|
|
]">
|
|
<div
|
|
class="w-[24px] h-[24px] flex-shrink-0 rounded-lg overflow-hidden flex items-center justify-center">
|
|
<Icon name="mynaui:code" class="w-4.5 h-4.5" />
|
|
</div>
|
|
Input
|
|
</button>
|
|
<button :disabled="toolCall.output === undefined || toolCall.output === null" @click="activeTab = 'output'"
|
|
:class="[
|
|
'function-call-tab-selector',
|
|
activeTab === 'output' ? 'text-orange-6' : ''
|
|
]">
|
|
<div
|
|
class="w-[24px] h-[24px] flex-shrink-0 rounded-lg overflow-hidden flex items-center justify-center">
|
|
<Icon name="mynaui:arrow-down-square" class="w-4.5 h-4.5" />
|
|
</div>
|
|
Output
|
|
</button>
|
|
<button v-if="toolCall.error !== undefined && toolCall.error !== null" @click="activeTab = 'error'" :class="[
|
|
'function-call-tab-selector',
|
|
activeTab === 'error' ? 'text-orange-6' : ''
|
|
]">
|
|
<div
|
|
class="w-[24px] h-[24px] flex-shrink-0 rounded-lg overflow-hidden flex items-center justify-center">
|
|
<Icon name="mynaui:danger-triangle" class="w-4.5 h-4.5" />
|
|
</div>
|
|
Error
|
|
</button>
|
|
<button @click="activeTab = 'trace'" :class="[
|
|
'function-call-tab-selector',
|
|
activeTab === 'trace' ? 'text-orange-6' : ''
|
|
]">
|
|
<div
|
|
class="w-[24px] h-[24px] flex-shrink-0 rounded-lg overflow-hidden flex items-center justify-center">
|
|
<Icon name="mynaui:flask" class="w-4.5 h-4.5" />
|
|
</div>
|
|
Function call
|
|
</button>
|
|
|
|
<div :style="indicatorStyle"
|
|
class="absolute bg-orange-6 w-[3px] h-8 rounded-l mt-1 right-0 transition-transform duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-grow overflow-auto max-h-full max-w-full">
|
|
<div class="overflow-hidden h-full" :style="`--line-number-width: ${lineNumberWidth}ch`"
|
|
id="function-call-container" v-html="html">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
#function-call-container>pre {
|
|
height: 100%;
|
|
overflow-x: auto;
|
|
scrollbar-width: thin;
|
|
padding: 1rem;
|
|
line-height: 1.625;
|
|
counter-reset: lines;
|
|
}
|
|
|
|
#function-call-container>pre>code .line::before {
|
|
counter-increment: lines;
|
|
content: counter(lines);
|
|
width: var(--line-number-width);
|
|
margin-right: 1.5rem;
|
|
display: inline-block;
|
|
text-align: right;
|
|
}
|
|
|
|
.dark #function-call-container>pre>code .line::before {
|
|
color: rgba(255, 255, 255, 0.25);
|
|
}
|
|
|
|
.light #function-call-container>pre>code .line::before {
|
|
color: rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
.function-call-tab-selector {
|
|
padding: 0.5rem;
|
|
border-radius: 0.375rem;
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
align-items: center;
|
|
width: 100%;
|
|
transition-property: color, background-color, border-color,
|
|
text-decoration-color, fill, stroke;
|
|
transition-duration: 200ms;
|
|
transition-timing-function: cubic-bezier(0.5, 1, 0.89, 1);
|
|
}
|
|
|
|
.function-call-tab-selector:hover:enabled {
|
|
background-color: var(--color-highlight);
|
|
}
|
|
|
|
.function-call-tab-selector:disabled {
|
|
opacity: 0.3;
|
|
cursor: not-allowed;
|
|
}
|
|
</style> |