|
270 | 270 | > |
271 | 271 | {{ getFirstWarning("description") }} |
272 | 272 | </SimpleBanner> |
| 273 | + <div class="flex items-center gap-4 mt-2 mb-3"> |
| 274 | + <Tooltip v-if="!canGenerateDescription"> |
| 275 | + <BrandedButton |
| 276 | + type="button" |
| 277 | + color="primary" |
| 278 | + :disabled="true" |
| 279 | + > |
| 280 | + <div class="flex items-center space-x-2"> |
| 281 | + <RiSparklingLine |
| 282 | + class="size-4" |
| 283 | + aria-hidden="true" |
| 284 | + /> |
| 285 | + <span>{{ $t('Suggérer une description') }}</span> |
| 286 | + </div> |
| 287 | + </BrandedButton> |
| 288 | + <template #tooltip> |
| 289 | + {{ $t('Remplissez le champ "Lien vers la documentation technique de l\'API" pour utiliser cette fonctionnalité.') }} |
| 290 | + </template> |
| 291 | + </Tooltip> |
| 292 | + <BrandedButton |
| 293 | + v-else |
| 294 | + type="button" |
| 295 | + color="primary" |
| 296 | + :icon="RiSparklingLine" |
| 297 | + :loading="isGeneratingDescription" |
| 298 | + @click="handleAutoCompleteDescription" |
| 299 | + > |
| 300 | + <template v-if="isGeneratingDescription"> |
| 301 | + {{ $t('Suggestion en cours...') }} |
| 302 | + </template> |
| 303 | + <template v-else> |
| 304 | + {{ $t('Suggérer une description') }} |
| 305 | + </template> |
| 306 | + </BrandedButton> |
| 307 | + <CdataLink |
| 308 | + v-if="config.public.generateDescriptionFeedbackUrl" |
| 309 | + :to="config.public.generateDescriptionFeedbackUrl" |
| 310 | + target="_blank" |
| 311 | + class="text-sm text-gray-medium" |
| 312 | + > |
| 313 | + {{ $t('Comment avez-vous trouvé cette suggestion ?') }} |
| 314 | + </CdataLink> |
| 315 | + </div> |
273 | 316 | </LinkedToAccordion> |
274 | 317 | <LinkedToAccordion |
275 | 318 | class="fr-fieldset__element" |
|
506 | 549 | </template> |
507 | 550 |
|
508 | 551 | <script setup lang="ts"> |
509 | | -import { BrandedButton, SimpleBanner, TranslationT, type Owned } from '@datagouv/components-next' |
510 | | -import { RiAddLine } from '@remixicon/vue' |
511 | | -import { computed } from 'vue' |
512 | | -import ModalClient from '../Modal/Modal.client.vue' |
| 552 | +import { BrandedButton, SimpleBanner, Tooltip, TranslationT, type Owned } from '@datagouv/components-next' |
| 553 | +import { RiAddLine, RiSparklingLine } from '@remixicon/vue' |
| 554 | +import { computed, nextTick } from 'vue' |
513 | 555 | import Accordion from '~/components/Accordion/Accordion.global.vue' |
514 | 556 | import AccordionGroup from '~/components/Accordion/AccordionGroup.global.vue' |
515 | | -import ToggleSwitch from '~/components/Form/ToggleSwitch.vue' |
| 557 | +import CdataLink from '~/components/CdataLink.vue' |
516 | 558 | import ContactPointSelect from '~/components/ContactPointSelect.vue' |
517 | 559 | import ProducerSelect from '~/components/ProducerSelect.vue' |
| 560 | +import ToggleSwitch from '~/components/Form/ToggleSwitch.vue' |
| 561 | +import ModalClient from '../Modal/Modal.client.vue' |
518 | 562 | import type { DataserviceForm } from '~/types/types' |
519 | 563 |
|
520 | 564 | const props = defineProps<{ |
@@ -554,6 +598,15 @@ const ownedOptions = computed<Array<Owned>>(() => { |
554 | 598 | const machineDocumentationUrlWarningMessage = t(`Il est fortement recommandé d'ajouter une documentation OpenAPI ou Swagger à votre API.`) |
555 | 599 | const openConfirmModal = ref(false) |
556 | 600 |
|
| 601 | +// Track description generation state |
| 602 | +const isGeneratingDescription = ref(false) |
| 603 | +
|
| 604 | +const hasTechnicalDocumentationUrl = computed(() => form.value.technical_documentation_url && form.value.technical_documentation_url.trim().length > 0) |
| 605 | +
|
| 606 | +const canGenerateDescription = computed(() => { |
| 607 | + return hasTechnicalDocumentationUrl.value |
| 608 | +}) |
| 609 | +
|
557 | 610 | const { form, touch, getFirstError, getFirstWarning, validate } = useForm(dataserviceForm, { |
558 | 611 | featured: [], |
559 | 612 | owned: [required()], |
@@ -582,6 +635,56 @@ const accordionState = (key: keyof typeof form.value) => { |
582 | 635 | return 'default' |
583 | 636 | } |
584 | 637 |
|
| 638 | +async function handleAutoCompleteDescription() { |
| 639 | + if (!form.value.technical_documentation_url) { |
| 640 | + return |
| 641 | + } |
| 642 | +
|
| 643 | + try { |
| 644 | + isGeneratingDescription.value = true |
| 645 | +
|
| 646 | + const requestBody: { |
| 647 | + technicalDocumentationUrl: string |
| 648 | + machineDocumentationUrl?: string |
| 649 | + title?: string |
| 650 | + } = { |
| 651 | + technicalDocumentationUrl: form.value.technical_documentation_url, |
| 652 | + } |
| 653 | +
|
| 654 | + // Include machine documentation URL only if it's provided |
| 655 | + if (form.value.machine_documentation_url && form.value.machine_documentation_url.trim().length > 0) { |
| 656 | + requestBody.machineDocumentationUrl = form.value.machine_documentation_url |
| 657 | + } |
| 658 | +
|
| 659 | + // Include title only if it's provided |
| 660 | + if (form.value.title && form.value.title.trim().length > 0) { |
| 661 | + requestBody.title = form.value.title |
| 662 | + } |
| 663 | +
|
| 664 | + // We call our server-side API route instead of Albert API directly to avoid CORS issues. |
| 665 | + // The Albert API doesn't allow direct requests from browser-side JavaScript. |
| 666 | + // Our server acts as a proxy, keeping the API key secure on the server side. |
| 667 | + const response = await $fetch<{ description?: string }>('/nuxt-api/albert/generate-dataservice-description', { |
| 668 | + method: 'POST', |
| 669 | + body: requestBody, |
| 670 | + }) |
| 671 | +
|
| 672 | + if (response.description) { |
| 673 | + form.value.description = response.description |
| 674 | + await nextTick() |
| 675 | + } |
| 676 | + } |
| 677 | + catch (error) { |
| 678 | + console.error('[Albert API] Failed to generate description:', error) |
| 679 | + if (error && typeof error === 'object' && 'data' in error) { |
| 680 | + console.error('[Albert API] Error details:', error.data) |
| 681 | + } |
| 682 | + } |
| 683 | + finally { |
| 684 | + isGeneratingDescription.value = false |
| 685 | + } |
| 686 | +} |
| 687 | +
|
585 | 688 | async function submit() { |
586 | 689 | if (await validate()) { |
587 | 690 | if (dataserviceForm.value.machine_documentation_url || openConfirmModal.value) { |
|
0 commit comments