@meistrari/chat-nuxt 4.10.0-rc.1 → 4.10.0-rc.2
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.json +1 -1
- package/dist/runtime/files/components/{audio-preview.d.vue.ts → chat-file-audio.d.vue.ts} +8 -7
- package/dist/runtime/files/components/chat-file-audio.vue +94 -0
- package/dist/runtime/files/components/{image-preview.d.vue.ts → chat-file-audio.vue.d.ts} +8 -7
- package/dist/runtime/files/components/chat-file-card.d.vue.ts +31 -0
- package/dist/runtime/files/components/chat-file-card.vue +44 -0
- package/dist/runtime/files/components/chat-file-card.vue.d.ts +31 -0
- package/dist/runtime/files/components/chat-file-preview.vue +90 -196
- package/dist/runtime/files/composables/pdf.d.ts +5 -842
- package/dist/runtime/files/content-detection.js +7 -3
- package/dist/runtime/files/formatting.d.ts +1 -0
- package/dist/runtime/files/formatting.js +4 -0
- package/dist/runtime/files/presentation.d.ts +3 -0
- package/dist/runtime/files/presentation.js +24 -0
- package/dist/runtime/messages/answer.d.ts +0 -5
- package/dist/runtime/messages/answer.js +0 -18
- package/dist/runtime/messages/components/chat-answer.vue +4 -3
- package/dist/runtime/messages/components/chat-message-bubble.vue +12 -0
- package/dist/runtime/messages/components/widgets/chat-thinking-widget.vue +4 -0
- package/dist/runtime/rendering/components/openui/bar-chart.d.vue.ts +10 -0
- package/dist/runtime/rendering/components/openui/bar-chart.vue +39 -0
- package/dist/runtime/rendering/components/openui/bar-chart.vue.d.ts +10 -0
- package/dist/runtime/rendering/components/openui/bar.d.vue.ts +10 -0
- package/dist/runtime/rendering/components/openui/bar.vue +12 -0
- package/dist/runtime/rendering/components/openui/bar.vue.d.ts +10 -0
- package/dist/runtime/rendering/components/openui/button.d.vue.ts +11 -0
- package/dist/runtime/rendering/components/openui/button.vue +30 -0
- package/dist/runtime/rendering/components/openui/button.vue.d.ts +11 -0
- package/dist/runtime/rendering/components/openui/buttons.d.vue.ts +8 -0
- package/dist/runtime/rendering/components/openui/buttons.vue +14 -0
- package/dist/runtime/rendering/components/openui/buttons.vue.d.ts +8 -0
- package/dist/runtime/rendering/components/openui/callout.d.vue.ts +11 -0
- package/dist/runtime/rendering/components/openui/callout.vue +27 -0
- package/dist/runtime/rendering/components/openui/callout.vue.d.ts +11 -0
- package/dist/runtime/rendering/components/openui/card.d.vue.ts +9 -0
- package/dist/runtime/rendering/components/openui/card.vue +19 -0
- package/dist/runtime/rendering/components/openui/card.vue.d.ts +9 -0
- package/dist/runtime/rendering/components/openui/checkbox.d.vue.ts +9 -0
- package/dist/runtime/rendering/components/openui/checkbox.vue +26 -0
- package/dist/runtime/rendering/components/openui/checkbox.vue.d.ts +9 -0
- package/dist/runtime/rendering/components/openui/citation.d.vue.ts +9 -0
- package/dist/runtime/rendering/components/openui/citation.vue +13 -0
- package/dist/runtime/rendering/components/openui/citation.vue.d.ts +9 -0
- package/dist/runtime/rendering/components/openui/col.d.vue.ts +10 -0
- package/dist/runtime/rendering/components/openui/col.vue +16 -0
- package/dist/runtime/rendering/components/openui/col.vue.d.ts +10 -0
- package/dist/runtime/rendering/components/openui/file-card.d.vue.ts +9 -0
- package/dist/runtime/rendering/components/openui/file-card.vue +15 -0
- package/dist/runtime/rendering/components/openui/file-card.vue.d.ts +9 -0
- package/dist/runtime/rendering/components/openui/form.d.vue.ts +10 -0
- package/dist/runtime/rendering/components/openui/form.vue +60 -0
- package/dist/runtime/rendering/components/openui/form.vue.d.ts +10 -0
- package/dist/runtime/rendering/components/openui/image.d.vue.ts +10 -0
- package/dist/runtime/rendering/components/openui/image.vue +40 -0
- package/dist/runtime/rendering/components/openui/image.vue.d.ts +10 -0
- package/dist/runtime/rendering/components/openui/input.d.vue.ts +10 -0
- package/dist/runtime/rendering/components/openui/input.vue +23 -0
- package/dist/runtime/rendering/components/openui/input.vue.d.ts +10 -0
- package/dist/runtime/rendering/components/openui/markdown.d.vue.ts +9 -0
- package/dist/runtime/rendering/components/openui/markdown.vue +14 -0
- package/dist/runtime/rendering/components/openui/markdown.vue.d.ts +9 -0
- package/dist/runtime/rendering/components/openui/metric.d.vue.ts +11 -0
- package/dist/runtime/rendering/components/openui/metric.vue +20 -0
- package/dist/runtime/rendering/components/openui/metric.vue.d.ts +11 -0
- package/dist/runtime/rendering/components/openui/option.d.vue.ts +10 -0
- package/dist/runtime/rendering/components/openui/option.vue +12 -0
- package/dist/runtime/rendering/components/openui/option.vue.d.ts +10 -0
- package/dist/runtime/rendering/components/openui/question.d.vue.ts +11 -0
- package/dist/runtime/rendering/components/openui/question.vue +15 -0
- package/dist/runtime/rendering/components/openui/question.vue.d.ts +11 -0
- package/dist/runtime/rendering/components/openui/questions.d.vue.ts +9 -0
- package/dist/runtime/rendering/components/openui/questions.vue +91 -0
- package/dist/runtime/rendering/components/openui/questions.vue.d.ts +9 -0
- package/dist/runtime/rendering/components/openui/select.d.vue.ts +10 -0
- package/dist/runtime/rendering/components/openui/select.vue +27 -0
- package/dist/runtime/rendering/components/openui/select.vue.d.ts +10 -0
- package/dist/runtime/rendering/components/openui/stack.d.vue.ts +9 -0
- package/dist/runtime/rendering/components/openui/stack.vue +18 -0
- package/dist/runtime/rendering/components/openui/stack.vue.d.ts +9 -0
- package/dist/runtime/rendering/components/openui/table.d.vue.ts +9 -0
- package/dist/runtime/rendering/components/openui/table.vue +41 -0
- package/dist/runtime/rendering/components/openui/table.vue.d.ts +9 -0
- package/dist/runtime/rendering/components/openui-block.d.vue.ts +12 -0
- package/dist/runtime/rendering/components/openui-block.vue +73 -0
- package/dist/runtime/rendering/components/openui-block.vue.d.ts +12 -0
- package/dist/runtime/rendering/components/openui-reasoning-placeholder.d.vue.ts +3 -0
- package/dist/runtime/rendering/components/openui-reasoning-placeholder.vue +10 -0
- package/dist/runtime/rendering/components/openui-reasoning-placeholder.vue.d.ts +3 -0
- package/dist/runtime/rendering/components/renderers/artifact-display.vue +1 -1
- package/dist/runtime/rendering/openui/actions.d.ts +15 -0
- package/dist/runtime/rendering/openui/actions.js +40 -0
- package/dist/runtime/rendering/openui/context.d.ts +22 -0
- package/dist/runtime/rendering/openui/context.js +4 -0
- package/dist/runtime/rendering/openui/form-fields.d.ts +10 -0
- package/dist/runtime/rendering/openui/form-fields.js +30 -0
- package/dist/runtime/rendering/openui/markdown-link.d.ts +2 -0
- package/dist/runtime/rendering/openui/markdown-link.js +5 -0
- package/dist/runtime/rendering/openui/program.d.ts +15 -0
- package/dist/runtime/rendering/openui/program.js +74 -0
- package/dist/runtime/rendering/openui/registry.d.ts +5 -0
- package/dist/runtime/rendering/openui/registry.js +44 -0
- package/dist/runtime/rendering/openui/render.d.ts +21 -0
- package/dist/runtime/rendering/openui/render.js +45 -0
- package/dist/runtime/rendering/openui/use-openui-context.d.ts +2 -0
- package/dist/runtime/rendering/openui/use-openui-context.js +8 -0
- package/dist/runtime/rendering/openui/use-widget-prose.d.ts +2 -0
- package/dist/runtime/rendering/openui/use-widget-prose.js +7 -0
- package/dist/runtime/shared/composables/polling.d.ts +1 -1
- package/dist/runtime/types/schemas/chat/conversation-runtime.d.ts +12 -0
- package/dist/runtime/types/schemas/chat/conversations.d.ts +12 -0
- package/dist/runtime/types/schemas/chat/conversations.js +3 -0
- package/dist/runtime/types/schemas/chat/messages.d.ts +18 -0
- package/dist/runtime/types/schemas/chat/ui-language.d.ts +14 -0
- package/dist/runtime/types/schemas/chat/ui-language.js +8 -0
- package/package.json +3 -1
- package/dist/runtime/files/components/audio-preview.vue +0 -52
- package/dist/runtime/files/components/audio-preview.vue.d.ts +0 -14
- package/dist/runtime/files/components/csv-preview.d.vue.ts +0 -20
- package/dist/runtime/files/components/csv-preview.vue +0 -157
- package/dist/runtime/files/components/csv-preview.vue.d.ts +0 -20
- package/dist/runtime/files/components/image-preview.vue +0 -54
- package/dist/runtime/files/components/image-preview.vue.d.ts +0 -14
- package/dist/runtime/files/components/pdf-preview.client.d.vue.ts +0 -20
- package/dist/runtime/files/components/pdf-preview.client.vue +0 -90
- package/dist/runtime/files/components/pdf-preview.client.vue.d.ts +0 -20
- package/dist/runtime/files/components/text-preview.d.vue.ts +0 -19
- package/dist/runtime/files/components/text-preview.vue +0 -105
- package/dist/runtime/files/components/text-preview.vue.d.ts +0 -19
- package/dist/runtime/files/components/video-preview.d.vue.ts +0 -14
- package/dist/runtime/files/components/video-preview.vue +0 -55
- package/dist/runtime/files/components/video-preview.vue.d.ts +0 -14
package/dist/module.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import type { ChatFileMenuItem } from './chat-file-card.vue.js';
|
|
1
2
|
type __VLS_Props = {
|
|
2
|
-
url
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
url?: string;
|
|
4
|
+
iconSrc: string;
|
|
5
|
+
name: string;
|
|
6
|
+
meta: string;
|
|
7
|
+
menuItems: ChatFileMenuItem[];
|
|
5
8
|
};
|
|
6
9
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
7
|
-
|
|
8
|
-
loaded: () => any;
|
|
10
|
+
open: () => any;
|
|
9
11
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
-
|
|
11
|
-
onLoaded?: (() => any) | undefined;
|
|
12
|
+
onOpen?: (() => any) | undefined;
|
|
12
13
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
14
|
declare const _default: typeof __VLS_export;
|
|
14
15
|
export default _default;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import ChatFileCard from "./chat-file-card.vue";
|
|
3
|
+
import { formatPlaybackTime } from "../formatting";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
url: { type: String, required: false },
|
|
6
|
+
iconSrc: { type: String, required: true },
|
|
7
|
+
name: { type: String, required: true },
|
|
8
|
+
meta: { type: String, required: true },
|
|
9
|
+
menuItems: { type: Array, required: true }
|
|
10
|
+
});
|
|
11
|
+
const emit = defineEmits(["open"]);
|
|
12
|
+
const player = ref();
|
|
13
|
+
const playing = ref(false);
|
|
14
|
+
const currentTime = ref(0);
|
|
15
|
+
const duration = ref(0);
|
|
16
|
+
const failed = ref(false);
|
|
17
|
+
const progress = computed(() => duration.value > 0 ? currentTime.value / duration.value * 100 : 0);
|
|
18
|
+
function togglePlayback() {
|
|
19
|
+
const audio = player.value;
|
|
20
|
+
if (!audio)
|
|
21
|
+
return;
|
|
22
|
+
if (audio.paused)
|
|
23
|
+
void audio.play();
|
|
24
|
+
else
|
|
25
|
+
audio.pause();
|
|
26
|
+
}
|
|
27
|
+
function seek(event) {
|
|
28
|
+
const audio = player.value;
|
|
29
|
+
if (!audio || duration.value <= 0)
|
|
30
|
+
return;
|
|
31
|
+
audio.currentTime = Number(event.target.value) / 100 * duration.value;
|
|
32
|
+
}
|
|
33
|
+
watch(() => props.url, () => {
|
|
34
|
+
playing.value = false;
|
|
35
|
+
currentTime.value = 0;
|
|
36
|
+
duration.value = 0;
|
|
37
|
+
failed.value = false;
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<ChatFileCard
|
|
43
|
+
:icon-src="iconSrc"
|
|
44
|
+
:name="name"
|
|
45
|
+
:meta="failed ? 'Falha ao carregar \xE1udio' : meta"
|
|
46
|
+
:menu-items="menuItems"
|
|
47
|
+
:disabled="!url"
|
|
48
|
+
@open="emit('open')"
|
|
49
|
+
>
|
|
50
|
+
<template #actions>
|
|
51
|
+
<TelaIconButton
|
|
52
|
+
:icon="playing ? 'i-ph-pause-fill' : 'i-ph-play-fill'"
|
|
53
|
+
color="secondary"
|
|
54
|
+
size="xs"
|
|
55
|
+
:aria-label="playing ? 'Pausar \xE1udio' : 'Reproduzir \xE1udio'"
|
|
56
|
+
:disabled="!url || failed"
|
|
57
|
+
@click="togglePlayback"
|
|
58
|
+
/>
|
|
59
|
+
</template>
|
|
60
|
+
<div flex items-center gap-12px px-12px py-10px border-t-0.5px border>
|
|
61
|
+
<input
|
|
62
|
+
type="range"
|
|
63
|
+
min="0"
|
|
64
|
+
max="100"
|
|
65
|
+
step="0.1"
|
|
66
|
+
:value="progress"
|
|
67
|
+
:disabled="!url || failed || duration <= 0"
|
|
68
|
+
aria-label="Posição do áudio"
|
|
69
|
+
flex-1 cursor-pointer text-primary
|
|
70
|
+
class="chat-file-audio__track"
|
|
71
|
+
@input="seek"
|
|
72
|
+
>
|
|
73
|
+
<span body-12-regular text-secondary tabular-nums shrink-0>
|
|
74
|
+
{{ formatPlaybackTime(currentTime) }} / {{ formatPlaybackTime(duration) }}
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
<audio
|
|
78
|
+
v-if="url"
|
|
79
|
+
ref="player"
|
|
80
|
+
:src="url"
|
|
81
|
+
preload="metadata"
|
|
82
|
+
@play="playing = true"
|
|
83
|
+
@pause="playing = false"
|
|
84
|
+
@ended="playing = false"
|
|
85
|
+
@timeupdate="currentTime = player?.currentTime ?? 0"
|
|
86
|
+
@loadedmetadata="duration = player?.duration ?? 0"
|
|
87
|
+
@error="failed = true"
|
|
88
|
+
/>
|
|
89
|
+
</ChatFileCard>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
92
|
+
<style scoped>
|
|
93
|
+
.chat-file-audio__track{accent-color:currentColor;height:4px}
|
|
94
|
+
</style>
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import type { ChatFileMenuItem } from './chat-file-card.vue.js';
|
|
1
2
|
type __VLS_Props = {
|
|
2
|
-
url
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
url?: string;
|
|
4
|
+
iconSrc: string;
|
|
5
|
+
name: string;
|
|
6
|
+
meta: string;
|
|
7
|
+
menuItems: ChatFileMenuItem[];
|
|
5
8
|
};
|
|
6
9
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
7
|
-
|
|
8
|
-
loaded: () => any;
|
|
10
|
+
open: () => any;
|
|
9
11
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
-
|
|
11
|
-
onLoaded?: (() => any) | undefined;
|
|
12
|
+
onOpen?: (() => any) | undefined;
|
|
12
13
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
14
|
declare const _default: typeof __VLS_export;
|
|
14
15
|
export default _default;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ChatFileMenuItem {
|
|
2
|
+
label: string;
|
|
3
|
+
icon: string;
|
|
4
|
+
click: () => void;
|
|
5
|
+
}
|
|
6
|
+
type __VLS_Props = {
|
|
7
|
+
iconSrc: string;
|
|
8
|
+
name: string;
|
|
9
|
+
meta: string;
|
|
10
|
+
menuItems: ChatFileMenuItem[];
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare var __VLS_1: {}, __VLS_14: {};
|
|
14
|
+
type __VLS_Slots = {} & {
|
|
15
|
+
actions?: (props: typeof __VLS_1) => any;
|
|
16
|
+
} & {
|
|
17
|
+
default?: (props: typeof __VLS_14) => any;
|
|
18
|
+
};
|
|
19
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
20
|
+
open: () => any;
|
|
21
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
22
|
+
onOpen?: (() => any) | undefined;
|
|
23
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
25
|
+
declare const _default: typeof __VLS_export;
|
|
26
|
+
export default _default;
|
|
27
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
28
|
+
new (): {
|
|
29
|
+
$slots: S;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
iconSrc: { type: String, required: true },
|
|
4
|
+
name: { type: String, required: true },
|
|
5
|
+
meta: { type: String, required: true },
|
|
6
|
+
menuItems: { type: Array, required: true },
|
|
7
|
+
disabled: { type: Boolean, required: false }
|
|
8
|
+
});
|
|
9
|
+
const emit = defineEmits(["open"]);
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div w-full max-w-480px rounded-12px border-0.5px border bg overflow-hidden data-chat-file="card">
|
|
14
|
+
<div flex items-center gap-12px p-12px>
|
|
15
|
+
<button
|
|
16
|
+
type="button"
|
|
17
|
+
flex items-center gap-12px flex-1 min-w-0 text-left
|
|
18
|
+
:class="disabled ? 'cursor-default' : 'cursor-pointer'"
|
|
19
|
+
:disabled="disabled"
|
|
20
|
+
:aria-label="`Abrir ${name}`"
|
|
21
|
+
@click="emit('open')"
|
|
22
|
+
>
|
|
23
|
+
<span size-40px rounded-8px bg-subtle flex items-center justify-center shrink-0>
|
|
24
|
+
<img :src="iconSrc" alt="" size-20px>
|
|
25
|
+
</span>
|
|
26
|
+
<span flex flex-col min-w-0>
|
|
27
|
+
<span body-14-medium text-primary truncate :title="name">{{ name }}</span>
|
|
28
|
+
<span body-12-regular text-secondary truncate>{{ meta }}</span>
|
|
29
|
+
</span>
|
|
30
|
+
</button>
|
|
31
|
+
<slot name="actions" />
|
|
32
|
+
<TelaDropdownMenu :items="menuItems" align="end">
|
|
33
|
+
<TelaIconButton
|
|
34
|
+
icon="i-ph-dots-three"
|
|
35
|
+
color="secondary"
|
|
36
|
+
size="3xs"
|
|
37
|
+
aria-label="Ações do arquivo"
|
|
38
|
+
:disabled="disabled"
|
|
39
|
+
/>
|
|
40
|
+
</TelaDropdownMenu>
|
|
41
|
+
</div>
|
|
42
|
+
<slot />
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ChatFileMenuItem {
|
|
2
|
+
label: string;
|
|
3
|
+
icon: string;
|
|
4
|
+
click: () => void;
|
|
5
|
+
}
|
|
6
|
+
type __VLS_Props = {
|
|
7
|
+
iconSrc: string;
|
|
8
|
+
name: string;
|
|
9
|
+
meta: string;
|
|
10
|
+
menuItems: ChatFileMenuItem[];
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare var __VLS_1: {}, __VLS_14: {};
|
|
14
|
+
type __VLS_Slots = {} & {
|
|
15
|
+
actions?: (props: typeof __VLS_1) => any;
|
|
16
|
+
} & {
|
|
17
|
+
default?: (props: typeof __VLS_14) => any;
|
|
18
|
+
};
|
|
19
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
20
|
+
open: () => any;
|
|
21
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
22
|
+
onOpen?: (() => any) | undefined;
|
|
23
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
25
|
+
declare const _default: typeof __VLS_export;
|
|
26
|
+
export default _default;
|
|
27
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
28
|
+
new (): {
|
|
29
|
+
$slots: S;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
import { useFilePreviewPanel } from "../composables/file-preview-panel";
|
|
3
3
|
import { useMediaCache } from "../composables/media-cache";
|
|
4
4
|
import { useVault } from "../composables/vault";
|
|
5
|
+
import ChatFileAudio from "./chat-file-audio.vue";
|
|
6
|
+
import ChatFileCard from "./chat-file-card.vue";
|
|
5
7
|
import { FILE_ICONS } from "../icons";
|
|
6
8
|
import { MIME_TO_EXTENSION, normalizeMimeType } from "../mime";
|
|
9
|
+
import { describeFile, resolveFilePresentation } from "../presentation";
|
|
7
10
|
import { isVaultPermalink } from "../vault-links";
|
|
8
11
|
const props = defineProps({
|
|
9
12
|
file: { type: Object, required: true }
|
|
@@ -11,32 +14,17 @@ const props = defineProps({
|
|
|
11
14
|
const { getFileInfo, isVaultReference } = useVault();
|
|
12
15
|
const { getCached, setCache, fetchUrlWithMetadata, base64ToDataUrl } = useMediaCache();
|
|
13
16
|
const { openPreview } = useFilePreviewPanel();
|
|
14
|
-
const FILE_COLORS = {
|
|
15
|
-
default: { bg: "bg-gray-100", text: "text-gray-600", border: "border-gray-200" }
|
|
16
|
-
};
|
|
17
17
|
const resolvedUrl = ref();
|
|
18
18
|
const cachedBase64 = ref();
|
|
19
19
|
const originalFileName = ref();
|
|
20
20
|
const resolvedMimeType = ref();
|
|
21
21
|
const isLoading = ref(false);
|
|
22
|
-
const isHovered = ref(false);
|
|
23
22
|
const fileSize = ref();
|
|
24
|
-
const
|
|
25
|
-
const csvInfo = ref();
|
|
26
|
-
const textInfo = ref();
|
|
23
|
+
const mediaFailed = ref(false);
|
|
27
24
|
const effectiveMimeType = computed(() => resolvedMimeType.value ?? props.file.mimeType);
|
|
28
25
|
const normalizedMimeType = computed(() => normalizeMimeType(effectiveMimeType.value));
|
|
26
|
+
const presentation = computed(() => resolveFilePresentation(normalizedMimeType.value));
|
|
29
27
|
const iconSrc = computed(() => FILE_ICONS[normalizedMimeType.value] ?? FILE_ICONS.unknown);
|
|
30
|
-
const fileColors = computed(() => FILE_COLORS.default ?? { bg: "bg-gray-100", text: "text-gray-600", border: "border-gray-200" });
|
|
31
|
-
const isPdf = computed(() => normalizedMimeType.value === "application/pdf");
|
|
32
|
-
const isImage = computed(() => normalizedMimeType.value.startsWith("image/"));
|
|
33
|
-
const isVideo = computed(() => normalizedMimeType.value.startsWith("video/"));
|
|
34
|
-
const isAudio = computed(() => normalizedMimeType.value.startsWith("audio/"));
|
|
35
|
-
const isCsv = computed(() => normalizedMimeType.value === "text/csv");
|
|
36
|
-
const isJson = computed(() => normalizedMimeType.value === "application/json");
|
|
37
|
-
const isText = computed(() => normalizedMimeType.value.startsWith("text/") && !isCsv.value || isJson.value);
|
|
38
|
-
const initialPage = computed(() => props.file.hashParams?.pageNumber);
|
|
39
|
-
const hasInlinePreview = computed(() => isPdf.value || isImage.value || isVideo.value || isAudio.value || isCsv.value || isText.value);
|
|
40
28
|
const effectiveUrl = computed(() => {
|
|
41
29
|
if (cachedBase64.value && normalizedMimeType.value)
|
|
42
30
|
return base64ToDataUrl(cachedBase64.value, normalizedMimeType.value);
|
|
@@ -50,32 +38,17 @@ const displayName = computed(() => {
|
|
|
50
38
|
const ext = MIME_TO_EXTENSION[normalizedMimeType.value];
|
|
51
39
|
return ext ? `${name}.${ext}` : name;
|
|
52
40
|
});
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return `${baseName.slice(0, 16)}...${ext ? `.${ext}` : ""}`;
|
|
60
|
-
});
|
|
61
|
-
const fileExtension = computed(() => {
|
|
62
|
-
const parts = displayName.value.split(".");
|
|
63
|
-
return parts.length > 1 ? parts.pop()?.toUpperCase() : "ARQUIVO";
|
|
64
|
-
});
|
|
65
|
-
const formattedSize = computed(() => {
|
|
66
|
-
if (!fileSize.value)
|
|
67
|
-
return null;
|
|
68
|
-
const bytes = fileSize.value;
|
|
69
|
-
if (bytes < 1024)
|
|
70
|
-
return `${bytes} B`;
|
|
71
|
-
if (bytes < 1024 * 1024)
|
|
72
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
73
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
74
|
-
});
|
|
41
|
+
const mediaUnavailable = computed(() => mediaFailed.value || (presentation.value === "image" || presentation.value === "video") && !effectiveUrl.value);
|
|
42
|
+
const meta = computed(() => describeFile(normalizedMimeType.value, displayName.value));
|
|
43
|
+
const menuItems = computed(() => [
|
|
44
|
+
{ label: "Abrir", icon: "i-ph-arrow-square-out", click: openSidePanel },
|
|
45
|
+
{ label: "Baixar", icon: "i-ph-download-simple", click: () => void download() }
|
|
46
|
+
]);
|
|
75
47
|
async function resolveVaultUrl() {
|
|
76
48
|
const reference = props.file.reference;
|
|
77
49
|
const cleanReference = reference.split("#")[0] ?? reference;
|
|
78
50
|
isLoading.value = true;
|
|
51
|
+
mediaFailed.value = false;
|
|
79
52
|
const cacheKey = cleanReference;
|
|
80
53
|
try {
|
|
81
54
|
const cached = await getCached(cacheKey);
|
|
@@ -142,177 +115,98 @@ function openSidePanel() {
|
|
|
142
115
|
cachedBase64: cachedBase64.value,
|
|
143
116
|
originalFileName: originalFileName.value,
|
|
144
117
|
resolvedMimeType: resolvedMimeType.value,
|
|
145
|
-
fileSize: fileSize.value
|
|
146
|
-
pdfTotalPages: pdfTotalPages.value,
|
|
147
|
-
csvInfo: csvInfo.value,
|
|
148
|
-
textInfo: textInfo.value
|
|
118
|
+
fileSize: fileSize.value
|
|
149
119
|
});
|
|
150
120
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
121
|
+
async function download() {
|
|
122
|
+
const url = effectiveUrl.value;
|
|
123
|
+
if (!url)
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetch(url);
|
|
127
|
+
if (!response.ok)
|
|
128
|
+
throw new Error(`Download failed with status ${response.status}`);
|
|
129
|
+
const blob = await response.blob();
|
|
130
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
131
|
+
const link = document.createElement("a");
|
|
132
|
+
link.href = blobUrl;
|
|
133
|
+
link.download = displayName.value;
|
|
134
|
+
document.body.appendChild(link);
|
|
135
|
+
link.click();
|
|
136
|
+
document.body.removeChild(link);
|
|
137
|
+
URL.revokeObjectURL(blobUrl);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error("[FilePreview] Failed to download file", error);
|
|
140
|
+
window.open(url, "_blank", "noopener");
|
|
141
|
+
}
|
|
159
142
|
}
|
|
160
143
|
</script>
|
|
161
144
|
|
|
162
145
|
<template>
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
<div v-if="hasInlinePreview" class="w-full">
|
|
166
|
-
<!-- Header -->
|
|
167
|
-
<div class="flex items-center gap-12px p-12px rounded-t-16px" style="background-color: #F5F5F5;">
|
|
168
|
-
<!-- File Icon -->
|
|
169
|
-
<img :src="iconSrc" :alt="displayName" class="w-16px h-16px flex-shrink-0">
|
|
170
|
-
|
|
171
|
-
<!-- File Info -->
|
|
172
|
-
<div class="flex-1 min-w-0">
|
|
173
|
-
<p class="text-14px font-medium text-gray-900 truncate">
|
|
174
|
-
{{ displayName }}
|
|
175
|
-
</p>
|
|
176
|
-
<p class="text-12px text-gray-500">
|
|
177
|
-
<span v-if="isPdf && pdfTotalPages">{{ pdfTotalPages }} páginas</span>
|
|
178
|
-
<span v-else-if="isCsv && csvInfo">{{ csvInfo.totalRows }} linhas · {{ csvInfo.totalColumns }} colunas</span>
|
|
179
|
-
<span v-else-if="isText && textInfo">{{ textInfo.totalLines }} linhas</span>
|
|
180
|
-
<span v-if="(isPdf && pdfTotalPages || isCsv && csvInfo || isText && textInfo) && formattedSize"> · </span>
|
|
181
|
-
<span v-if="formattedSize">{{ formattedSize }}</span>
|
|
182
|
-
<span v-if="isLoading">Carregando...</span>
|
|
183
|
-
</p>
|
|
184
|
-
</div>
|
|
185
|
-
|
|
186
|
-
<!-- Visualizar Button -->
|
|
187
|
-
<TelaButton
|
|
188
|
-
variant="secondary"
|
|
189
|
-
:disabled="isLoading || !effectiveUrl"
|
|
190
|
-
@click="openSidePanel"
|
|
191
|
-
>
|
|
192
|
-
Visualizar
|
|
193
|
-
</TelaButton>
|
|
194
|
-
</div>
|
|
195
|
-
|
|
196
|
-
<!-- Preview Content -->
|
|
197
|
-
<div v-if="effectiveUrl" class="rounded-b-16px overflow-hidden" style="background-color: #F5F5F5;">
|
|
198
|
-
<!-- PDF Preview -->
|
|
199
|
-
<PdfPreview
|
|
200
|
-
v-if="isPdf"
|
|
201
|
-
:url="effectiveUrl"
|
|
202
|
-
:initial-page="initialPage"
|
|
203
|
-
:max-height="300"
|
|
204
|
-
:clickable="false"
|
|
205
|
-
@loaded="onPdfLoaded"
|
|
206
|
-
@click="openSidePanel"
|
|
207
|
-
/>
|
|
208
|
-
|
|
209
|
-
<!-- Image Preview -->
|
|
210
|
-
<ImagePreview
|
|
211
|
-
v-else-if="isImage"
|
|
212
|
-
:url="effectiveUrl"
|
|
213
|
-
:max-height="300"
|
|
214
|
-
:clickable="false"
|
|
215
|
-
@click="openSidePanel"
|
|
216
|
-
/>
|
|
217
|
-
|
|
218
|
-
<!-- Video Preview -->
|
|
219
|
-
<VideoPreview
|
|
220
|
-
v-else-if="isVideo"
|
|
221
|
-
:url="effectiveUrl"
|
|
222
|
-
:max-height="300"
|
|
223
|
-
:clickable="false"
|
|
224
|
-
@click="openSidePanel"
|
|
225
|
-
/>
|
|
226
|
-
|
|
227
|
-
<!-- Audio Preview -->
|
|
228
|
-
<AudioPreview
|
|
229
|
-
v-else-if="isAudio"
|
|
230
|
-
:url="effectiveUrl"
|
|
231
|
-
:name="displayName"
|
|
232
|
-
:clickable="false"
|
|
233
|
-
@click="openSidePanel"
|
|
234
|
-
/>
|
|
235
|
-
|
|
236
|
-
<!-- CSV Preview -->
|
|
237
|
-
<CsvPreview
|
|
238
|
-
v-else-if="isCsv"
|
|
239
|
-
:url="effectiveUrl"
|
|
240
|
-
:max-height="300"
|
|
241
|
-
:clickable="false"
|
|
242
|
-
@loaded="onCsvLoaded"
|
|
243
|
-
@click="openSidePanel"
|
|
244
|
-
/>
|
|
245
|
-
|
|
246
|
-
<!-- Text Preview -->
|
|
247
|
-
<TextPreview
|
|
248
|
-
v-else-if="isText"
|
|
249
|
-
:url="effectiveUrl"
|
|
250
|
-
:mime-type="normalizedMimeType"
|
|
251
|
-
:max-height="300"
|
|
252
|
-
:clickable="false"
|
|
253
|
-
@loaded="onTextLoaded"
|
|
254
|
-
@click="openSidePanel"
|
|
255
|
-
/>
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
|
|
259
|
-
<!-- Regular File Card (for unsupported types) -->
|
|
146
|
+
<TelaSkeleton v-if="isLoading" class="rounded-12px" w-full max-w-480px h-64px data-chat-file="loading" />
|
|
147
|
+
<div v-else-if="presentation === 'image' && !mediaUnavailable" data-chat-file="media">
|
|
260
148
|
<button
|
|
261
|
-
v-else
|
|
262
149
|
type="button"
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
fileColors.border,
|
|
266
|
-
isLoading || !effectiveUrl ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:shadow-md hover:scale-102'
|
|
267
|
-
]"
|
|
268
|
-
:disabled="isLoading || !effectiveUrl"
|
|
269
|
-
@mouseenter="isHovered = true"
|
|
270
|
-
@mouseleave="isHovered = false"
|
|
150
|
+
block p-0 border-0 bg-transparent cursor-zoom-in
|
|
151
|
+
aria-label="Abrir imagem"
|
|
271
152
|
@click="openSidePanel"
|
|
272
153
|
>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
154
|
+
<img
|
|
155
|
+
:src="effectiveUrl"
|
|
156
|
+
:alt="displayName"
|
|
157
|
+
block max-w-full max-h-480px rounded-12px object-contain
|
|
158
|
+
@error="mediaFailed = true"
|
|
276
159
|
>
|
|
277
|
-
<!-- Loading State -->
|
|
278
|
-
<div v-if="isLoading" class="flex items-center justify-center w-full h-full">
|
|
279
|
-
<TelaSkeleton class="rounded-8px" style="width: 120px; height: 60px;" />
|
|
280
|
-
</div>
|
|
281
|
-
|
|
282
|
-
<!-- File Icon -->
|
|
283
|
-
<div v-else class="flex flex-col items-center gap-4px">
|
|
284
|
-
<img :src="iconSrc" :alt="displayName" class="w-32px h-32px">
|
|
285
|
-
<span class="text-10px font-bold uppercase tracking-wide px-6px py-2px rounded-4px bg-white/80 text-gray-600">
|
|
286
|
-
{{ fileExtension }}
|
|
287
|
-
</span>
|
|
288
|
-
</div>
|
|
289
|
-
|
|
290
|
-
<!-- Hover Overlay -->
|
|
291
|
-
<Transition name="fade">
|
|
292
|
-
<div
|
|
293
|
-
v-if="isHovered && !isLoading && effectiveUrl"
|
|
294
|
-
class="absolute inset-0 bg-black/50 flex items-center justify-center"
|
|
295
|
-
>
|
|
296
|
-
<div class="w-36px h-36px rounded-full bg-white flex items-center justify-center shadow-lg">
|
|
297
|
-
<span class="i-ph-eye text-18px text-gray-700" />
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
</Transition>
|
|
301
|
-
</div>
|
|
302
|
-
|
|
303
|
-
<!-- File Info -->
|
|
304
|
-
<div class="px-10px py-8px bg-white">
|
|
305
|
-
<p class="text-12px font-medium text-gray-800 truncate text-left">
|
|
306
|
-
{{ shortName }}
|
|
307
|
-
</p>
|
|
308
|
-
<p v-if="formattedSize" class="text-10px text-gray-400 text-left mt-2px">
|
|
309
|
-
{{ formattedSize }}
|
|
310
|
-
</p>
|
|
311
|
-
</div>
|
|
312
160
|
</button>
|
|
313
161
|
</div>
|
|
162
|
+
<ChatFileCard
|
|
163
|
+
v-else-if="presentation === 'video' && !mediaUnavailable"
|
|
164
|
+
:icon-src="iconSrc"
|
|
165
|
+
:name="displayName"
|
|
166
|
+
:meta="meta"
|
|
167
|
+
:menu-items="menuItems"
|
|
168
|
+
@open="openSidePanel"
|
|
169
|
+
>
|
|
170
|
+
<video
|
|
171
|
+
:src="effectiveUrl"
|
|
172
|
+
controls
|
|
173
|
+
preload="metadata"
|
|
174
|
+
block w-full max-h-480px bg-black border-t-0.5px border
|
|
175
|
+
data-chat-file="media"
|
|
176
|
+
@error="mediaFailed = true"
|
|
177
|
+
/>
|
|
178
|
+
</ChatFileCard>
|
|
179
|
+
<ChatFileAudio
|
|
180
|
+
v-else-if="presentation === 'audio'"
|
|
181
|
+
:url="effectiveUrl"
|
|
182
|
+
:icon-src="iconSrc"
|
|
183
|
+
:name="displayName"
|
|
184
|
+
:meta="meta"
|
|
185
|
+
:menu-items="menuItems"
|
|
186
|
+
@open="openSidePanel"
|
|
187
|
+
/>
|
|
188
|
+
<ChatFileCard
|
|
189
|
+
v-else-if="presentation === 'card' || mediaUnavailable"
|
|
190
|
+
:icon-src="iconSrc"
|
|
191
|
+
:name="displayName"
|
|
192
|
+
:meta="mediaUnavailable ? 'Falha ao carregar a pr\xE9via' : meta"
|
|
193
|
+
:menu-items="menuItems"
|
|
194
|
+
:disabled="!effectiveUrl"
|
|
195
|
+
@open="openSidePanel"
|
|
196
|
+
/>
|
|
197
|
+
<button
|
|
198
|
+
v-else
|
|
199
|
+
type="button"
|
|
200
|
+
class="chat-file-link"
|
|
201
|
+
inline-flex items-center gap-6px max-w-full text-left
|
|
202
|
+
:disabled="!effectiveUrl"
|
|
203
|
+
@click="openSidePanel"
|
|
204
|
+
>
|
|
205
|
+
<img :src="iconSrc" alt="" size-14px shrink-0>
|
|
206
|
+
<span truncate>{{ displayName }}</span>
|
|
207
|
+
</button>
|
|
314
208
|
</template>
|
|
315
209
|
|
|
316
210
|
<style scoped>
|
|
317
|
-
.
|
|
211
|
+
.chat-file-link{color:inherit;cursor:pointer;text-decoration:underline;text-decoration-color:#a3a3a3;text-decoration-style:dotted;text-decoration-thickness:1px;text-underline-offset:3px;transition:text-decoration-color .15s ease}.chat-file-link:hover{text-decoration-color:#525252}.chat-file-link:disabled{cursor:default}
|
|
318
212
|
</style>
|