diff --git a/app/javascript/dashboard/api/captain/document.js b/app/javascript/dashboard/api/captain/document.js index dc22b0c32..e23a8c460 100644 --- a/app/javascript/dashboard/api/captain/document.js +++ b/app/javascript/dashboard/api/captain/document.js @@ -6,15 +6,22 @@ class CaptainDocument extends ApiClient { super('captain/documents', { accountScoped: true }); } - get({ page = 1, searchKey, assistantId } = {}) { + get({ page = 1, searchKey, assistantId, filter, source, sort } = {}) { return axios.get(this.url, { params: { page, - searchKey, + search_key: searchKey, assistant_id: assistantId, + filter, + source, + sort, }, }); } + + sync(id) { + return axios.post(`${this.url}/${id}/sync`); + } } export default new CaptainDocument(); diff --git a/app/javascript/dashboard/components-next/captain/assistant/DocumentBulkActions.vue b/app/javascript/dashboard/components-next/captain/assistant/DocumentBulkActions.vue new file mode 100644 index 000000000..378860b3e --- /dev/null +++ b/app/javascript/dashboard/components-next/captain/assistant/DocumentBulkActions.vue @@ -0,0 +1,113 @@ + + + diff --git a/app/javascript/dashboard/components-next/captain/assistant/DocumentCard.vue b/app/javascript/dashboard/components-next/captain/assistant/DocumentCard.vue index 30ebfc448..c8a011b9a 100644 --- a/app/javascript/dashboard/components-next/captain/assistant/DocumentCard.vue +++ b/app/javascript/dashboard/components-next/captain/assistant/DocumentCard.vue @@ -5,14 +5,17 @@ import { useI18n } from 'vue-i18n'; import { dynamicTime } from 'shared/helpers/timeHelper'; import { usePolicy } from 'dashboard/composables/usePolicy'; import { - isPdfDocument, + isSafeHttpLink, formatDocumentLink, + getDocumentDisplayPath, } from 'shared/helpers/documentHelper'; +import Icon from 'dashboard/components-next/icon/Icon.vue'; import CardLayout from 'dashboard/components-next/CardLayout.vue'; import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue'; import Button from 'dashboard/components-next/button/Button.vue'; import Checkbox from 'dashboard/components-next/checkbox/Checkbox.vue'; +import DocumentSyncStatus from 'dashboard/components-next/captain/assistant/DocumentSyncStatus.vue'; const props = defineProps({ id: { @@ -31,10 +34,38 @@ const props = defineProps({ type: String, required: true, }, + pdfDocument: { + type: Boolean, + default: false, + }, createdAt: { type: Number, required: true, }, + status: { + type: String, + default: null, + }, + syncStatus: { + type: String, + default: null, + }, + lastSyncedAt: { + type: Number, + default: null, + }, + lastSyncErrorCode: { + type: String, + default: null, + }, + syncInProgress: { + type: Boolean, + default: false, + }, + syncStaleAfterHours: { + type: Number, + default: null, + }, isSelected: { type: Boolean, default: false, @@ -64,6 +95,20 @@ const modelValue = computed({ set: () => emit('select', props.id), }); +const isPdf = computed(() => props.pdfDocument); +const hasSafeLink = computed(() => isSafeHttpLink(props.externalLink)); +const canManage = computed(() => checkPermissions(['administrator'])); +const isAvailable = computed(() => props.status === 'available'); +const canSync = computed( + () => canManage.value && !isPdf.value && isAvailable.value +); +const isSyncing = computed(() => props.syncStatus === 'syncing'); +const isFailed = computed(() => props.syncStatus === 'failed'); +const isRetryableSync = computed( + () => isFailed.value || (isSyncing.value && !props.syncInProgress) +); +const showSyncStatus = computed(() => !isPdf.value); + const menuItems = computed(() => { const allOptions = [ { @@ -74,7 +119,19 @@ const menuItems = computed(() => { }, ]; - if (checkPermissions(['administrator'])) { + if (canSync.value) { + allOptions.push({ + label: isRetryableSync.value + ? t('CAPTAIN.DOCUMENTS.OPTIONS.RETRY_SYNC') + : t('CAPTAIN.DOCUMENTS.OPTIONS.SYNC_NOW'), + value: 'sync', + action: 'sync', + icon: 'i-lucide-refresh-cw', + disabled: props.syncInProgress, + }); + } + + if (canManage.value) { allOptions.push({ label: t('CAPTAIN.DOCUMENTS.OPTIONS.DELETE_DOCUMENT'), value: 'delete', @@ -86,17 +143,25 @@ const menuItems = computed(() => { return allOptions; }); -const createdAt = computed(() => dynamicTime(props.createdAt)); +const createdAtLabel = computed(() => dynamicTime(props.createdAt)); -const displayLink = computed(() => formatDocumentLink(props.externalLink)); +const displayLink = computed(() => + isPdf.value + ? formatDocumentLink(props.externalLink) + : getDocumentDisplayPath(props.externalLink) +); const linkIcon = computed(() => - isPdfDocument(props.externalLink) ? 'i-ph-file-pdf' : 'i-ph-link-simple' + isPdf.value ? 'i-ph-file-pdf' : 'i-ph-link-simple' ); const handleAction = ({ action, value }) => { toggleDropdown(false); emit('action', { action, value, id: props.id }); }; + +const handleRetry = () => { + emit('action', { action: 'sync', id: props.id }); +};