Skip to content

Commit

Permalink
feat(fe2): Update permissions needed to use Gendo (#3844)
Browse files Browse the repository at this point in the history
* Check if user can contribute to project

* Reorder tooltip prio

* Fix tippy reactivity

* Fix reactivity. Add loading icon

* Update graphql.ts

* Add feedback mechanism

* Update Dialog.vue

* Adjust feedback dialog intro

* handleFeedback
  • Loading branch information
andrewwallacespeckle authored Jan 21, 2025
1 parent bb9f112 commit c9fb11c
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 42 deletions.
4 changes: 3 additions & 1 deletion packages/frontend-2/components/feedback/Dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const props = withDefaults(
title?: string
intro?: string
hideSuppport?: boolean
metadata?: Record<string, unknown>
}>(),
{
type: 'general'
Expand Down Expand Up @@ -92,7 +93,8 @@ const onSubmit = handleSubmit(async () => {
mixpanel.track('Feedback Sent', {
message: feedback.value,
feedbackType: props.type
feedbackType: props.type,
...props.metadata
})
await sendWebhook(defaultZapierWebhookUrl, {
Expand Down
95 changes: 77 additions & 18 deletions packages/frontend-2/components/viewer/gendo/Dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,98 @@
>
<CommonLoadingIcon />
</button>
<div class="w-full h-full min-h-96">
<div class="flex items-center justify-center w-full h-full min-h-96">
<NuxtImg
:src="renderUrl"
:alt="renderPrompt"
class="relative z-10 w-full h-full max-h-[70vh] max-w-[80vw] object-contain"
class="relative z-10 w-full h-full max-h-[70vh] max-w-[80vw] object-contain rounded-xl"
/>
</div>
<div class="relative z-10 flex gap-2">
<FormButton
:to="renderUrl"
external
target="_blank"
download
color="outline"
:icon-left="ArrowDownTrayIcon"
>
Download
</FormButton>
<FormButton color="outline" :icon-left="XMarkIcon" @click="isOpen = false">
Close
</FormButton>
<div
class="bg-foundation rounded-md p-2 relative z-10 flex items-center justify-between gap-2 mb-4 w-full"
>
<div class="flex gap-2 h-8">
<FormButton :to="renderUrl" external target="_blank" download color="outline">
Download
</FormButton>
<FormButton color="subtle" @click="isOpen = false">Close</FormButton>
</div>
<div v-if="!hasFeedback" class="flex items-center gap-2">
<p class="text-body-xs text-foreground-2">
What do you think about this render?
</p>
<FormButton
:icon-left="HandThumbUpIcon"
hide-text
color="subtle"
@click="handleFeedback('positive')"
>
Thumbs up
</FormButton>
<FormButton
:icon-left="HandThumbDownIcon"
hide-text
color="subtle"
@click="handleFeedback('negative')"
>
Thumbs down
</FormButton>
</div>
<div v-else class="flex items-center gap-2">
<p class="text-body-xs text-foreground-2">Thanks for your feedback!</p>
<FormButton color="outline" @click="isFeedbackDialogOpen = true">
Give detailed feedback
</FormButton>
</div>
</div>
</div>
<FeedbackDialog
v-model:open="isFeedbackDialogOpen"
type="gendo"
:intro="feedbackIntro"
:metadata="{
initialFeedback: initialFeedback,
render_url: renderUrl,
prompt: renderPrompt
}"
/>
</LayoutDialog>
</template>

<script setup lang="ts">
import { XMarkIcon, ArrowDownTrayIcon } from '@heroicons/vue/24/solid'
import { HandThumbUpIcon, HandThumbDownIcon } from '@heroicons/vue/24/outline'
import { useMixpanel } from '~/lib/core/composables/mp'
type FeedbackValue = 'positive' | 'negative'
defineProps<{
const mixpanel = useMixpanel()
const props = defineProps<{
renderUrl?: string
renderPrompt?: string
}>()
const isOpen = defineModel<boolean>('open', { required: true })
const isFeedbackDialogOpen = ref(false)
const hasFeedback = ref(false)
const initialFeedback = ref<FeedbackValue>()
const feedbackIntro = computed(() => {
if (initialFeedback.value === 'positive') {
return "Great to hear you liked this render! Help us make Gendo even better - what worked well about this render? Are there specific features you'd like to see?"
}
return "We're sorry this render didn't meet your expectations. Please help us improve - what aspects didn't work well? What would have made this render more useful for you?"
})
const handleFeedback = (value: FeedbackValue) => {
initialFeedback.value = value
mixpanel.track('Gendo Render Feedback Given', {
feedback: value,
prompt: props.renderPrompt,
renderUrl: props.renderUrl
})
hasFeedback.value = true
}
</script>
75 changes: 52 additions & 23 deletions packages/frontend-2/components/viewer/gendo/Panel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</CommonBadge>
</div>
</template>
<div class="pt-3">
<div v-if="!loading" class="pt-3">
<div class="px-3 flex flex-col gap-y-3">
<CommonAlert v-if="!limits" color="danger" size="xs">
<template #title>No credits available</template>
Expand Down Expand Up @@ -49,11 +49,9 @@
size="lg"
:placeholder="randomPlaceholder"
color="foundation"
:disabled="isLoading || timeOutWait || isOutOfCredits"
:disabled="textAreaDisabled"
textarea-classes="sm:!min-h-24"
@keypress.enter.prevent="
!isLoading && !timeOutWait && !isOutOfCredits && prompt && enqueMagic()
"
@keypress.enter.prevent="!buttonDisabled && enqueMagic()"
/>
<div class="flex justify-between gap-2 items-center text-foreground-2">
<FormButton
Expand All @@ -70,26 +68,24 @@
</FormButton>

<div
:key="`gendo-credits-${isOutOfCredits}`"
v-tippy="
!limits
? 'No credits available'
: isOutOfCredits
? 'No credits remaining'
: undefined
"
v-if="!limits"
:key="`gendo-tooltip-${buttonDisabled}`"
v-tippy="`No credits available`"
>
<FormButton
:disabled="!prompt || isLoading || timeOutWait || isOutOfCredits"
@click="enqueMagic()"
>
<FormButton disabled>Generate</FormButton>
</div>
<div
v-else
:key="`gendo-tooltip-${buttonDisabled}`"
v-tippy="tooltipMessage"
>
<FormButton :disabled="buttonDisabled" @click="enqueMagic()">
Generate
</FormButton>
</div>
</div>
</div>
<ViewerGendoList @reuse-prompt="prompt = $event" />
<!-- Empty div to maintain flex gapping -->
</div>
<div
class="flex w-full items-center justify-between gap-2 border-t border-outline-2 py-1 px-1"
Expand All @@ -112,13 +108,20 @@
</FormButton>
</div>
</div>
<template v-if="limits" #actions>
<div v-else class="flex w-full h-full items-center justify-center">
<CommonLoadingIcon />
</div>
<template v-if="!loading && limits" #actions>
<div class="text-body-2xs p-1">
{{ limits.used }}/{{ limits.limit }} free renders used
<span class="hidden-under-250">this month</span>
</div>
</template>
<FeedbackDialog v-model:open="isFeedbackOpen" type="gendo" />
<FeedbackDialog
v-model:open="isFeedbackOpen"
intro="Help us improve Gendo AI renders. What did you like or dislike? How could we improve the experience for you and your workflow?"
type="gendo"
/>
</ViewerLayoutPanel>
</template>
<script setup lang="ts">
Expand All @@ -135,11 +138,12 @@ import { useMixpanel } from '~/lib/core/composables/mp'
import { CommonAlert, CommonBadge } from '@speckle/ui-components'
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline'
import dayjs from 'dayjs'
import { canModifyModels } from '~/lib/projects/helpers/permissions'
const {
projectId,
resources: {
response: { resourceItems }
response: { resourceItems, project }
},
ui: { camera },
viewer: { instance: viewerInstance }
Expand All @@ -166,16 +170,34 @@ const suggestedPrompts = ref<string[]>([
const isGendoEnabled = useIsGendoModuleEnabled()
const canContribute = computed(() =>
project.value ? canModifyModels(project.value) : false
)
const isGendoPanelEnabled = computed(() => !!activeUser.value && !!isGendoEnabled.value)
const { result, refetch } = useQuery(activeUserGendoLimits, undefined, {
const { result, refetch, loading } = useQuery(activeUserGendoLimits, undefined, {
enabled: isGendoPanelEnabled.value
})
const limits = computed(() => {
return result?.value?.activeUser?.gendoAICredits
})
const textAreaDisabled = computed(() => {
return (
isLoading.value ||
timeOutWait.value ||
isOutOfCredits.value ||
!canContribute.value ||
!activeUser.value
)
})
const buttonDisabled = computed(() => {
return !prompt.value || textAreaDisabled.value
})
const randomPlaceholder = computed(() => {
const randomIndex = Math.floor(Math.random() * suggestedPrompts.value.length)
return suggestedPrompts.value[randomIndex]
Expand Down Expand Up @@ -258,10 +280,17 @@ const lodgeRequest = async (screenshot: string) => {
} else {
triggerNotification({
type: ToastNotificationType.Success,
title: 'Render successfully enqued'
title: 'Render successfully enqueued'
})
}
isLoading.value = false
refetch()
}
const tooltipMessage = computed(() => {
if (!activeUser.value) return 'You must be logged in'
if (!canContribute.value) return 'Project permissions required'
if (isOutOfCredits.value) return 'No credits remaining'
return undefined
})
</script>

0 comments on commit c9fb11c

Please sign in to comment.