Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Metadata selection before file upload #108

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,17 @@ declare module 'vue' {
DynamicForm: typeof import('./src/components/DynamicForm.vue')['default']
ErrorBox: typeof import('./src/components/ErrorBox.vue')['default']
Header: typeof import('./src/components/Header.vue')['default']
HeroiconsAdjustmentsVertical: typeof import('~icons/heroicons/adjustments-vertical')['default']
HeroiconsArrowDown20Solid: typeof import('~icons/heroicons/arrow-down20-solid')['default']
HeroiconsArrowPath: typeof import('~icons/heroicons/arrow-path')['default']
HeroiconsArrowRightOnRectangle: typeof import('~icons/heroicons/arrow-right-on-rectangle')['default']
HeroiconsBars3Solid: typeof import('~icons/heroicons/bars3-solid')['default']
HeroiconsBoltSolid: typeof import('~icons/heroicons/bolt-solid')['default']
HeroiconsChevronUpDown20Solid: typeof import('~icons/heroicons/chevron-up-down20-solid')['default']
HeroiconsClipboard: typeof import('~icons/heroicons/clipboard')['default']
HeroiconsCloudArrowDownSolid: typeof import('~icons/heroicons/cloud-arrow-down-solid')['default']
HeroiconsCog6Tooth20Solid: typeof import('~icons/heroicons/cog6-tooth20-solid')['default']
HeroiconsDocumentTextSolid: typeof import('~icons/heroicons/document-text-solid')['default']
HeroiconsGlobeAlt: typeof import('~icons/heroicons/globe-alt')['default']
HeroiconsHome20Solid: typeof import('~icons/heroicons/home20-solid')['default']
HeroiconsInformationCircleSolid: typeof import('~icons/heroicons/information-circle-solid')['default']
HeroiconsLink20Solid: typeof import('~icons/heroicons/link20-solid')['default']
HeroiconsLogout: typeof import('~icons/heroicons/logout')['default']
HeroiconsLogoutSolid: typeof import('~icons/heroicons/logout-solid')['default']
HeroiconsMagnifyingGlass20Solid: typeof import('~icons/heroicons/magnifying-glass20-solid')['default']
HeroiconsMicrophoneSolid: typeof import('~icons/heroicons/microphone-solid')['default']
HeroiconsMoonSolid: typeof import('~icons/heroicons/moon-solid')['default']
Expand All @@ -53,36 +47,24 @@ declare module 'vue' {
ModalBox: typeof import('./src/components/ModalBox.vue')['default']
NotificationStack: typeof import('./src/components/NotificationStack.vue')['default']
Pagination: typeof import('./src/components/Pagination.vue')['default']
PhArrowCounterClockwiseBold: typeof import('~icons/ph/arrow-counter-clockwise-bold')['default']
PhBrainFill: typeof import('~icons/ph/brain-fill')['default']
PhCaretLeftFill: typeof import('~icons/ph/caret-left-fill')['default']
PhCaretRightFill: typeof import('~icons/ph/caret-right-fill')['default']
PhChatCenteredDots: typeof import('~icons/ph/chat-centered-dots')['default']
PhChats: typeof import('~icons/ph/chats')['default']
PhExportBold: typeof import('~icons/ph/export-bold')['default']
PhFileFill: typeof import('~icons/ph/file-fill')['default']
PhFiles: typeof import('~icons/ph/files')['default']
PhFloppyDiskBold: typeof import('~icons/ph/floppy-disk-bold')['default']
PhInfo: typeof import('~icons/ph/info')['default']
PhLightbulbFilamentFill: typeof import('~icons/ph/lightbulb-filament-fill')['default']
PhListMagnifyingGlass: typeof import('~icons/ph/list-magnifying-glass')['default']
PhNut: typeof import('~icons/ph/nut')['default']
PhPencilFill: typeof import('~icons/ph/pencil-fill')['default']
PhPlugFill: typeof import('~icons/ph/plug-fill')['default']
PhPlus: typeof import('~icons/ph/plus')['default']
PhQuestionMark: typeof import('~icons/ph/question-mark')['default']
PhTextbox: typeof import('~icons/ph/textbox')['default']
PhToolbox: typeof import('~icons/ph/toolbox')['default']
PhTrashFill: typeof import('~icons/ph/trash-fill')['default']
PhUser: typeof import('~icons/ph/user')['default']
PhUserFill: typeof import('~icons/ph/user-fill')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SelectBox: typeof import('./src/components/SelectBox.vue')['default']
SidePanel: typeof import('./src/components/SidePanel.vue')['default']
TransitionChild: typeof import('@headlessui/vue')['TransitionChild']
TransitionRoot: typeof import('@headlessui/vue')['TransitionRoot']
UseImage: typeof import('@vueuse/components')['UseImage']
UserDropdown: typeof import('./src/components/UserDropdown.vue')['default']
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"animate.css": "^4.1.1",
"apexcharts": "^3.54.1",
"axios": "^1.7.7",
"ccat-api": "github:cheshire-cat-ai/api-client-ts#develop",
"ccat-api": "^0.11.3",
"daisyui": "^4.12.13",
"highlight.js": "^11.10.0",
"jwt-decode": "^4.0.0",
Expand Down
11 changes: 5 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions src/services/RabbitHoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { apiClient, tryRequest } from '@services/ApiService'
* Meaning this service sends files to the backend.
*/
const RabbitHoleService = Object.freeze({
sendFile: async (file: File) => {
sendFile: async (file: File, metadata?: Record<string, any>) => {
return await tryRequest(
apiClient?.api?.rabbitHole.uploadFile({ file }),
apiClient?.api?.rabbitHole.uploadFile({
file,
metadata: JSON.stringify(metadata),
}),
`File ${file.name} successfully sent down the rabbit hole!`,
'Unable to send the file to the rabbit hole!',
'Sending a file to the rabbit hole',
Expand Down
72 changes: 71 additions & 1 deletion src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useMessages } from '@stores/useMessages'
import { useMemory } from '@stores/useMemory'
import ModalBox from '@components/ModalBox.vue'
import { capitalize } from 'lodash'
import RabbitHoleService from '@services/RabbitHoleService'

const route = useRoute()
const messagesStore = useMessages()
Expand All @@ -14,6 +15,51 @@ const userMessage = ref(''),
insertedURL = ref(''),
isScrollable = ref(false),
isTwoLines = ref(false)

/**
* File Upload w/ Metadata Management
* **/

const uploadFileDialog = useFileDialog()

// User selects a file and then the Metadata Modal opens up
const selectedFiles = ref<File[]>([])
uploadFileDialog.onChange((files) => {
selectedFiles!.value = files
fileMetadata.value[0].value = selectedFiles.value[0].name
boxUploadFile.value?.toggleModal()
})

const fileMetadata = ref<{key: string, value: string}[]>([
{ key: 'source', value: 'filename.pdf'},
])

const addMetadata = () => {
fileMetadata.value.push(
{ key: '', value: '' }
)
}
const removeMetadata = (index: number) => {
fileMetadata.value.splice(index, 1)
}

const boxUploadFile = ref<InstanceType<typeof ModalBox>>()

const dispatchFiles = async () => {
boxUploadFile.value?.toggleModal()
const json: Record<string, string> = {};
// turn metadata into Json
for (const md of fileMetadata.value)
json[md.key] = md.value
for (const file of selectedFiles.value)
await RabbitHoleService.sendFile(file, json)
uploadFileDialog.reset()
}

/**
* End Custom Metadata Management
* */

const boxUploadURL = ref<InstanceType<typeof ModalBox>>()

const { textarea: textArea } = useTextareaAutosize({
Expand Down Expand Up @@ -123,6 +169,7 @@ useEventListener(document, 'scroll', () => {
isScrollable.value = doc.scrollHeight > doc.clientHeight + doc.scrollTop
})


/**
* Dispatches the inserted url to the RabbitHole service and closes the modal.
*/
Expand Down Expand Up @@ -281,7 +328,7 @@ const scrollToBottom = () => {
<button
:disabled="rabbitHoleState.loading"
class="btn join-item w-full flex-nowrap px-2 text-left font-medium"
@click="uploadFile('content')">
@click="uploadFileDialog.open">
<span class="rounded-lg p-1 text-warning">
<heroicons-document-text-solid class="size-5" />
</span>
Expand Down Expand Up @@ -332,6 +379,29 @@ const scrollToBottom = () => {
<heroicons-arrow-down-20-solid class="size-5" />
</button>
</div>
<Teleport to="#modal">
<ModalBox ref="boxUploadFile" class="text-center">
<div class="flex flex-col text-center justify-center gap-4 text-neutral">
<h3 class="text-lg font-bold">File Upload</h3>
<p>Selected file(s): <b>{{ `${selectedFiles.length} ${selectedFiles.length === 1 ? 'file' : 'files'}` }}</b></p>
<li v-for="file of selectedFiles" :key="file.name">
{{ file.name }}
</li>
<p>Add metadata to file:</p>
<div class="bg-amber-50" v-for="(md, index) in fileMetadata" :key="index">
<label>Label you will query for:</label>
<InputBox v-model="md.key" placeholder="Metadata Key ..." />
<label>Value of the metadata:</label>
<InputBox v-model="md.value" placeholder="Value ..." />
<button class="btn btn-sm btn-neutral" @click="removeMetadata(index)">-</button>
</div>
<button class="btn btn-active btn-sm btn-circle btn-primary p-3" @click="addMetadata">+</button>

<button class="btn btn-primary btn-sm" @click="dispatchFiles">Send</button>
</div>

</ModalBox>
</Teleport>
<Teleport to="#modal">
<ModalBox ref="boxUploadURL">
<div class="flex flex-col items-center justify-center gap-4 text-neutral">
Expand Down