@oh-my-pi/pi-web-ui 1.337.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.md +609 -0
- package/example/README.md +61 -0
- package/example/index.html +13 -0
- package/example/package.json +24 -0
- package/example/src/app.css +1 -0
- package/example/src/custom-messages.ts +99 -0
- package/example/src/main.ts +420 -0
- package/example/tsconfig.json +23 -0
- package/example/vite.config.ts +6 -0
- package/package.json +57 -0
- package/scripts/count-prompt-tokens.ts +88 -0
- package/src/ChatPanel.ts +218 -0
- package/src/app.css +68 -0
- package/src/components/AgentInterface.ts +390 -0
- package/src/components/AttachmentTile.ts +107 -0
- package/src/components/ConsoleBlock.ts +74 -0
- package/src/components/CustomProviderCard.ts +96 -0
- package/src/components/ExpandableSection.ts +46 -0
- package/src/components/Input.ts +113 -0
- package/src/components/MessageEditor.ts +404 -0
- package/src/components/MessageList.ts +97 -0
- package/src/components/Messages.ts +384 -0
- package/src/components/ProviderKeyInput.ts +152 -0
- package/src/components/SandboxedIframe.ts +626 -0
- package/src/components/StreamingMessageContainer.ts +107 -0
- package/src/components/ThinkingBlock.ts +45 -0
- package/src/components/message-renderer-registry.ts +28 -0
- package/src/components/sandbox/ArtifactsRuntimeProvider.ts +219 -0
- package/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
- package/src/components/sandbox/ConsoleRuntimeProvider.ts +186 -0
- package/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
- package/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
- package/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
- package/src/components/sandbox/SandboxRuntimeProvider.ts +52 -0
- package/src/dialogs/ApiKeyPromptDialog.ts +75 -0
- package/src/dialogs/AttachmentOverlay.ts +640 -0
- package/src/dialogs/CustomProviderDialog.ts +274 -0
- package/src/dialogs/ModelSelector.ts +314 -0
- package/src/dialogs/PersistentStorageDialog.ts +146 -0
- package/src/dialogs/ProvidersModelsTab.ts +212 -0
- package/src/dialogs/SessionListDialog.ts +157 -0
- package/src/dialogs/SettingsDialog.ts +216 -0
- package/src/index.ts +115 -0
- package/src/prompts/prompts.ts +282 -0
- package/src/storage/app-storage.ts +60 -0
- package/src/storage/backends/indexeddb-storage-backend.ts +193 -0
- package/src/storage/store.ts +33 -0
- package/src/storage/stores/custom-providers-store.ts +62 -0
- package/src/storage/stores/provider-keys-store.ts +33 -0
- package/src/storage/stores/sessions-store.ts +136 -0
- package/src/storage/stores/settings-store.ts +34 -0
- package/src/storage/types.ts +206 -0
- package/src/tools/artifacts/ArtifactElement.ts +14 -0
- package/src/tools/artifacts/ArtifactPill.ts +26 -0
- package/src/tools/artifacts/Console.ts +102 -0
- package/src/tools/artifacts/DocxArtifact.ts +213 -0
- package/src/tools/artifacts/ExcelArtifact.ts +231 -0
- package/src/tools/artifacts/GenericArtifact.ts +118 -0
- package/src/tools/artifacts/HtmlArtifact.ts +203 -0
- package/src/tools/artifacts/ImageArtifact.ts +116 -0
- package/src/tools/artifacts/MarkdownArtifact.ts +83 -0
- package/src/tools/artifacts/PdfArtifact.ts +201 -0
- package/src/tools/artifacts/SvgArtifact.ts +82 -0
- package/src/tools/artifacts/TextArtifact.ts +148 -0
- package/src/tools/artifacts/artifacts-tool-renderer.ts +371 -0
- package/src/tools/artifacts/artifacts.ts +713 -0
- package/src/tools/artifacts/index.ts +7 -0
- package/src/tools/extract-document.ts +271 -0
- package/src/tools/index.ts +46 -0
- package/src/tools/javascript-repl.ts +316 -0
- package/src/tools/renderer-registry.ts +127 -0
- package/src/tools/renderers/BashRenderer.ts +52 -0
- package/src/tools/renderers/CalculateRenderer.ts +58 -0
- package/src/tools/renderers/DefaultRenderer.ts +95 -0
- package/src/tools/renderers/GetCurrentTimeRenderer.ts +92 -0
- package/src/tools/types.ts +15 -0
- package/src/utils/attachment-utils.ts +472 -0
- package/src/utils/auth-token.ts +22 -0
- package/src/utils/format.ts +42 -0
- package/src/utils/i18n.ts +653 -0
- package/src/utils/model-discovery.ts +277 -0
- package/src/utils/proxy-utils.ts +134 -0
- package/src/utils/test-sessions.ts +2357 -0
- package/tsconfig.build.json +20 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { icon } from "@mariozechner/mini-lit";
|
|
2
|
+
import { html, type TemplateResult } from "lit";
|
|
3
|
+
import type { Ref } from "lit/directives/ref.js";
|
|
4
|
+
import { ref } from "lit/directives/ref.js";
|
|
5
|
+
import { ChevronsUpDown, ChevronUp, Loader } from "lucide";
|
|
6
|
+
import type { ToolRenderer } from "./types.js";
|
|
7
|
+
|
|
8
|
+
// Registry of tool renderers
|
|
9
|
+
export const toolRenderers = new Map<string, ToolRenderer>();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Register a custom tool renderer
|
|
13
|
+
*/
|
|
14
|
+
export function registerToolRenderer(toolName: string, renderer: ToolRenderer): void {
|
|
15
|
+
toolRenderers.set(toolName, renderer);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get a tool renderer by name
|
|
20
|
+
*/
|
|
21
|
+
export function getToolRenderer(toolName: string): ToolRenderer | undefined {
|
|
22
|
+
return toolRenderers.get(toolName);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Helper to render a header for tool renderers
|
|
27
|
+
* Shows icon on left when complete/error, spinner on right when in progress
|
|
28
|
+
*/
|
|
29
|
+
export function renderHeader(
|
|
30
|
+
state: "inprogress" | "complete" | "error",
|
|
31
|
+
toolIcon: any,
|
|
32
|
+
text: string | TemplateResult,
|
|
33
|
+
): TemplateResult {
|
|
34
|
+
const statusIcon = (iconComponent: any, color: string) =>
|
|
35
|
+
html`<span class="inline-block ${color}">${icon(iconComponent, "sm")}</span>`;
|
|
36
|
+
|
|
37
|
+
switch (state) {
|
|
38
|
+
case "inprogress":
|
|
39
|
+
return html`
|
|
40
|
+
<div class="flex items-center justify-between gap-2 text-sm text-muted-foreground">
|
|
41
|
+
<div class="flex items-center gap-2">${statusIcon(toolIcon, "text-foreground")} ${text}</div>
|
|
42
|
+
${statusIcon(Loader, "text-foreground animate-spin")}
|
|
43
|
+
</div>
|
|
44
|
+
`;
|
|
45
|
+
case "complete":
|
|
46
|
+
return html`
|
|
47
|
+
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
|
48
|
+
${statusIcon(toolIcon, "text-green-600 dark:text-green-500")} ${text}
|
|
49
|
+
</div>
|
|
50
|
+
`;
|
|
51
|
+
case "error":
|
|
52
|
+
return html`
|
|
53
|
+
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
|
54
|
+
${statusIcon(toolIcon, "text-destructive")} ${text}
|
|
55
|
+
</div>
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Helper to render a collapsible header for tool renderers
|
|
62
|
+
* Same as renderHeader but with a chevron button that toggles visibility of content
|
|
63
|
+
*/
|
|
64
|
+
export function renderCollapsibleHeader(
|
|
65
|
+
state: "inprogress" | "complete" | "error",
|
|
66
|
+
toolIcon: any,
|
|
67
|
+
text: string | TemplateResult,
|
|
68
|
+
contentRef: Ref<HTMLElement>,
|
|
69
|
+
chevronRef: Ref<HTMLElement>,
|
|
70
|
+
defaultExpanded = false,
|
|
71
|
+
): TemplateResult {
|
|
72
|
+
const statusIcon = (iconComponent: any, color: string) =>
|
|
73
|
+
html`<span class="inline-block ${color}">${icon(iconComponent, "sm")}</span>`;
|
|
74
|
+
|
|
75
|
+
const toggleContent = (e: Event) => {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
const content = contentRef.value;
|
|
78
|
+
const chevron = chevronRef.value;
|
|
79
|
+
if (content && chevron) {
|
|
80
|
+
const isCollapsed = content.classList.contains("max-h-0");
|
|
81
|
+
if (isCollapsed) {
|
|
82
|
+
content.classList.remove("max-h-0");
|
|
83
|
+
content.classList.add("max-h-[2000px]", "mt-3");
|
|
84
|
+
// Show ChevronUp, hide ChevronsUpDown
|
|
85
|
+
const upIcon = chevron.querySelector(".chevron-up");
|
|
86
|
+
const downIcon = chevron.querySelector(".chevrons-up-down");
|
|
87
|
+
if (upIcon && downIcon) {
|
|
88
|
+
upIcon.classList.remove("hidden");
|
|
89
|
+
downIcon.classList.add("hidden");
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
content.classList.remove("max-h-[2000px]", "mt-3");
|
|
93
|
+
content.classList.add("max-h-0");
|
|
94
|
+
// Show ChevronsUpDown, hide ChevronUp
|
|
95
|
+
const upIcon = chevron.querySelector(".chevron-up");
|
|
96
|
+
const downIcon = chevron.querySelector(".chevrons-up-down");
|
|
97
|
+
if (upIcon && downIcon) {
|
|
98
|
+
upIcon.classList.add("hidden");
|
|
99
|
+
downIcon.classList.remove("hidden");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const toolIconColor =
|
|
106
|
+
state === "complete"
|
|
107
|
+
? "text-green-600 dark:text-green-500"
|
|
108
|
+
: state === "error"
|
|
109
|
+
? "text-destructive"
|
|
110
|
+
: "text-foreground";
|
|
111
|
+
|
|
112
|
+
return html`
|
|
113
|
+
<button
|
|
114
|
+
@click=${toggleContent}
|
|
115
|
+
class="flex items-center justify-between gap-2 text-sm text-muted-foreground w-full text-left hover:text-foreground transition-colors cursor-pointer"
|
|
116
|
+
>
|
|
117
|
+
<div class="flex items-center gap-2">
|
|
118
|
+
${state === "inprogress" ? statusIcon(Loader, "text-foreground animate-spin") : ""}
|
|
119
|
+
${statusIcon(toolIcon, toolIconColor)} ${text}
|
|
120
|
+
</div>
|
|
121
|
+
<span class="inline-block text-muted-foreground" ${ref(chevronRef)}>
|
|
122
|
+
<span class="chevron-up ${defaultExpanded ? "" : "hidden"}">${icon(ChevronUp, "sm")}</span>
|
|
123
|
+
<span class="chevrons-up-down ${defaultExpanded ? "hidden" : ""}">${icon(ChevronsUpDown, "sm")}</span>
|
|
124
|
+
</span>
|
|
125
|
+
</button>
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { html } from "lit";
|
|
3
|
+
import { SquareTerminal } from "lucide";
|
|
4
|
+
import { i18n } from "../../utils/i18n.js";
|
|
5
|
+
import { renderHeader } from "../renderer-registry.js";
|
|
6
|
+
import type { ToolRenderer, ToolRenderResult } from "../types.js";
|
|
7
|
+
|
|
8
|
+
interface BashParams {
|
|
9
|
+
command: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Bash tool has undefined details (only uses output)
|
|
13
|
+
export class BashRenderer implements ToolRenderer<BashParams, undefined> {
|
|
14
|
+
render(params: BashParams | undefined, result: ToolResultMessage<undefined> | undefined): ToolRenderResult {
|
|
15
|
+
const state = result ? (result.isError ? "error" : "complete") : "inprogress";
|
|
16
|
+
|
|
17
|
+
// With result: show command + output
|
|
18
|
+
if (result && params?.command) {
|
|
19
|
+
const output =
|
|
20
|
+
result.content
|
|
21
|
+
?.filter((c) => c.type === "text")
|
|
22
|
+
.map((c: any) => c.text)
|
|
23
|
+
.join("\n") || "";
|
|
24
|
+
const combined = output ? `> ${params.command}\n\n${output}` : `> ${params.command}`;
|
|
25
|
+
return {
|
|
26
|
+
content: html`
|
|
27
|
+
<div class="space-y-3">
|
|
28
|
+
${renderHeader(state, SquareTerminal, i18n("Running command..."))}
|
|
29
|
+
<console-block .content=${combined} .variant=${result.isError ? "error" : "default"}></console-block>
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
isCustom: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Just params (streaming or waiting)
|
|
37
|
+
if (params?.command) {
|
|
38
|
+
return {
|
|
39
|
+
content: html`
|
|
40
|
+
<div class="space-y-3">
|
|
41
|
+
${renderHeader(state, SquareTerminal, i18n("Running command..."))}
|
|
42
|
+
<console-block .content=${`> ${params.command}`}></console-block>
|
|
43
|
+
</div>
|
|
44
|
+
`,
|
|
45
|
+
isCustom: false,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// No params yet
|
|
50
|
+
return { content: renderHeader(state, SquareTerminal, i18n("Waiting for command...")), isCustom: false };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { html } from "lit";
|
|
3
|
+
import { Calculator } from "lucide";
|
|
4
|
+
import { i18n } from "../../utils/i18n.js";
|
|
5
|
+
import { renderHeader } from "../renderer-registry.js";
|
|
6
|
+
import type { ToolRenderer, ToolRenderResult } from "../types.js";
|
|
7
|
+
|
|
8
|
+
interface CalculateParams {
|
|
9
|
+
expression: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Calculate tool has undefined details (only uses output)
|
|
13
|
+
export class CalculateRenderer implements ToolRenderer<CalculateParams, undefined> {
|
|
14
|
+
render(params: CalculateParams | undefined, result: ToolResultMessage<undefined> | undefined): ToolRenderResult {
|
|
15
|
+
const state = result ? (result.isError ? "error" : "complete") : "inprogress";
|
|
16
|
+
|
|
17
|
+
// Full params + full result
|
|
18
|
+
if (result && params?.expression) {
|
|
19
|
+
const output =
|
|
20
|
+
result.content
|
|
21
|
+
?.filter((c) => c.type === "text")
|
|
22
|
+
.map((c: any) => c.text)
|
|
23
|
+
.join("\n") || "";
|
|
24
|
+
|
|
25
|
+
// Error: show expression in header, error below
|
|
26
|
+
if (result.isError) {
|
|
27
|
+
return {
|
|
28
|
+
content: html`
|
|
29
|
+
<div class="space-y-3">
|
|
30
|
+
${renderHeader(state, Calculator, params.expression)}
|
|
31
|
+
<div class="text-sm text-destructive">${output}</div>
|
|
32
|
+
</div>
|
|
33
|
+
`,
|
|
34
|
+
isCustom: false,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Success: show expression = result in header
|
|
39
|
+
return { content: renderHeader(state, Calculator, `${params.expression} = ${output}`), isCustom: false };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Full params, no result: just show header with expression in it
|
|
43
|
+
if (params?.expression) {
|
|
44
|
+
return {
|
|
45
|
+
content: renderHeader(state, Calculator, `${i18n("Calculating")} ${params.expression}`),
|
|
46
|
+
isCustom: false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Partial params (empty expression), no result
|
|
51
|
+
if (params && !params.expression) {
|
|
52
|
+
return { content: renderHeader(state, Calculator, i18n("Writing expression...")), isCustom: false };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// No params, no result
|
|
56
|
+
return { content: renderHeader(state, Calculator, i18n("Waiting for expression...")), isCustom: false };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { html } from "lit";
|
|
3
|
+
import { Code } from "lucide";
|
|
4
|
+
import { i18n } from "../../utils/i18n.js";
|
|
5
|
+
import { renderHeader } from "../renderer-registry.js";
|
|
6
|
+
import type { ToolRenderer, ToolRenderResult } from "../types.js";
|
|
7
|
+
|
|
8
|
+
export class DefaultRenderer implements ToolRenderer {
|
|
9
|
+
render(params: any | undefined, result: ToolResultMessage | undefined, isStreaming?: boolean): ToolRenderResult {
|
|
10
|
+
const state = result ? (result.isError ? "error" : "complete") : isStreaming ? "inprogress" : "complete";
|
|
11
|
+
|
|
12
|
+
// Format params as JSON
|
|
13
|
+
let paramsJson = "";
|
|
14
|
+
if (params) {
|
|
15
|
+
try {
|
|
16
|
+
paramsJson = JSON.stringify(JSON.parse(params), null, 2);
|
|
17
|
+
} catch {
|
|
18
|
+
try {
|
|
19
|
+
paramsJson = JSON.stringify(params, null, 2);
|
|
20
|
+
} catch {
|
|
21
|
+
paramsJson = String(params);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// With result: show header + params + result
|
|
27
|
+
if (result) {
|
|
28
|
+
let outputJson =
|
|
29
|
+
result.content
|
|
30
|
+
?.filter((c) => c.type === "text")
|
|
31
|
+
.map((c: any) => c.text)
|
|
32
|
+
.join("\n") || i18n("(no output)");
|
|
33
|
+
let outputLanguage = "text";
|
|
34
|
+
|
|
35
|
+
// Try to parse and pretty-print if it's valid JSON
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(outputJson);
|
|
38
|
+
outputJson = JSON.stringify(parsed, null, 2);
|
|
39
|
+
outputLanguage = "json";
|
|
40
|
+
} catch {
|
|
41
|
+
// Not valid JSON, leave as-is and use text highlighting
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: html`
|
|
46
|
+
<div class="space-y-3">
|
|
47
|
+
${renderHeader(state, Code, "Tool Call")}
|
|
48
|
+
${
|
|
49
|
+
paramsJson
|
|
50
|
+
? html`<div>
|
|
51
|
+
<div class="text-xs font-medium mb-1 text-muted-foreground">${i18n("Input")}</div>
|
|
52
|
+
<code-block .code=${paramsJson} language="json"></code-block>
|
|
53
|
+
</div>`
|
|
54
|
+
: ""
|
|
55
|
+
}
|
|
56
|
+
<div>
|
|
57
|
+
<div class="text-xs font-medium mb-1 text-muted-foreground">${i18n("Output")}</div>
|
|
58
|
+
<code-block .code=${outputJson} language="${outputLanguage}"></code-block>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
`,
|
|
62
|
+
isCustom: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Just params (streaming or waiting for result)
|
|
67
|
+
if (params) {
|
|
68
|
+
if (isStreaming && (!paramsJson || paramsJson === "{}" || paramsJson === "null")) {
|
|
69
|
+
return {
|
|
70
|
+
content: html` <div>${renderHeader(state, Code, "Preparing tool parameters...")}</div> `,
|
|
71
|
+
isCustom: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
content: html`
|
|
77
|
+
<div class="space-y-3">
|
|
78
|
+
${renderHeader(state, Code, "Tool Call")}
|
|
79
|
+
<div>
|
|
80
|
+
<div class="text-xs font-medium mb-1 text-muted-foreground">${i18n("Input")}</div>
|
|
81
|
+
<code-block .code=${paramsJson} language="json"></code-block>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
`,
|
|
85
|
+
isCustom: false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// No params or result yet
|
|
90
|
+
return {
|
|
91
|
+
content: html` <div>${renderHeader(state, Code, "Preparing tool...")}</div> `,
|
|
92
|
+
isCustom: false,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { html } from "lit";
|
|
3
|
+
import { Clock } from "lucide";
|
|
4
|
+
import { i18n } from "../../utils/i18n.js";
|
|
5
|
+
import { renderHeader } from "../renderer-registry.js";
|
|
6
|
+
import type { ToolRenderer, ToolRenderResult } from "../types.js";
|
|
7
|
+
|
|
8
|
+
interface GetCurrentTimeParams {
|
|
9
|
+
timezone?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// GetCurrentTime tool has undefined details (only uses output)
|
|
13
|
+
export class GetCurrentTimeRenderer implements ToolRenderer<GetCurrentTimeParams, undefined> {
|
|
14
|
+
render(
|
|
15
|
+
params: GetCurrentTimeParams | undefined,
|
|
16
|
+
result: ToolResultMessage<undefined> | undefined,
|
|
17
|
+
): ToolRenderResult {
|
|
18
|
+
const state = result ? (result.isError ? "error" : "complete") : "inprogress";
|
|
19
|
+
|
|
20
|
+
// Full params + full result
|
|
21
|
+
if (result && params) {
|
|
22
|
+
const output =
|
|
23
|
+
result.content
|
|
24
|
+
?.filter((c) => c.type === "text")
|
|
25
|
+
.map((c: any) => c.text)
|
|
26
|
+
.join("\n") || "";
|
|
27
|
+
const headerText = params.timezone
|
|
28
|
+
? `${i18n("Getting current time in")} ${params.timezone}`
|
|
29
|
+
: i18n("Getting current date and time");
|
|
30
|
+
|
|
31
|
+
// Error: show header, error below
|
|
32
|
+
if (result.isError) {
|
|
33
|
+
return {
|
|
34
|
+
content: html`
|
|
35
|
+
<div class="space-y-3">
|
|
36
|
+
${renderHeader(state, Clock, headerText)}
|
|
37
|
+
<div class="text-sm text-destructive">${output}</div>
|
|
38
|
+
</div>
|
|
39
|
+
`,
|
|
40
|
+
isCustom: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Success: show time in header
|
|
45
|
+
return { content: renderHeader(state, Clock, `${headerText}: ${output}`), isCustom: false };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Full result, no params
|
|
49
|
+
if (result) {
|
|
50
|
+
const output =
|
|
51
|
+
result.content
|
|
52
|
+
?.filter((c) => c.type === "text")
|
|
53
|
+
.map((c: any) => c.text)
|
|
54
|
+
.join("\n") || "";
|
|
55
|
+
|
|
56
|
+
// Error: show header, error below
|
|
57
|
+
if (result.isError) {
|
|
58
|
+
return {
|
|
59
|
+
content: html`
|
|
60
|
+
<div class="space-y-3">
|
|
61
|
+
${renderHeader(state, Clock, i18n("Getting current date and time"))}
|
|
62
|
+
<div class="text-sm text-destructive">${output}</div>
|
|
63
|
+
</div>
|
|
64
|
+
`,
|
|
65
|
+
isCustom: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Success: show time in header
|
|
70
|
+
return {
|
|
71
|
+
content: renderHeader(state, Clock, `${i18n("Getting current date and time")}: ${output}`),
|
|
72
|
+
isCustom: false,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Full params, no result: show timezone info in header
|
|
77
|
+
if (params?.timezone) {
|
|
78
|
+
return {
|
|
79
|
+
content: renderHeader(state, Clock, `${i18n("Getting current time in")} ${params.timezone}`),
|
|
80
|
+
isCustom: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Partial params (no timezone) or empty params, no result
|
|
85
|
+
if (params) {
|
|
86
|
+
return { content: renderHeader(state, Clock, i18n("Getting current date and time")), isCustom: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// No params, no result
|
|
90
|
+
return { content: renderHeader(state, Clock, i18n("Getting time...")), isCustom: false };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
|
|
4
|
+
export interface ToolRenderResult {
|
|
5
|
+
content: TemplateResult;
|
|
6
|
+
isCustom: boolean; // true = no card wrapper, false = wrap in card
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ToolRenderer<TParams = any, TDetails = any> {
|
|
10
|
+
render(
|
|
11
|
+
params: TParams | undefined,
|
|
12
|
+
result: ToolResultMessage<TDetails> | undefined,
|
|
13
|
+
isStreaming?: boolean,
|
|
14
|
+
): ToolRenderResult;
|
|
15
|
+
}
|