blob: 270965bce49ac9368a53c57d5984ec66e59bc894 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
<script lang="ts">
/*** IMPORT ------------------------------------------- ***/
import type { Extension } from "@codemirror/state";
import type { Snippet } from "svelte";
/*** UTILITY ------------------------------------------ ***/
import Editor from "./Editor.svelte";
import type { TabTiming } from "../state/session.svelte.ts";
type Props = {
footer?: Snippet<[{ result: string }]>;
streamIntervals?: number[];
theme?: Extension;
timing?: TabTiming | null;
value: string;
};
let {
footer,
streamIntervals = [],
theme,
timing = null,
value
}: Props = $props();
let metadata = $derived.by(() => {
if (!timing)
return null;
if (streamIntervals.length > 0) {
const medianMs = Math.round(median(streamIntervals));
const messages = streamIntervals.length + 1;
return `${messages} messages · median ${medianMs}ms`;
}
const totalMs = Math.round(timing.endMs - timing.startMs);
const firstByteMs = Math.round(timing.firstByteMs - timing.startMs);
return `${totalMs}ms · first byte ${firstByteMs}ms`;
});
/*** HELPER ------------------------------------------- ***/
function median(values: number[]): number {
if (values.length === 0)
return 0;
const sorted = [...values].sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
return sorted.length % 2 === 0 ?
(sorted[mid - 1] + sorted[mid]) / 2 :
sorted[mid];
}
function noop(_v: string) {}
</script>
<style lang="scss">
.result {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
position: relative;
}
.label,
.metadata,
.footer {
font-size: 0.75rem;
padding: 0.25rem 0.75rem;
}
.label,
.metadata {
background-color: var(--uchu-gray-2);
color: var(--uchu-yin-7);
}
.label {
top: 1rem; right: 1rem;
border: 1px solid var(--uchu-gray-3);
font-weight: 600;
letter-spacing: 0.05rem;
pointer-events: none;
position: absolute;
text-transform: uppercase;
z-index: 1;
}
.metadata,
.footer {
position: relative;
}
.metadata {
align-items: center;
display: flex;
flex-direction: row;
svg {
width: 1rem; height: 1rem;
margin-right: 0.25rem;
}
}
.footer {
background-color: var(--uchu-gray-3);
border-top: 1px solid var(--uchu-gray-4);
}
</style>
<div class="result">
<div class="label">Response</div>
<Editor language="json" onChange={noop} readOnly {theme} {value}/>
{#if metadata}
<div class="metadata">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 5.75L20.25 12L14 18.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="square"/>
<path d="M19.5 12H3.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="square"/>
</svg>
{@html metadata}
</div>
{/if}
{#if footer}
<div class="footer">{@render footer({ result: value })}</div>
{/if}
</div>
|