streaming, markdown, model selecting, and lots more

This commit is contained in:
Zoe
2026-02-02 23:16:06 -06:00
parent 8c28946703
commit d5a5945c03
114 changed files with 6109 additions and 2938 deletions
+177
View File
@@ -0,0 +1,177 @@
<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', '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) 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) return '';
if (props.toolCall.output!.type === 'json') {
return JSON.stringify(JSON.parse(props.toolCall.output!.value), null, 2);
}
return props.toolCall.output!.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="[
'hover:bg-[var(--color-highlight)] p-2 rounded-lg flex gap-1 items-center w-full transition-colors duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]',
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 @click="activeTab = 'output'" :class="[
'hover:bg-[var(--color-highlight)] p-2 rounded-lg flex gap-1 items-center w-full transition-colors duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]',
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 @click="activeTab = 'trace'" :class="[
'hover:bg-[var(--color-highlight)] p-2 rounded-lg flex gap-1 items-center w-full transition-colors duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]',
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);
}
</style>
@@ -0,0 +1,64 @@
<script lang="ts" setup>
import type { Entity } from '@triplit/client';
import type schema from '#triplit/schema';
import Debug from './Debug.vue';
const props = defineProps<{
toolCall: Readonly<Entity<typeof schema, 'tool_calls'>>;
}>();
const deubgToolCallOpen = ref(false);
const toggleDebugToolCall = () => {
deubgToolCallOpen.value = !deubgToolCallOpen.value;
};
const iconName = ref('mynaui:tool');
const iconColor = ref('var(--color-subtle)');
watch(
() => props.toolCall.status,
(status) => {
switch (status) {
case 'pending':
iconName.value = 'svg-spinners:180-ring-with-bg';
break;
case 'completed':
iconName.value = 'mynaui:tool';
break;
case 'failed':
iconName.value = 'mynaui:x-solid';
iconColor.value = '#ff3b3b';
break;
}
},
{ immediate: true },
);
</script>
<template>
<div class="flex flex-col gap-2">
<div
class="select-none w-full hover:bg-[var(--color-highlight)] group rounded-lg p-1 flex justify-between items-center text-[--color-reasoning] transition-colors duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
<div class="flex items-center justify-between w-full">
<div class="flex items-center gap-1">
<div
class="w-[24px] h-[24px] flex-shrink-0 rounded-lg overflow-hidden bg-[var(--color-neutral)] flex items-center justify-center">
<Icon :name="iconName" :style="{ color: iconColor }"
class="w-3 h-3 text-[var(--color-subtle)]" />
</div>
{{ toolCall.toolName }}
</div>
<div
class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
<button @click="toggleDebugToolCall"
class="w-[24px] h-[24px] flex-shrink-0 rounded-lg overflow-hidden hover:bg-[var(--color-highlight)] flex items-center justify-center transition-colors duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
<Icon name="mynaui:search" class="w-3 h-3 text-[var(--color-subtle)]" />
</button>
</div>
</div>
</div>
<Debug v-if="deubgToolCallOpen" :toolCall="toolCall" />
</div>
</template>