@d4y/agent-runtime-nuxt 0.1.2 → 0.1.3
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/dist/module.d.mts +2 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -1
- package/dist/runtime/components/AgentRuntimeArtifactDialog.d.vue.ts +20 -0
- package/dist/runtime/components/AgentRuntimeArtifactDialog.vue +90 -0
- package/dist/runtime/components/AgentRuntimeArtifactDialog.vue.d.ts +20 -0
- package/dist/runtime/components/AgentRuntimeArtifactPreview.d.vue.ts +0 -2
- package/dist/runtime/components/AgentRuntimeArtifactPreview.vue +19 -64
- package/dist/runtime/components/AgentRuntimeArtifactPreview.vue.d.ts +0 -2
- package/dist/runtime/components/AgentRuntimeMarkdown.d.vue.ts +19 -0
- package/dist/runtime/components/AgentRuntimeMarkdown.vue +77 -0
- package/dist/runtime/components/AgentRuntimeMarkdown.vue.d.ts +19 -0
- package/dist/runtime/composables/useAgentRuntime.d.ts +3 -0
- package/dist/runtime/composables/useAgentRuntime.js +6 -0
- package/dist/runtime/composables/useAgentRuntimeMarkdown.d.ts +2 -7
- package/dist/runtime/composables/useAgentRuntimeMarkdown.js +94 -56
- package/dist/runtime/frontend.d.ts +4 -2
- package/dist/runtime/frontend.js +8 -2
- package/dist/runtime/server/api/conversations/[id]/files/preview/[...path].get.d.ts +2 -0
- package/dist/runtime/server/api/conversations/[id]/files/preview/[...path].get.js +42 -0
- package/dist/runtime/server/api/conversations/[id]/files.get.d.ts +1 -0
- package/dist/runtime/utils/files.d.ts +21 -3
- package/dist/runtime/utils/files.js +63 -18
- package/dist/types.d.mts +3 -1
- package/package.json +1 -1
package/dist/module.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
export { AppAuth, AppEnvField, AppInfo, ChatStatus, FileEntry, SendOptions, UIMessage, UiAction, UseAgentRuntime } from '../dist/runtime/composables/useAgentRuntime.js';
|
|
3
3
|
export { AgentRuntimeMarkdownRenderOptions } from '../dist/runtime/composables/useAgentRuntimeMarkdown.js';
|
|
4
|
-
export { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind } from '../dist/runtime/utils/files.js';
|
|
4
|
+
export { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind, AgentRuntimeResolvedAsset } from '../dist/runtime/utils/files.js';
|
|
5
|
+
export { createAgentRuntimeRequestHeaders, createScopeFingerprint, normalizeAgentRuntimeBaseUrl, resolveAgentRuntimeConfig } from '../dist/runtime/shared.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Module options for `@d4y/agent-runtime-nuxt`.
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addImportsDir, addTypeTemplate, addServerHandler } from '@nuxt/kit';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
|
+
export { createAgentRuntimeRequestHeaders, createScopeFingerprint, normalizeAgentRuntimeBaseUrl, resolveAgentRuntimeConfig } from '../dist/runtime/shared.js';
|
|
3
4
|
|
|
4
5
|
const DEFAULTS = {
|
|
5
6
|
baseUrl: "http://127.0.0.1:18791",
|
|
@@ -35,7 +36,7 @@ const module$1 = defineNuxtModule({
|
|
|
35
36
|
`// Auto-generated by @d4y/agent-runtime-nuxt`,
|
|
36
37
|
`export type { UseAgentRuntime, AppInfo, AppAuth, AppEnvField, FileEntry, UiAction, SendOptions, ChatStatus } from '${resolver.resolve("./runtime/composables/useAgentRuntime")}'`,
|
|
37
38
|
`export type { AgentRuntimeMarkdownRenderOptions } from '${resolver.resolve("./runtime/composables/useAgentRuntimeMarkdown")}'`,
|
|
38
|
-
`export type { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind } from '${resolver.resolve("./runtime/utils/files")}'`,
|
|
39
|
+
`export type { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind, AgentRuntimeResolvedAsset } from '${resolver.resolve("./runtime/utils/files")}'`,
|
|
39
40
|
""
|
|
40
41
|
].join("\n")
|
|
41
42
|
});
|
|
@@ -53,6 +54,7 @@ const module$1 = defineNuxtModule({
|
|
|
53
54
|
route("/conversations/:id/abort", "conversations/[id]/abort.post", "post");
|
|
54
55
|
route("/conversations/:id/env", "conversations/[id]/env.post", "post");
|
|
55
56
|
route("/conversations/:id/files", "conversations/[id]/files.get", "get");
|
|
57
|
+
route("/conversations/:id/files/preview/**", "conversations/[id]/files/preview/[...path].get", "get");
|
|
56
58
|
route("/conversations/:id/files/raw/**", "conversations/[id]/files/raw/[...path].get", "get");
|
|
57
59
|
}
|
|
58
60
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type AgentRuntimeFileLike } from '../utils/files.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
file?: AgentRuntimeFileLike | null;
|
|
5
|
+
title?: string | null;
|
|
6
|
+
previewSrc?: string | null;
|
|
7
|
+
downloadSrc?: string | null;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
|
+
"update:open": (open: boolean) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:open"?: ((open: boolean) => any) | undefined;
|
|
13
|
+
}>, {
|
|
14
|
+
file: AgentRuntimeFileLike | null;
|
|
15
|
+
title: string | null;
|
|
16
|
+
previewSrc: string | null;
|
|
17
|
+
downloadSrc: string | null;
|
|
18
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
19
|
+
declare const _default: typeof __VLS_export;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, onBeforeUnmount, watch } from "vue";
|
|
3
|
+
import AgentRuntimeArtifactPreview from "./AgentRuntimeArtifactPreview.vue";
|
|
4
|
+
import { getAgentRuntimeFilePreviewKind } from "../utils/files";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
open: { type: Boolean, required: true },
|
|
7
|
+
file: { type: [Object, null], required: false, default: null },
|
|
8
|
+
title: { type: [String, null], required: false, default: null },
|
|
9
|
+
previewSrc: { type: [String, null], required: false, default: null },
|
|
10
|
+
downloadSrc: { type: [String, null], required: false, default: null }
|
|
11
|
+
});
|
|
12
|
+
const emit = defineEmits(["update:open"]);
|
|
13
|
+
const previewKind = computed(() => props.file ? getAgentRuntimeFilePreviewKind(props.file) : "download");
|
|
14
|
+
const isPreviewable = computed(() => previewKind.value === "image" || previewKind.value === "html" || previewKind.value === "pdf");
|
|
15
|
+
const dialogTitle = computed(() => props.title ?? props.file?.name ?? props.file?.relPath ?? "Artifact preview");
|
|
16
|
+
const close = () => emit("update:open", false);
|
|
17
|
+
const onBackdropClick = () => close();
|
|
18
|
+
const onKeydown = (event) => {
|
|
19
|
+
if (event.key === "Escape") {
|
|
20
|
+
close();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
watch(() => props.open, (open) => {
|
|
24
|
+
if (!import.meta.client) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (open) {
|
|
28
|
+
window.addEventListener("keydown", onKeydown);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
window.removeEventListener("keydown", onKeydown);
|
|
32
|
+
}, { immediate: true });
|
|
33
|
+
onBeforeUnmount(() => {
|
|
34
|
+
if (!import.meta.client) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
window.removeEventListener("keydown", onKeydown);
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<Teleport to="body">
|
|
43
|
+
<Transition
|
|
44
|
+
enter-active-class="transition duration-200 ease-out"
|
|
45
|
+
enter-from-class="opacity-0"
|
|
46
|
+
enter-to-class="opacity-100"
|
|
47
|
+
leave-active-class="transition duration-150 ease-in"
|
|
48
|
+
leave-from-class="opacity-100"
|
|
49
|
+
leave-to-class="opacity-0">
|
|
50
|
+
<div
|
|
51
|
+
v-if="open"
|
|
52
|
+
class="agent-runtime-artifact-dialog"
|
|
53
|
+
aria-modal="true"
|
|
54
|
+
role="dialog"
|
|
55
|
+
:aria-label="dialogTitle"
|
|
56
|
+
@click="onBackdropClick">
|
|
57
|
+
<div class="agent-runtime-artifact-dialog__viewport">
|
|
58
|
+
<div
|
|
59
|
+
class="agent-runtime-artifact-dialog__surface"
|
|
60
|
+
:class="{ 'agent-runtime-artifact-dialog__surface--preview': isPreviewable }"
|
|
61
|
+
@click.stop>
|
|
62
|
+
<AgentRuntimeArtifactPreview
|
|
63
|
+
v-if="file"
|
|
64
|
+
:file="file"
|
|
65
|
+
:src="previewSrc" />
|
|
66
|
+
|
|
67
|
+
<div
|
|
68
|
+
v-if="downloadSrc && isPreviewable"
|
|
69
|
+
class="agent-runtime-artifact-dialog__actions">
|
|
70
|
+
<UButton
|
|
71
|
+
size="sm"
|
|
72
|
+
color="neutral"
|
|
73
|
+
variant="soft"
|
|
74
|
+
icon="material-symbols:download-rounded"
|
|
75
|
+
label="Download"
|
|
76
|
+
:href="downloadSrc"
|
|
77
|
+
external
|
|
78
|
+
:download="file?.name ?? file?.relPath ?? void 0"
|
|
79
|
+
target="_blank" />
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</Transition>
|
|
85
|
+
</Teleport>
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<style scoped>
|
|
89
|
+
.agent-runtime-artifact-dialog{backdrop-filter:blur(4px);background:rgba(24,24,27,.46);inset:0;position:fixed;z-index:200}.agent-runtime-artifact-dialog__viewport{align-items:center;display:flex;justify-content:center;min-height:100vh;padding:1rem;width:100%}.agent-runtime-artifact-dialog__surface{background:transparent;max-width:none;overflow:visible;position:relative;width:min(96vw,72rem)}.agent-runtime-artifact-dialog__surface--preview{align-items:center;display:flex;justify-content:center}.agent-runtime-artifact-dialog__actions{bottom:1rem;left:1rem;position:absolute;z-index:1}
|
|
90
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type AgentRuntimeFileLike } from '../utils/files.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
file?: AgentRuntimeFileLike | null;
|
|
5
|
+
title?: string | null;
|
|
6
|
+
previewSrc?: string | null;
|
|
7
|
+
downloadSrc?: string | null;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
|
+
"update:open": (open: boolean) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:open"?: ((open: boolean) => any) | undefined;
|
|
13
|
+
}>, {
|
|
14
|
+
file: AgentRuntimeFileLike | null;
|
|
15
|
+
title: string | null;
|
|
16
|
+
previewSrc: string | null;
|
|
17
|
+
downloadSrc: string | null;
|
|
18
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
19
|
+
declare const _default: typeof __VLS_export;
|
|
20
|
+
export default _default;
|
|
@@ -3,12 +3,10 @@ type __VLS_Props = {
|
|
|
3
3
|
file: AgentRuntimeFileLike;
|
|
4
4
|
src?: string | null;
|
|
5
5
|
resolveSrc?: ((relPath: string) => string | null | undefined) | null;
|
|
6
|
-
textMaxChars?: number;
|
|
7
6
|
};
|
|
8
7
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
9
8
|
src: string | null;
|
|
10
9
|
resolveSrc: ((relPath: string) => string | null | undefined) | null;
|
|
11
|
-
textMaxChars: number;
|
|
12
10
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
11
|
declare const _default: typeof __VLS_export;
|
|
14
12
|
export default _default;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed
|
|
2
|
+
import { computed } from "vue";
|
|
3
3
|
import { getAgentRuntimeFilePreviewKind } from "../utils/files";
|
|
4
4
|
const props = defineProps({
|
|
5
5
|
file: { type: Object, required: true },
|
|
6
6
|
src: { type: [String, null], required: false, default: null },
|
|
7
|
-
resolveSrc: { type: [Function, null], required: false, default: null }
|
|
8
|
-
textMaxChars: { type: Number, required: false, default: 12e4 }
|
|
7
|
+
resolveSrc: { type: [Function, null], required: false, default: null }
|
|
9
8
|
});
|
|
10
9
|
const kind = computed(() => getAgentRuntimeFilePreviewKind(props.file));
|
|
11
10
|
const resolvedSrc = computed(() => {
|
|
@@ -17,54 +16,6 @@ const resolvedSrc = computed(() => {
|
|
|
17
16
|
}
|
|
18
17
|
return props.resolveSrc(props.file.relPath) ?? null;
|
|
19
18
|
});
|
|
20
|
-
const textContent = ref("");
|
|
21
|
-
const textError = ref(null);
|
|
22
|
-
const textLoading = ref(false);
|
|
23
|
-
let textAbortController = null;
|
|
24
|
-
const abortTextLoad = () => {
|
|
25
|
-
textAbortController?.abort();
|
|
26
|
-
textAbortController = null;
|
|
27
|
-
};
|
|
28
|
-
const loadTextPreview = async () => {
|
|
29
|
-
abortTextLoad();
|
|
30
|
-
textContent.value = "";
|
|
31
|
-
textError.value = null;
|
|
32
|
-
if (kind.value !== "text" || !resolvedSrc.value) {
|
|
33
|
-
textLoading.value = false;
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const controller = new AbortController();
|
|
37
|
-
textAbortController = controller;
|
|
38
|
-
textLoading.value = true;
|
|
39
|
-
try {
|
|
40
|
-
const response = await fetch(resolvedSrc.value, {
|
|
41
|
-
signal: controller.signal
|
|
42
|
-
});
|
|
43
|
-
if (!response.ok) {
|
|
44
|
-
throw new Error(`Failed to load preview (${response.status})`);
|
|
45
|
-
}
|
|
46
|
-
const body = await response.text();
|
|
47
|
-
textContent.value = body.length > props.textMaxChars ? `${body.slice(0, props.textMaxChars).trimEnd()}
|
|
48
|
-
|
|
49
|
-
\u2026` : body;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
if (controller.signal.aborted) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
textError.value = error instanceof Error ? error.message : "Failed to load preview";
|
|
55
|
-
} finally {
|
|
56
|
-
if (textAbortController === controller) {
|
|
57
|
-
textAbortController = null;
|
|
58
|
-
}
|
|
59
|
-
textLoading.value = false;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
watch([kind, resolvedSrc], () => {
|
|
63
|
-
void loadTextPreview();
|
|
64
|
-
}, { immediate: true });
|
|
65
|
-
onBeforeUnmount(() => {
|
|
66
|
-
abortTextLoad();
|
|
67
|
-
});
|
|
68
19
|
</script>
|
|
69
20
|
|
|
70
21
|
<template>
|
|
@@ -76,22 +27,20 @@ onBeforeUnmount(() => {
|
|
|
76
27
|
class="agent-runtime-artifact-preview__image">
|
|
77
28
|
|
|
78
29
|
<iframe
|
|
79
|
-
v-else-if="kind === '
|
|
30
|
+
v-else-if="kind === 'html' && resolvedSrc"
|
|
80
31
|
:src="resolvedSrc"
|
|
81
|
-
:title="file.name ?? file.relPath ?? '
|
|
32
|
+
:title="file.name ?? file.relPath ?? 'Artifact preview'"
|
|
33
|
+
sandbox=""
|
|
34
|
+
referrerpolicy="no-referrer"
|
|
82
35
|
class="agent-runtime-artifact-preview__frame" />
|
|
83
36
|
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
{{ textError }}
|
|
90
|
-
</div>
|
|
91
|
-
<pre v-else class="agent-runtime-artifact-preview__text">{{ textContent }}</pre>
|
|
92
|
-
</div>
|
|
37
|
+
<iframe
|
|
38
|
+
v-else-if="kind === 'pdf' && resolvedSrc"
|
|
39
|
+
:src="resolvedSrc"
|
|
40
|
+
:title="file.name ?? file.relPath ?? 'Artifact preview'"
|
|
41
|
+
class="agent-runtime-artifact-preview__frame" />
|
|
93
42
|
|
|
94
|
-
<div v-else class="agent-runtime-artifact-preview__fallback">
|
|
43
|
+
<div v-else-if="kind !== 'blocked'" class="agent-runtime-artifact-preview__fallback">
|
|
95
44
|
<p class="agent-runtime-artifact-preview__note">
|
|
96
45
|
Preview unavailable for this file type.
|
|
97
46
|
</p>
|
|
@@ -104,9 +53,15 @@ onBeforeUnmount(() => {
|
|
|
104
53
|
Open file
|
|
105
54
|
</a>
|
|
106
55
|
</div>
|
|
56
|
+
|
|
57
|
+
<div v-else class="agent-runtime-artifact-preview__fallback">
|
|
58
|
+
<p class="agent-runtime-artifact-preview__note">
|
|
59
|
+
This file type is not available in the user-facing UI.
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
107
62
|
</div>
|
|
108
63
|
</template>
|
|
109
64
|
|
|
110
65
|
<style scoped>
|
|
111
|
-
.agent-runtime-artifact-preview{display:block;min-height:12rem;width:100%}.agent-runtime-artifact-preview__frame,.agent-runtime-artifact-preview__image{background:rgba(15,23,42,.04);border:0;border-radius:.875rem;display:block;width:100%}.agent-runtime-artifact-preview__image{max-height:min(70vh,48rem);-o-object-fit:contain;object-fit:contain}.agent-runtime-artifact-preview__frame{min-height:min(
|
|
66
|
+
.agent-runtime-artifact-preview{display:block;min-height:12rem;width:100%}.agent-runtime-artifact-preview__frame,.agent-runtime-artifact-preview__image{background:rgba(15,23,42,.04);border:0;border-radius:.875rem;display:block;width:100%}.agent-runtime-artifact-preview__image{max-height:min(70vh,48rem);-o-object-fit:contain;object-fit:contain}.agent-runtime-artifact-preview__frame{min-height:min(68vh,48rem)}.agent-runtime-artifact-preview__fallback{background:rgba(15,23,42,.03);border:1px solid rgba(15,23,42,.1);border-radius:.875rem;min-height:18rem}.agent-runtime-artifact-preview__note{color:rgba(15,23,42,.72);font-size:.875rem;padding:1rem}.agent-runtime-artifact-preview__link{color:inherit;display:inline-flex;margin:0 1rem 1rem;text-decoration:underline;text-underline-offset:.18em}
|
|
112
67
|
</style>
|
|
@@ -3,12 +3,10 @@ type __VLS_Props = {
|
|
|
3
3
|
file: AgentRuntimeFileLike;
|
|
4
4
|
src?: string | null;
|
|
5
5
|
resolveSrc?: ((relPath: string) => string | null | undefined) | null;
|
|
6
|
-
textMaxChars?: number;
|
|
7
6
|
};
|
|
8
7
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
9
8
|
src: string | null;
|
|
10
9
|
resolveSrc: ((relPath: string) => string | null | undefined) | null;
|
|
11
|
-
textMaxChars: number;
|
|
12
10
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
11
|
declare const _default: typeof __VLS_export;
|
|
14
12
|
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
text: string;
|
|
3
|
+
class?: string | Record<string, boolean> | string[];
|
|
4
|
+
streaming?: boolean;
|
|
5
|
+
resolveWorkspacePath?: ((relPath: string) => string | null | undefined) | null;
|
|
6
|
+
resolveWorkspacePreviewPath?: ((relPath: string) => string | null | undefined) | null;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
renderedChange: (rendered: boolean) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
11
|
+
onRenderedChange?: ((rendered: boolean) => any) | undefined;
|
|
12
|
+
}>, {
|
|
13
|
+
class: string | Record<string, boolean> | string[];
|
|
14
|
+
streaming: boolean;
|
|
15
|
+
resolveWorkspacePath: ((relPath: string) => string | null | undefined) | null;
|
|
16
|
+
resolveWorkspacePreviewPath: ((relPath: string) => string | null | undefined) | null;
|
|
17
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, nextTick, ref, watch } from "vue";
|
|
3
|
+
import { useAgentRuntimeMarkdown } from "../composables/useAgentRuntimeMarkdown";
|
|
4
|
+
import AgentRuntimeArtifactDialog from "./AgentRuntimeArtifactDialog.vue";
|
|
5
|
+
const emit = defineEmits(["renderedChange"]);
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
text: { type: String, required: true },
|
|
8
|
+
class: { type: [String, Object, Array], required: false, default: "" },
|
|
9
|
+
streaming: { type: Boolean, required: false, default: false },
|
|
10
|
+
resolveWorkspacePath: { type: [Function, null], required: false, default: null },
|
|
11
|
+
resolveWorkspacePreviewPath: { type: [Function, null], required: false, default: null }
|
|
12
|
+
});
|
|
13
|
+
const { render } = useAgentRuntimeMarkdown({
|
|
14
|
+
resolveWorkspacePath: (relPath) => props.resolveWorkspacePath?.(relPath) ?? null,
|
|
15
|
+
resolveWorkspacePreviewPath: (relPath) => props.resolveWorkspacePreviewPath?.(relPath) ?? null
|
|
16
|
+
});
|
|
17
|
+
const html = computed(() => props.text ? render(props.text) : "");
|
|
18
|
+
const dialogOpen = ref(false);
|
|
19
|
+
const dialogFile = ref(null);
|
|
20
|
+
const dialogPreviewSrc = ref(null);
|
|
21
|
+
const dialogDownloadSrc = ref(null);
|
|
22
|
+
const rendered = computed(() => html.value.length > 0);
|
|
23
|
+
const onClick = (event) => {
|
|
24
|
+
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
25
|
+
const trigger = target?.closest('[data-agent-runtime-preview="true"]');
|
|
26
|
+
if (!trigger) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const previewUrl = trigger.dataset.agentRuntimePreviewUrl;
|
|
30
|
+
const kind = trigger.dataset.agentRuntimePreviewKind;
|
|
31
|
+
const label = trigger.dataset.agentRuntimePreviewLabel;
|
|
32
|
+
if (!previewUrl || !kind) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
dialogFile.value = {
|
|
37
|
+
name: label ?? void 0,
|
|
38
|
+
relPath: null,
|
|
39
|
+
mimeType: kind === "image" ? "image/*" : kind === "pdf" ? "application/pdf" : "text/html",
|
|
40
|
+
kind
|
|
41
|
+
};
|
|
42
|
+
dialogPreviewSrc.value = previewUrl;
|
|
43
|
+
dialogDownloadSrc.value = trigger.dataset.agentRuntimeDownloadUrl ?? previewUrl;
|
|
44
|
+
dialogOpen.value = true;
|
|
45
|
+
};
|
|
46
|
+
watch(rendered, async (value) => {
|
|
47
|
+
if (!value) {
|
|
48
|
+
emit("renderedChange", false);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
await nextTick();
|
|
52
|
+
emit("renderedChange", true);
|
|
53
|
+
}, { immediate: true });
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<div
|
|
58
|
+
v-if="rendered"
|
|
59
|
+
class="agent-runtime-markdown"
|
|
60
|
+
:class="[props.class, streaming ? 'agent-runtime-markdown--streaming' : '']"
|
|
61
|
+
@click="onClick"
|
|
62
|
+
v-html="
|
|
63
|
+
html
|
|
64
|
+
/* eslint-disable-line vue/no-v-html -- output is escaped by markdown-it; raw HTML disabled */
|
|
65
|
+
" />
|
|
66
|
+
|
|
67
|
+
<AgentRuntimeArtifactDialog
|
|
68
|
+
v-model:open="dialogOpen"
|
|
69
|
+
:file="dialogFile"
|
|
70
|
+
:title="dialogFile?.name ?? dialogFile?.relPath ?? null"
|
|
71
|
+
:preview-src="dialogPreviewSrc"
|
|
72
|
+
:download-src="dialogDownloadSrc" />
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<style scoped>
|
|
76
|
+
.agent-runtime-markdown{line-height:1.65}.agent-runtime-markdown--streaming{animation:agent-runtime-markdown-pulse .16s ease-out}.agent-runtime-markdown :deep(.agent-runtime-md-image-link){cursor:zoom-in;display:inline-block}.agent-runtime-markdown :deep(.agent-runtime-md-image){border-radius:.95rem;display:block;max-height:20rem;max-width:min(100%,30rem);-o-object-fit:contain;object-fit:contain}.agent-runtime-markdown :deep(.agent-runtime-md-file-link){color:inherit;text-decoration:underline;text-underline-offset:.18em}.agent-runtime-markdown :deep(.agent-runtime-md-html-shell),.agent-runtime-markdown :deep(.agent-runtime-md-pdf-shell){background:color-mix(in srgb,var(--ui-bg-elevated,rgba(248,250,252,.9)) 82%,transparent);border:1px solid color-mix(in srgb,var(--ui-border,rgba(148,163,184,.28)) 80%,transparent);border-radius:1rem;display:block;margin-top:.75rem;overflow:hidden}.agent-runtime-markdown :deep(.agent-runtime-md-embed-toolbar){align-items:center;border-bottom:1px solid color-mix(in srgb,var(--ui-border,rgba(148,163,184,.28)) 70%,transparent);display:flex;flex-wrap:wrap;font-size:.75rem;gap:.75rem;justify-content:space-between;padding:.75rem .9rem}.agent-runtime-markdown :deep(.agent-runtime-md-embed-label){font-family:var(--font-mono,ui-monospace,monospace);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-runtime-markdown :deep(.agent-runtime-md-embed-actions){display:inline-flex;flex-wrap:wrap;gap:.5rem}.agent-runtime-markdown :deep(.agent-runtime-md-download-link),.agent-runtime-markdown :deep(.agent-runtime-md-preview-link){color:inherit;text-decoration:underline;text-underline-offset:.18em}.agent-runtime-markdown :deep(.agent-runtime-md-preview-link[data-agent-runtime-preview=true]){cursor:zoom-in}.agent-runtime-markdown :deep(.agent-runtime-md-html-frame),.agent-runtime-markdown :deep(.agent-runtime-md-pdf-frame){background:color-mix(in srgb,var(--ui-bg,hsla(0,0%,100%,.94)) 85%,transparent);border:0;display:block;height:22rem;width:100%}@keyframes agent-runtime-markdown-pulse{0%{opacity:.88;transform:translateY(1px)}to{opacity:1;transform:translateY(0)}}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
text: string;
|
|
3
|
+
class?: string | Record<string, boolean> | string[];
|
|
4
|
+
streaming?: boolean;
|
|
5
|
+
resolveWorkspacePath?: ((relPath: string) => string | null | undefined) | null;
|
|
6
|
+
resolveWorkspacePreviewPath?: ((relPath: string) => string | null | undefined) | null;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
renderedChange: (rendered: boolean) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
11
|
+
onRenderedChange?: ((rendered: boolean) => any) | undefined;
|
|
12
|
+
}>, {
|
|
13
|
+
class: string | Record<string, boolean> | string[];
|
|
14
|
+
streaming: boolean;
|
|
15
|
+
resolveWorkspacePath: ((relPath: string) => string | null | undefined) | null;
|
|
16
|
+
resolveWorkspacePreviewPath: ((relPath: string) => string | null | undefined) | null;
|
|
17
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
@@ -45,6 +45,7 @@ export interface FileEntry {
|
|
|
45
45
|
size: number;
|
|
46
46
|
mtime: string;
|
|
47
47
|
mimeType: string;
|
|
48
|
+
kind: 'image' | 'html' | 'pdf' | 'download';
|
|
48
49
|
}
|
|
49
50
|
/** Per-turn options for `send`. */
|
|
50
51
|
export interface SendOptions {
|
|
@@ -89,6 +90,8 @@ export interface UseAgentRuntime {
|
|
|
89
90
|
files: Ref<FileEntry[]>;
|
|
90
91
|
/** Build the proxied download URL for a workspace-relative path. */
|
|
91
92
|
fileUrl: (relPath: string) => string;
|
|
93
|
+
/** Build the proxied preview URL for a workspace-relative path. */
|
|
94
|
+
filePreviewUrl: (relPath: string) => string;
|
|
92
95
|
/** Manually re-fetch the workspace file listing. */
|
|
93
96
|
refreshFiles: () => Promise<void>;
|
|
94
97
|
/** Persist a new auth map; if a conversation is open, push the change to its env. */
|
|
@@ -61,6 +61,11 @@ export const useAgentRuntime = () => {
|
|
|
61
61
|
if (!cid) return "";
|
|
62
62
|
return `${apiPrefix}/conversations/${cid}/files/raw/${encodeRelPath(relPath)}`;
|
|
63
63
|
};
|
|
64
|
+
const filePreviewUrl = (relPath) => {
|
|
65
|
+
const cid = conversationId.value;
|
|
66
|
+
if (!cid) return "";
|
|
67
|
+
return `${apiPrefix}/conversations/${cid}/files/preview/${encodeRelPath(relPath)}`;
|
|
68
|
+
};
|
|
64
69
|
const refreshFiles = async () => {
|
|
65
70
|
const cid = conversationId.value;
|
|
66
71
|
if (!cid) {
|
|
@@ -296,6 +301,7 @@ export const useAgentRuntime = () => {
|
|
|
296
301
|
authReady,
|
|
297
302
|
files,
|
|
298
303
|
fileUrl,
|
|
304
|
+
filePreviewUrl,
|
|
299
305
|
refreshFiles,
|
|
300
306
|
saveAuth,
|
|
301
307
|
start,
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import MarkdownIt from 'markdown-it';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Resolve a workspace-relative path (e.g. `outputs/foo.pdf`) to a fetchable
|
|
5
|
-
* URL on the runtime backend. Returns null/empty if the path can't be
|
|
6
|
-
* resolved, in which case the renderer falls back to the original text.
|
|
7
|
-
*/
|
|
8
|
-
resolveWorkspacePath?: (relPath: string) => string | null | undefined;
|
|
2
|
+
import { type AgentRuntimeAssetResolverOptions } from '../utils/files.js';
|
|
3
|
+
export interface AgentRuntimeMarkdownRenderOptions extends AgentRuntimeAssetResolverOptions {
|
|
9
4
|
}
|
|
10
5
|
export declare const createAgentRuntimeMarkdownRenderer: (options?: AgentRuntimeMarkdownRenderOptions) => MarkdownIt;
|
|
11
6
|
export declare const useAgentRuntimeMarkdown: (options?: AgentRuntimeMarkdownRenderOptions) => {
|
|
@@ -1,10 +1,70 @@
|
|
|
1
1
|
import MarkdownIt from "markdown-it";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { resolveAgentRuntimeWorkspaceAsset, resolveAgentRuntimeWorkspaceUri } from "../utils/files.js";
|
|
3
|
+
const escapeAttr = (md, value) => md.utils.escapeHtml(value);
|
|
4
|
+
const previewAttrs = (md, asset) => {
|
|
5
|
+
if (!asset.canPreview || !asset.previewUrl) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
const attrs = [
|
|
9
|
+
["data-agent-runtime-preview", "true"],
|
|
10
|
+
["data-agent-runtime-preview-kind", asset.kind],
|
|
11
|
+
["data-agent-runtime-preview-label", asset.label],
|
|
12
|
+
["data-agent-runtime-preview-url", asset.previewUrl]
|
|
13
|
+
];
|
|
14
|
+
if (asset.canDownload && asset.rawUrl) {
|
|
15
|
+
attrs.push(["data-agent-runtime-download-url", asset.rawUrl]);
|
|
16
|
+
}
|
|
17
|
+
return attrs.map(([name, value]) => `${name}="${escapeAttr(md, value)}"`).join(" ");
|
|
18
|
+
};
|
|
19
|
+
const buildDownloadLinkMarkup = (md, asset, label) => {
|
|
20
|
+
if (!asset.canDownload || !asset.rawUrl) {
|
|
21
|
+
return escapeAttr(md, label);
|
|
22
|
+
}
|
|
23
|
+
return `<a href="${escapeAttr(md, asset.rawUrl)}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-file-link">${escapeAttr(md, label)}</a>`;
|
|
24
|
+
};
|
|
25
|
+
const buildImageMarkup = (md, asset, alt, title) => {
|
|
26
|
+
const previewUrl = asset.previewUrl ?? asset.rawUrl;
|
|
27
|
+
if (!previewUrl) {
|
|
28
|
+
return escapeAttr(md, alt || asset.label);
|
|
29
|
+
}
|
|
30
|
+
const safeAlt = escapeAttr(md, alt || asset.label);
|
|
31
|
+
const safePreviewUrl = escapeAttr(md, previewUrl);
|
|
32
|
+
const titleAttr = title ? ` title="${escapeAttr(md, title)}"` : "";
|
|
33
|
+
const attrs = previewAttrs(md, asset);
|
|
34
|
+
return `<a href="${safePreviewUrl}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-image-link" ${attrs}><img src="${safePreviewUrl}" alt="${safeAlt}" loading="lazy" class="agent-runtime-md-image"${titleAttr} /></a>`;
|
|
35
|
+
};
|
|
36
|
+
const buildPreviewShellMarkup = (md, asset, label) => {
|
|
37
|
+
const previewUrl = asset.previewUrl;
|
|
38
|
+
if (!previewUrl) {
|
|
39
|
+
return buildDownloadLinkMarkup(md, asset, label);
|
|
40
|
+
}
|
|
41
|
+
const safeLabel = escapeAttr(md, label);
|
|
42
|
+
const safePreviewUrl = escapeAttr(md, previewUrl);
|
|
43
|
+
const attrs = previewAttrs(md, asset);
|
|
44
|
+
const shellClass = asset.kind === "pdf" ? "agent-runtime-md-pdf-shell" : "agent-runtime-md-html-shell";
|
|
45
|
+
const frameClass = asset.kind === "pdf" ? "agent-runtime-md-pdf-frame" : "agent-runtime-md-html-frame";
|
|
46
|
+
const frameAttrs = asset.kind === "pdf" ? "" : ' sandbox="" referrerpolicy="no-referrer"';
|
|
47
|
+
const title = asset.kind === "pdf" ? `PDF preview: ${label}` : `HTML preview: ${label}`;
|
|
48
|
+
const downloadButton = asset.canDownload && asset.rawUrl ? `<a href="${escapeAttr(md, asset.rawUrl)}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-download-link">Download</a>` : "";
|
|
49
|
+
return `<span class="${shellClass}"><span class="agent-runtime-md-embed-toolbar"><span class="agent-runtime-md-embed-label">${safeLabel}</span><span class="agent-runtime-md-embed-actions"><a href="${safePreviewUrl}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-preview-link" ${attrs}>Preview</a>${downloadButton}</span></span><iframe src="${safePreviewUrl}" title="${escapeAttr(md, title)}" loading="lazy"${frameAttrs} class="${frameClass}"></iframe></span>`;
|
|
50
|
+
};
|
|
51
|
+
const toHtmlToken = (state, markup) => {
|
|
52
|
+
const token = new state.Token("html_inline", "", 0);
|
|
53
|
+
token.content = markup;
|
|
54
|
+
return token;
|
|
55
|
+
};
|
|
56
|
+
const renderWorkspaceLink = (md, asset, label) => {
|
|
57
|
+
if (asset.kind === "blocked") {
|
|
58
|
+
return escapeAttr(md, label);
|
|
59
|
+
}
|
|
60
|
+
if (asset.kind === "image") {
|
|
61
|
+
return buildImageMarkup(md, asset, label);
|
|
62
|
+
}
|
|
63
|
+
if (asset.kind === "html" || asset.kind === "pdf") {
|
|
64
|
+
return buildPreviewShellMarkup(md, asset, label);
|
|
65
|
+
}
|
|
66
|
+
return buildDownloadLinkMarkup(md, asset, label);
|
|
67
|
+
};
|
|
8
68
|
export const createAgentRuntimeMarkdownRenderer = (options = {}) => {
|
|
9
69
|
const md = new MarkdownIt({
|
|
10
70
|
html: false,
|
|
@@ -12,15 +72,15 @@ export const createAgentRuntimeMarkdownRenderer = (options = {}) => {
|
|
|
12
72
|
breaks: true,
|
|
13
73
|
typographer: false
|
|
14
74
|
});
|
|
15
|
-
const
|
|
75
|
+
const resolveAsset = (uri) => resolveAgentRuntimeWorkspaceAsset(uri, options);
|
|
16
76
|
const defaultLinkOpen = md.renderer.rules.link_open ?? ((tokens, idx, opts, _env, self) => self.renderToken(tokens, idx, opts));
|
|
17
77
|
md.renderer.rules.link_open = (tokens, idx, opts, env, self) => {
|
|
18
78
|
const token = tokens[idx];
|
|
19
79
|
if (token) {
|
|
20
80
|
const href = token.attrGet("href") ?? "";
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
token.attrSet("href",
|
|
81
|
+
const resolved = resolveAgentRuntimeWorkspaceUri(href, options.resolveWorkspacePath);
|
|
82
|
+
if (resolved) {
|
|
83
|
+
token.attrSet("href", resolved);
|
|
24
84
|
token.attrSet("target", "_blank");
|
|
25
85
|
token.attrSet("rel", "noopener noreferrer");
|
|
26
86
|
token.attrSet("class", "agent-runtime-md-file-link");
|
|
@@ -34,64 +94,42 @@ export const createAgentRuntimeMarkdownRenderer = (options = {}) => {
|
|
|
34
94
|
md.renderer.rules.image = (tokens, idx) => {
|
|
35
95
|
const token = tokens[idx];
|
|
36
96
|
if (!token) return "";
|
|
37
|
-
const
|
|
38
|
-
const rewritten = resolve(rawSrc);
|
|
39
|
-
const src = rewritten ?? rawSrc;
|
|
97
|
+
const src = token.attrGet("src") ?? "";
|
|
40
98
|
const alt = token.content || "";
|
|
41
99
|
const title = token.attrGet("title");
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return `<
|
|
100
|
+
const asset = resolveAsset(src);
|
|
101
|
+
if (!asset) {
|
|
102
|
+
const safeSrc = escapeAttr(md, src);
|
|
103
|
+
const safeAlt = escapeAttr(md, alt);
|
|
104
|
+
const titleAttr = title ? ` title="${escapeAttr(md, title)}"` : "";
|
|
105
|
+
return `<img src="${safeSrc}" alt="${safeAlt}" loading="lazy" class="agent-runtime-md-image"${titleAttr} />`;
|
|
106
|
+
}
|
|
107
|
+
if (asset.kind === "image") {
|
|
108
|
+
return buildImageMarkup(md, asset, alt, title);
|
|
48
109
|
}
|
|
49
|
-
return
|
|
110
|
+
return renderWorkspaceLink(md, asset, alt || asset.label);
|
|
50
111
|
};
|
|
51
|
-
md.core.ruler.after("inline", "
|
|
112
|
+
md.core.ruler.after("inline", "agent_runtime_workspace_links", (state) => {
|
|
52
113
|
for (const blockToken of state.tokens) {
|
|
53
114
|
if (blockToken.type !== "inline" || !blockToken.children) continue;
|
|
54
115
|
const out = [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
116
|
+
const children = blockToken.children;
|
|
117
|
+
for (let idx = 0; idx < children.length; idx++) {
|
|
118
|
+
const child = children[idx];
|
|
119
|
+
if (!child) {
|
|
58
120
|
continue;
|
|
59
121
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
122
|
+
const next = children[idx + 1];
|
|
123
|
+
const nextNext = children[idx + 2];
|
|
124
|
+
const href = child.type === "link_open" && typeof child.attrGet === "function" ? child.attrGet("href") ?? "" : "";
|
|
125
|
+
const asset = href ? resolveAsset(href) : null;
|
|
126
|
+
const isWorkspaceLink = asset && child.type === "link_open" && next?.type === "text" && nextNext?.type === "link_close";
|
|
127
|
+
if (!isWorkspaceLink) {
|
|
63
128
|
out.push(child);
|
|
64
129
|
continue;
|
|
65
130
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let match;
|
|
69
|
-
while ((match = BARE_WORKSPACE_PATH_RE.exec(text)) !== null) {
|
|
70
|
-
const uri = match[0];
|
|
71
|
-
const url = resolve(uri);
|
|
72
|
-
if (!url) continue;
|
|
73
|
-
if (match.index > lastIndex) {
|
|
74
|
-
const before = new state.Token("text", "", 0);
|
|
75
|
-
before.content = text.slice(lastIndex, match.index);
|
|
76
|
-
out.push(before);
|
|
77
|
-
}
|
|
78
|
-
const open = new state.Token("link_open", "a", 1);
|
|
79
|
-
open.attrSet("href", url);
|
|
80
|
-
open.attrSet("target", "_blank");
|
|
81
|
-
open.attrSet("rel", "noopener noreferrer");
|
|
82
|
-
open.attrSet("class", "agent-runtime-md-file-link");
|
|
83
|
-
const inner = new state.Token("text", "", 0);
|
|
84
|
-
const relPath = toAgentRuntimeWorkspaceRelativePath(uri) ?? uri;
|
|
85
|
-
inner.content = relPath.split("/").pop() || relPath;
|
|
86
|
-
const close = new state.Token("link_close", "a", -1);
|
|
87
|
-
out.push(open, inner, close);
|
|
88
|
-
lastIndex = match.index + uri.length;
|
|
89
|
-
}
|
|
90
|
-
if (lastIndex < text.length) {
|
|
91
|
-
const tail = new state.Token("text", "", 0);
|
|
92
|
-
tail.content = text.slice(lastIndex);
|
|
93
|
-
out.push(tail);
|
|
94
|
-
}
|
|
131
|
+
out.push(toHtmlToken(state, renderWorkspaceLink(md, asset, next.content || asset.label)));
|
|
132
|
+
idx += 2;
|
|
95
133
|
}
|
|
96
134
|
blockToken.children = out;
|
|
97
135
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type { AgentRuntimeMarkdownRenderOptions } from './composables/useAgentRuntimeMarkdown.js';
|
|
2
|
-
export type { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind } from './utils/files.js';
|
|
2
|
+
export type { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind, AgentRuntimeResolvedAsset } from './utils/files.js';
|
|
3
|
+
export { default as AgentRuntimeArtifactDialog } from './components/AgentRuntimeArtifactDialog.vue.js';
|
|
3
4
|
export { default as AgentRuntimeArtifactPreview } from './components/AgentRuntimeArtifactPreview.vue.js';
|
|
5
|
+
export { default as AgentRuntimeMarkdown } from './components/AgentRuntimeMarkdown.vue.js';
|
|
4
6
|
export { createAgentRuntimeMarkdownRenderer, useAgentRuntimeMarkdown } from './composables/useAgentRuntimeMarkdown.js';
|
|
5
|
-
export { canPreviewAgentRuntimeFileInline, getAgentRuntimeFilePreviewKind, isAgentRuntimeImageMimeType, isAgentRuntimeImagePath, isAgentRuntimePdfMimeType, isAgentRuntimePdfPath,
|
|
7
|
+
export { canPreviewAgentRuntimeFileInline, isAgentRuntimeBlockedMimeType, isAgentRuntimeBlockedPath, isAgentRuntimeHtmlMimeType, isAgentRuntimeHtmlPath, getAgentRuntimeFilePreviewKind, isAgentRuntimeImageMimeType, isAgentRuntimeImagePath, isAgentRuntimePdfMimeType, isAgentRuntimePdfPath, resolveAgentRuntimeFileAsset, resolveAgentRuntimeWorkspaceUri, resolveAgentRuntimeWorkspaceAsset, toAgentRuntimeWorkspaceRelativePath, } from './utils/files.js';
|
package/dist/runtime/frontend.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
export { default as AgentRuntimeArtifactDialog } from "./components/AgentRuntimeArtifactDialog.vue";
|
|
1
2
|
export { default as AgentRuntimeArtifactPreview } from "./components/AgentRuntimeArtifactPreview.vue";
|
|
3
|
+
export { default as AgentRuntimeMarkdown } from "./components/AgentRuntimeMarkdown.vue";
|
|
2
4
|
export { createAgentRuntimeMarkdownRenderer, useAgentRuntimeMarkdown } from "./composables/useAgentRuntimeMarkdown.js";
|
|
3
5
|
export {
|
|
4
6
|
canPreviewAgentRuntimeFileInline,
|
|
7
|
+
isAgentRuntimeBlockedMimeType,
|
|
8
|
+
isAgentRuntimeBlockedPath,
|
|
9
|
+
isAgentRuntimeHtmlMimeType,
|
|
10
|
+
isAgentRuntimeHtmlPath,
|
|
5
11
|
getAgentRuntimeFilePreviewKind,
|
|
6
12
|
isAgentRuntimeImageMimeType,
|
|
7
13
|
isAgentRuntimeImagePath,
|
|
8
14
|
isAgentRuntimePdfMimeType,
|
|
9
15
|
isAgentRuntimePdfPath,
|
|
10
|
-
|
|
11
|
-
isAgentRuntimeTextPath,
|
|
16
|
+
resolveAgentRuntimeFileAsset,
|
|
12
17
|
resolveAgentRuntimeWorkspaceUri,
|
|
18
|
+
resolveAgentRuntimeWorkspaceAsset,
|
|
13
19
|
toAgentRuntimeWorkspaceRelativePath
|
|
14
20
|
} from "./utils/files.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { createError, defineEventHandler, getRouterParam, sendStream, setHeader } from "h3";
|
|
4
|
+
import { agentRuntime } from "../../../../../utils/agent-runtime.js";
|
|
5
|
+
const PASS_THROUGH_HEADERS = [
|
|
6
|
+
"content-type",
|
|
7
|
+
"content-length",
|
|
8
|
+
"content-disposition",
|
|
9
|
+
"cache-control",
|
|
10
|
+
"content-security-policy",
|
|
11
|
+
"x-content-type-options"
|
|
12
|
+
];
|
|
13
|
+
export default defineEventHandler(async (event) => {
|
|
14
|
+
const cfg = agentRuntime();
|
|
15
|
+
const id = getRouterParam(event, "id");
|
|
16
|
+
if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
|
|
17
|
+
const apiPrefix = (useRuntimeConfig().public.agentRuntime?.apiPrefix ?? "/api/agent-runtime").replace(/\/+$/, "");
|
|
18
|
+
const marker = `${apiPrefix}/conversations/${id}/files/preview/`;
|
|
19
|
+
const idx = event.path.indexOf(marker);
|
|
20
|
+
const tail = idx >= 0 ? event.path.slice(idx + marker.length) : "";
|
|
21
|
+
if (!tail) throw createError({ statusCode: 400, statusMessage: "missing file path" });
|
|
22
|
+
const upstreamUrl = `${cfg.baseUrl}/v1/conversations/${id}/files/preview/${tail}`;
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
event.node.req.on("close", () => controller.abort());
|
|
25
|
+
const upstream = await fetch(upstreamUrl, {
|
|
26
|
+
headers: { "X-Agent-Runtime-App-Key": cfg.appKey },
|
|
27
|
+
signal: controller.signal
|
|
28
|
+
});
|
|
29
|
+
if (!upstream.ok || !upstream.body) {
|
|
30
|
+
throw createError({
|
|
31
|
+
statusCode: upstream.status || 502,
|
|
32
|
+
statusMessage: `agent-runtime file preview failed: ${upstream.status} ${upstream.statusText}`
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
for (const name of PASS_THROUGH_HEADERS) {
|
|
36
|
+
const value = upstream.headers.get(name);
|
|
37
|
+
if (value) {
|
|
38
|
+
setHeader(event, name, value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return sendStream(event, Readable.fromWeb(upstream.body));
|
|
42
|
+
});
|
|
@@ -2,15 +2,33 @@ export interface AgentRuntimeFileLike {
|
|
|
2
2
|
name?: string | null;
|
|
3
3
|
relPath?: string | null;
|
|
4
4
|
mimeType?: string | null;
|
|
5
|
+
kind?: AgentRuntimeFilePreviewKind | null;
|
|
6
|
+
}
|
|
7
|
+
export type AgentRuntimeFilePreviewKind = 'image' | 'html' | 'pdf' | 'download' | 'blocked';
|
|
8
|
+
export interface AgentRuntimeResolvedAsset extends AgentRuntimeFileLike {
|
|
9
|
+
kind: AgentRuntimeFilePreviewKind;
|
|
10
|
+
label: string;
|
|
11
|
+
relPath: string | null;
|
|
12
|
+
rawUrl: string | null;
|
|
13
|
+
previewUrl: string | null;
|
|
14
|
+
canPreview: boolean;
|
|
15
|
+
canDownload: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface AgentRuntimeAssetResolverOptions {
|
|
18
|
+
resolveWorkspacePath?: (relPath: string) => string | null | undefined;
|
|
19
|
+
resolveWorkspacePreviewPath?: (relPath: string) => string | null | undefined;
|
|
5
20
|
}
|
|
6
|
-
export type AgentRuntimeFilePreviewKind = 'image' | 'pdf' | 'text' | 'download';
|
|
7
21
|
export declare const toAgentRuntimeWorkspaceRelativePath: (uri: string) => string | null;
|
|
8
22
|
export declare const resolveAgentRuntimeWorkspaceUri: (uri: string, resolveWorkspacePath?: (relPath: string) => string | null | undefined) => string | null;
|
|
23
|
+
export declare const isAgentRuntimeBlockedMimeType: (mimeType: string | null | undefined) => boolean;
|
|
9
24
|
export declare const isAgentRuntimeImageMimeType: (mimeType: string | null | undefined) => boolean;
|
|
25
|
+
export declare const isAgentRuntimeHtmlMimeType: (mimeType: string | null | undefined) => boolean;
|
|
10
26
|
export declare const isAgentRuntimePdfMimeType: (mimeType: string | null | undefined) => boolean;
|
|
11
|
-
export declare const isAgentRuntimeTextMimeType: (mimeType: string | null | undefined) => boolean;
|
|
12
27
|
export declare const isAgentRuntimeImagePath: (value: string | null | undefined) => boolean;
|
|
28
|
+
export declare const isAgentRuntimeHtmlPath: (value: string | null | undefined) => boolean;
|
|
13
29
|
export declare const isAgentRuntimePdfPath: (value: string | null | undefined) => boolean;
|
|
14
|
-
export declare const
|
|
30
|
+
export declare const isAgentRuntimeBlockedPath: (value: string | null | undefined) => boolean;
|
|
15
31
|
export declare const getAgentRuntimeFilePreviewKind: (file: AgentRuntimeFileLike) => AgentRuntimeFilePreviewKind;
|
|
16
32
|
export declare const canPreviewAgentRuntimeFileInline: (file: AgentRuntimeFileLike) => boolean;
|
|
33
|
+
export declare const resolveAgentRuntimeFileAsset: (file: AgentRuntimeFileLike, options?: AgentRuntimeAssetResolverOptions) => AgentRuntimeResolvedAsset;
|
|
34
|
+
export declare const resolveAgentRuntimeWorkspaceAsset: (uri: string, options?: AgentRuntimeAssetResolverOptions) => AgentRuntimeResolvedAsset | null;
|
|
@@ -1,42 +1,87 @@
|
|
|
1
1
|
const IMAGE_EXT_RE = /\.(?:png|jpe?g|gif|webp|svg|avif|bmp)(?:[?#].*)?$/i;
|
|
2
|
+
const HTML_EXT_RE = /\.html?(?:[?#].*)?$/i;
|
|
2
3
|
const PDF_EXT_RE = /\.pdf(?:[?#].*)?$/i;
|
|
3
|
-
const
|
|
4
|
+
const DOWNLOAD_EXT_RE = /\.(?:txt|md|json|csv|tsv|xml|ya?ml|log)(?:[?#].*)?$/i;
|
|
5
|
+
const BLOCKED_EXT_RE = /\.(?:py|rb|pl|php|sh|bash|zsh|fish|ps1|bat|cmd|js|mjs|cjs|ts|tsx|jsx|vue)(?:[?#].*)?$/i;
|
|
6
|
+
const BLOCKED_MIME_RE = /^(?:application\/javascript|application\/typescript|application\/x-httpd-php|application\/x-python-code|application\/x-sh|text\/javascript|text\/x-python|text\/x-script\.python|text\/x-shellscript|text\/x-typescript)(?:$|;)/i;
|
|
7
|
+
const PREVIEWABLE_KINDS = /* @__PURE__ */ new Set(["image", "html", "pdf"]);
|
|
8
|
+
const FILE_KIND_ORDER = ["image", "html", "pdf", "download", "blocked"];
|
|
9
|
+
const basename = (value) => value.split("/").pop() || value;
|
|
10
|
+
const normalizeKind = (value) => typeof value === "string" && FILE_KIND_ORDER.includes(value) ? value : null;
|
|
4
11
|
export const toAgentRuntimeWorkspaceRelativePath = (uri) => {
|
|
5
|
-
if (!uri)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return null;
|
|
12
|
+
if (!uri.startsWith("sandbox:")) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const relPath = uri.slice("sandbox:".length).replace(/^\/+/, "");
|
|
16
|
+
return relPath.length > 0 ? relPath : null;
|
|
11
17
|
};
|
|
12
18
|
export const resolveAgentRuntimeWorkspaceUri = (uri, resolveWorkspacePath) => {
|
|
13
19
|
const relPath = toAgentRuntimeWorkspaceRelativePath(uri);
|
|
14
|
-
if (relPath === null)
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
if (relPath === null) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return resolveWorkspacePath?.(relPath) ?? null;
|
|
17
24
|
};
|
|
25
|
+
export const isAgentRuntimeBlockedMimeType = (mimeType) => typeof mimeType === "string" && BLOCKED_MIME_RE.test(mimeType);
|
|
18
26
|
export const isAgentRuntimeImageMimeType = (mimeType) => typeof mimeType === "string" && /^image\//i.test(mimeType);
|
|
27
|
+
export const isAgentRuntimeHtmlMimeType = (mimeType) => typeof mimeType === "string" && /^text\/html(?:$|;)/i.test(mimeType);
|
|
19
28
|
export const isAgentRuntimePdfMimeType = (mimeType) => typeof mimeType === "string" && mimeType.toLowerCase() === "application/pdf";
|
|
20
|
-
export const
|
|
29
|
+
export const isAgentRuntimeImagePath = (value) => typeof value === "string" && IMAGE_EXT_RE.test(value);
|
|
30
|
+
export const isAgentRuntimeHtmlPath = (value) => typeof value === "string" && HTML_EXT_RE.test(value);
|
|
31
|
+
export const isAgentRuntimePdfPath = (value) => typeof value === "string" && PDF_EXT_RE.test(value);
|
|
32
|
+
export const isAgentRuntimeBlockedPath = (value) => typeof value === "string" && BLOCKED_EXT_RE.test(value);
|
|
33
|
+
const isAgentRuntimeDownloadMimeType = (mimeType) => {
|
|
21
34
|
if (typeof mimeType !== "string" || !mimeType) {
|
|
22
35
|
return false;
|
|
23
36
|
}
|
|
24
37
|
const normalized = mimeType.toLowerCase();
|
|
25
|
-
return normalized.startsWith("text/") || normalized === "application/json" || normalized === "application/ld+json" || normalized === "application/xml" || normalized === "application/yaml" || normalized === "application/x-yaml";
|
|
38
|
+
return normalized.startsWith("text/") && !isAgentRuntimeHtmlMimeType(normalized) && !isAgentRuntimeBlockedMimeType(normalized) || normalized === "application/json" || normalized === "application/ld+json" || normalized === "application/xml" || normalized === "application/yaml" || normalized === "application/x-yaml";
|
|
26
39
|
};
|
|
27
|
-
|
|
28
|
-
export const isAgentRuntimePdfPath = (value) => typeof value === "string" && PDF_EXT_RE.test(value);
|
|
29
|
-
export const isAgentRuntimeTextPath = (value) => typeof value === "string" && TEXT_EXT_RE.test(value);
|
|
40
|
+
const isAgentRuntimeDownloadPath = (value) => typeof value === "string" && DOWNLOAD_EXT_RE.test(value);
|
|
30
41
|
export const getAgentRuntimeFilePreviewKind = (file) => {
|
|
42
|
+
const explicitKind = normalizeKind(file.kind);
|
|
43
|
+
if (explicitKind) {
|
|
44
|
+
return explicitKind;
|
|
45
|
+
}
|
|
46
|
+
if (isAgentRuntimeBlockedMimeType(file.mimeType) || isAgentRuntimeBlockedPath(file.name) || isAgentRuntimeBlockedPath(file.relPath)) {
|
|
47
|
+
return "blocked";
|
|
48
|
+
}
|
|
31
49
|
if (isAgentRuntimeImageMimeType(file.mimeType) || isAgentRuntimeImagePath(file.name) || isAgentRuntimeImagePath(file.relPath)) {
|
|
32
50
|
return "image";
|
|
33
51
|
}
|
|
52
|
+
if (isAgentRuntimeHtmlMimeType(file.mimeType) || isAgentRuntimeHtmlPath(file.name) || isAgentRuntimeHtmlPath(file.relPath)) {
|
|
53
|
+
return "html";
|
|
54
|
+
}
|
|
34
55
|
if (isAgentRuntimePdfMimeType(file.mimeType) || isAgentRuntimePdfPath(file.name) || isAgentRuntimePdfPath(file.relPath)) {
|
|
35
56
|
return "pdf";
|
|
36
57
|
}
|
|
37
|
-
if (
|
|
38
|
-
return "
|
|
58
|
+
if (isAgentRuntimeDownloadMimeType(file.mimeType) || isAgentRuntimeDownloadPath(file.name) || isAgentRuntimeDownloadPath(file.relPath)) {
|
|
59
|
+
return "download";
|
|
39
60
|
}
|
|
40
61
|
return "download";
|
|
41
62
|
};
|
|
42
|
-
export const canPreviewAgentRuntimeFileInline = (file) => getAgentRuntimeFilePreviewKind(file)
|
|
63
|
+
export const canPreviewAgentRuntimeFileInline = (file) => PREVIEWABLE_KINDS.has(getAgentRuntimeFilePreviewKind(file));
|
|
64
|
+
export const resolveAgentRuntimeFileAsset = (file, options = {}) => {
|
|
65
|
+
const relPath = file.relPath ?? null;
|
|
66
|
+
const kind = getAgentRuntimeFilePreviewKind(file);
|
|
67
|
+
const label = file.name ?? (relPath ? basename(relPath) : "Attachment");
|
|
68
|
+
const rawUrl = relPath ? options.resolveWorkspacePath?.(relPath) || null : null;
|
|
69
|
+
const previewUrl = relPath && PREVIEWABLE_KINDS.has(kind) ? options.resolveWorkspacePreviewPath?.(relPath) || rawUrl : null;
|
|
70
|
+
return {
|
|
71
|
+
...file,
|
|
72
|
+
kind,
|
|
73
|
+
label,
|
|
74
|
+
relPath,
|
|
75
|
+
rawUrl,
|
|
76
|
+
previewUrl,
|
|
77
|
+
canPreview: kind !== "blocked" && previewUrl !== null,
|
|
78
|
+
canDownload: kind !== "blocked" && rawUrl !== null
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
export const resolveAgentRuntimeWorkspaceAsset = (uri, options = {}) => {
|
|
82
|
+
const relPath = toAgentRuntimeWorkspaceRelativePath(uri);
|
|
83
|
+
if (relPath === null) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return resolveAgentRuntimeFileAsset({ relPath, name: basename(relPath) }, options);
|
|
87
|
+
};
|
package/dist/types.d.mts
CHANGED
|
@@ -2,7 +2,9 @@ export { type AppAuth, type AppEnvField, type AppInfo, type ChatStatus, type Fil
|
|
|
2
2
|
|
|
3
3
|
export { type AgentRuntimeMarkdownRenderOptions } from '../dist/runtime/composables/useAgentRuntimeMarkdown.js'
|
|
4
4
|
|
|
5
|
-
export { type AgentRuntimeFileLike, type AgentRuntimeFilePreviewKind } from '../dist/runtime/utils/files.js'
|
|
5
|
+
export { type AgentRuntimeFileLike, type AgentRuntimeFilePreviewKind, type AgentRuntimeResolvedAsset } from '../dist/runtime/utils/files.js'
|
|
6
|
+
|
|
7
|
+
export { type createAgentRuntimeRequestHeaders, type createScopeFingerprint, type normalizeAgentRuntimeBaseUrl, type resolveAgentRuntimeConfig } from '../dist/runtime/shared.js'
|
|
6
8
|
|
|
7
9
|
export { default } from './module.mjs'
|
|
8
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d4y/agent-runtime-nuxt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Headless Nuxt module that connects a Nuxt app to an agent-runtime server. Ships server-side proxy routes (so your X-Agent-Runtime-App-Key never leaves the server) and a single composable, useAgentRuntime(), that exposes a typed chat client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|