feat: modify body with ai & feedback on ai requests hoppscotch-common bindings (#4386)

Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
Akash K
2024-09-30 22:21:14 +05:30
committed by GitHub
parent 5e9f8743d4
commit 0b5a424b69
13 changed files with 791 additions and 79 deletions

View File

@@ -1138,6 +1138,14 @@
},
"ai_experiments": {
"generate_request_name": "Generate Request Name Using AI",
"generate_or_modify_request_body": "Generate or Modify Request Body"
"generate_or_modify_request_body": "Generate or Modify Request Body",
"modify_with_ai": "Modify with AI",
"generate_or_modify_request_body_input_placeholder": "Enter your prompt to modify request body",
"accept_change": "Accept Change",
"feedback_success": "Feedback submitted successfully",
"feedback_failure": "Failed to submit feedback",
"feedback_thank_you": "Thank you for your feedback!",
"feedback_cta_text_long": "Rate the generation, helps us to improve",
"feedback_cta_request_name": "Did you like name generated?"
}
}

View File

@@ -30,6 +30,7 @@
"@codemirror/language": "6.10.1",
"@codemirror/legacy-modes": "6.3.3",
"@codemirror/lint": "6.5.0",
"@codemirror/merge": "6.7.0",
"@codemirror/search": "6.5.6",
"@codemirror/state": "6.4.1",
"@codemirror/view": "6.25.1",

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import { jsonLanguage } from "@codemirror/lang-json"
import { MergeView } from "@codemirror/merge"
import { onUnmounted, ref, watch } from "vue"
import { basicSetup, baseTheme } from "@helpers/editor/themes/baseTheme"
import { EditorState } from "@codemirror/state"
type MergeViewContent = {
content: string
langMime: string
}
const props = defineProps<{
contentLeft: MergeViewContent
contentRight: MergeViewContent
}>()
const diffEditor = ref<Element | null>(null)
let mergeView: MergeView | null = null
watch(
() => props.contentRight,
() => {
if (!mergeView) {
return
}
if (!props.contentRight.content) {
return
}
mergeView.b.dispatch({
changes: {
from: 0,
to: mergeView.b.state.doc.length,
insert: props.contentRight.content,
},
})
}
)
watch(
diffEditor,
() => {
if (!diffEditor.value) {
return
}
mergeView = new MergeView({
a: {
doc: props.contentLeft.content,
extensions: [
jsonLanguage,
basicSetup,
baseTheme,
EditorState.readOnly.of(true),
],
},
b: {
doc: props.contentRight.content,
extensions: [jsonLanguage, baseTheme, basicSetup],
},
// @ts-expect-error attribute mismatch
parent: diffEditor.value,
highlightChanges: false,
})
},
{
immediate: true,
}
)
onUnmounted(() => {
if (mergeView) {
mergeView.destroy()
}
})
</script>
<template>
<div ref="diffEditor"></div>
</template>

View File

@@ -0,0 +1,140 @@
<script setup lang="ts">
import IconArrowRight from "~icons/lucide/arrow-right"
import IconThumbsUp from "~icons/lucide/thumbs-up"
import IconThumbsDown from "~icons/lucide/thumbs-down"
import {
useModifyRequestBody,
useSubmitFeedback,
} from "~/composables/ai-experiments"
import { ref } from "vue"
import { useI18n } from "~/composables/i18n"
const t = useI18n()
const props = defineProps<{
currentBody: string
}>()
const emit = defineEmits<{
(e: "closeModal"): void
(e: "updateBody", body: string): void
}>()
const generatedBodyContent = ref("")
const userPrompt = ref("")
const { modifyRequestBody, isModifyRequestBodyPending, lastTraceID } =
useModifyRequestBody(props.currentBody, userPrompt, generatedBodyContent)
const submittedFeedback = ref(false)
const { submitFeedback, isSubmitFeedbackPending } = useSubmitFeedback()
</script>
<template>
<HoppSmartModal styles="sm:max-w-3xl" full-width>
<template #body>
<div class="flex flex-col border-b border-divider transition relative">
<div class="flex items-center pt-3 pb-3 sticky">
<input
id="command"
v-model="userPrompt"
v-focus
type="text"
autocomplete="off"
name="command"
:placeholder="`${t(
'ai_experiments.generate_or_modify_request_body_input_placeholder'
)}`"
class="flex flex-1 bg-transparent px-6 text-base text-secondaryDark"
/>
<HoppButtonSecondary
:icon="IconArrowRight"
class="mr-6 rounded-md"
outline
filled
:loading="isModifyRequestBodyPending"
:disabled="!userPrompt || isModifyRequestBodyPending"
@click="modifyRequestBody"
/>
</div>
<div>
<AiexperimentsMergeView
:content-left="{
content: currentBody ?? '',
langMime: 'application/json',
}"
:content-right="{
content: generatedBodyContent,
langMime: 'application/json',
}"
></AiexperimentsMergeView>
</div>
</div>
</template>
<template #footer>
<div class="flex gap-1 px-6 py-3 justify-between items-center w-full">
<div
v-if="lastTraceID && !submittedFeedback"
class="flex items-center gap-2"
>
<p>{{ t("ai_experiments.feedback_cta_text_long") }}</p>
<template v-if="!isSubmitFeedbackPending">
<HoppButtonSecondary
:icon="IconThumbsUp"
outline
@click="
async () => {
if (lastTraceID) {
await submitFeedback('positive', lastTraceID)
submittedFeedback = true
}
}
"
/>
<HoppButtonSecondary
:icon="IconThumbsDown"
outline
@click="submitFeedback('negative', lastTraceID)"
/>
</template>
<HoppSmartSpinner v-else />
</div>
<div v-if="submittedFeedback">
<p>{{ t("ai_experiments.feedback_thank_you") }}</p>
</div>
<div class="ml-auto space-x-2">
<HoppButtonSecondary
:label="t('action.cancel')"
outline
@click="
() => {
emit('closeModal')
}
"
/>
<HoppButtonSecondary
:label="t('ai_experiments.accept_change')"
outline
filled
:disabled="isModifyRequestBodyPending || !generatedBodyContent"
@click="
() => {
emit('updateBody', generatedBodyContent)
emit('closeModal')
}
"
/>
</div>
</div>
</template>
</HoppSmartModal>
</template>

View File

@@ -3,7 +3,7 @@
v-if="show"
dialog
:title="t('request.new')"
@close="$emit('hide-modal')"
@close="hideModal"
>
<template #body>
<div class="flex gap-1">
@@ -30,20 +30,61 @@
</div>
</template>
<template #footer>
<span class="flex space-x-2">
<HoppButtonPrimary
:label="t('action.save')"
:loading="loadingState"
outline
@click="addRequest"
/>
<HoppButtonSecondary
:label="t('action.cancel')"
outline
filled
@click="hideModal"
/>
</span>
<div class="flex justify-between items-center w-full">
<div class="flex space-x-2">
<HoppButtonPrimary
:label="t('action.save')"
:loading="loadingState"
outline
@click="addRequest"
/>
<HoppButtonSecondary
:label="t('action.cancel')"
outline
filled
@click="hideModal"
/>
</div>
<div
v-if="lastTraceID && !submittedFeedback"
class="flex items-center gap-2"
>
<p>{{ t("ai_experiments.feedback_cta_request_name") }}</p>
<template v-if="!isSubmitFeedbackPending">
<HoppButtonSecondary
:icon="IconThumbsUp"
outline
@click="
async () => {
if (lastTraceID) {
await submitFeedback('positive', lastTraceID)
submittedFeedback = true
}
}
"
/>
<HoppButtonSecondary
:icon="IconThumbsDown"
outline
@click="
() => {
if (lastTraceID) {
submitFeedback('negative', lastTraceID)
submittedFeedback = true
}
}
"
/>
</template>
<template v-else>
<HoppSmartSpinner />
</template>
</div>
<div v-if="submittedFeedback">
<p>{{ t("ai_experiments.feedback_thank_you") }}</p>
</div>
</div>
</template>
</HoppSmartModal>
</template>
@@ -54,9 +95,14 @@ import { useI18n } from "@composables/i18n"
import { useToast } from "@composables/toast"
import { useService } from "dioc/vue"
import { RESTTabService } from "~/services/tab/rest"
import { useRequestNameGeneration } from "~/composables/ai-experiments"
import {
useRequestNameGeneration,
useSubmitFeedback,
} from "~/composables/ai-experiments"
import { HoppRESTRequest } from "@hoppscotch/data"
import IconSparkle from "~icons/lucide/sparkles"
import IconThumbsUp from "~icons/lucide/thumbs-up"
import IconThumbsDown from "~icons/lucide/thumbs-down"
const toast = useToast()
const t = useI18n()
@@ -85,8 +131,22 @@ const {
generateRequestName,
isGenerateRequestNamePending,
canDoRequestNameGeneration,
lastTraceID,
} = useRequestNameGeneration(editingName)
watch(
() => props.show,
(newVal) => {
if (!newVal) {
submittedFeedback.value = false
lastTraceID.value = null
}
}
)
const submittedFeedback = ref(false)
const { submitFeedback, isSubmitFeedbackPending } = useSubmitFeedback()
const tabs = useService(RESTTabService)
watch(
() => props.show,

View File

@@ -30,20 +30,61 @@
</div>
</template>
<template #footer>
<span class="flex space-x-2">
<HoppButtonPrimary
:label="t('action.save')"
:loading="loadingState"
outline
@click="editRequest"
/>
<HoppButtonSecondary
:label="t('action.cancel')"
outline
filled
@click="hideModal"
/>
</span>
<div class="flex justify-between items-center w-full">
<div class="flex space-x-2">
<HoppButtonPrimary
:label="t('action.save')"
:loading="loadingState"
outline
@click="editRequest"
/>
<HoppButtonSecondary
:label="t('action.cancel')"
outline
filled
@click="hideModal"
/>
</div>
<div
v-if="lastTraceID && !submittedFeedback"
class="flex items-center gap-2"
>
<p>{{ t("ai_experiments.feedback_cta_request_name") }}</p>
<template v-if="!isSubmitFeedbackPending">
<HoppButtonSecondary
:icon="IconThumbsUp"
outline
@click="
async () => {
if (lastTraceID) {
await submitFeedback('positive', lastTraceID)
submittedFeedback = true
}
}
"
/>
<HoppButtonSecondary
:icon="IconThumbsDown"
outline
@click="
() => {
if (lastTraceID) {
submitFeedback('negative', lastTraceID)
submittedFeedback = true
}
}
"
/>
</template>
<template v-else>
<HoppSmartSpinner />
</template>
</div>
<div v-if="submittedFeedback">
<p>{{ t("ai_experiments.feedback_thank_you") }}</p>
</div>
</div>
</template>
</HoppSmartModal>
</template>
@@ -53,8 +94,14 @@ import { useI18n } from "@composables/i18n"
import { useToast } from "@composables/toast"
import { HoppRESTRequest } from "@hoppscotch/data"
import { useVModel } from "@vueuse/core"
import { useRequestNameGeneration } from "~/composables/ai-experiments"
import { ref, watch } from "vue"
import {
useRequestNameGeneration,
useSubmitFeedback,
} from "~/composables/ai-experiments"
import IconSparkle from "~icons/lucide/sparkles"
import IconThumbsUp from "~icons/lucide/thumbs-up"
import IconThumbsDown from "~icons/lucide/thumbs-down"
const toast = useToast()
const t = useI18n()
@@ -85,8 +132,22 @@ const {
generateRequestName,
canDoRequestNameGeneration,
isGenerateRequestNamePending,
lastTraceID,
} = useRequestNameGeneration(editingName)
watch(
() => props.show,
(newVal) => {
if (!newVal) {
submittedFeedback.value = false
lastTraceID.value = null
}
}
)
const submittedFeedback = ref(false)
const { submitFeedback, isSubmitFeedbackPending } = useSubmitFeedback()
const editRequest = () => {
if (editingName.value.trim() === "") {
toast.error(t("request.invalid_name"))

View File

@@ -52,20 +52,61 @@
</div>
</template>
<template #footer>
<span class="flex space-x-2">
<HoppButtonPrimary
:label="`${t('action.save')}`"
:loading="modalLoadingState"
outline
@click="saveRequestAs"
/>
<HoppButtonSecondary
:label="`${t('action.cancel')}`"
outline
filled
@click="hideModal"
/>
</span>
<div class="flex justify-between items-center w-full">
<div class="flex space-x-2">
<HoppButtonPrimary
:label="`${t('action.save')}`"
:loading="modalLoadingState"
outline
@click="saveRequestAs"
/>
<HoppButtonSecondary
:label="`${t('action.cancel')}`"
outline
filled
@click="hideModal"
/>
</div>
<div
v-if="lastTraceID && !submittedFeedback"
class="flex items-center gap-2"
>
<p>{{ t("ai_experiments.feedback_cta_request_name") }}</p>
<template v-if="!isSubmitFeedbackPending">
<HoppButtonSecondary
:icon="IconThumbsUp"
outline
@click="
async () => {
if (lastTraceID) {
await submitFeedback('positive', lastTraceID)
submittedFeedback = true
}
}
"
/>
<HoppButtonSecondary
:icon="IconThumbsDown"
outline
@click="
() => {
if (lastTraceID) {
submitFeedback('negative', lastTraceID)
submittedFeedback = true
}
}
"
/>
</template>
<template v-else>
<HoppSmartSpinner />
</template>
</div>
<div v-if="submittedFeedback">
<p>{{ t("ai_experiments.feedback_thank_you") }}</p>
</div>
</div>
</template>
</HoppSmartModal>
</template>
@@ -84,7 +125,10 @@ import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function"
import { cloneDeep } from "lodash-es"
import { computed, nextTick, reactive, ref, watch } from "vue"
import { useRequestNameGeneration } from "~/composables/ai-experiments"
import {
useRequestNameGeneration,
useSubmitFeedback,
} from "~/composables/ai-experiments"
import { GQLError } from "~/helpers/backend/GQLClient"
import {
createRequestInCollection,
@@ -103,6 +147,8 @@ import { GQLTabService } from "~/services/tab/graphql"
import { RESTTabService } from "~/services/tab/rest"
import { TeamWorkspace } from "~/services/workspace.service"
import IconSparkle from "~icons/lucide/sparkles"
import IconThumbsDown from "~icons/lucide/thumbs-down"
import IconThumbsUp from "~icons/lucide/thumbs-up"
const t = useI18n()
const toast = useToast()
@@ -185,8 +231,22 @@ const {
canDoRequestNameGeneration,
generateRequestName,
isGenerateRequestNamePending,
lastTraceID,
} = useRequestNameGeneration(requestName)
watch(
() => props.show,
(newVal) => {
if (!newVal) {
submittedFeedback.value = false
lastTraceID.value = null
}
}
)
const submittedFeedback = ref(false)
const { submitFeedback, isSubmitFeedbackPending } = useSubmitFeedback()
watch(
() => [RESTTabs.currentActiveTab.value, GQLTabs.currentActiveTab.value],
() => {

View File

@@ -30,19 +30,60 @@
</div>
</template>
<template #footer>
<span class="flex space-x-2">
<HoppButtonPrimary
:label="t('action.save')"
outline
@click="addRequest"
/>
<HoppButtonSecondary
:label="t('action.cancel')"
outline
filled
@click="hideModal"
/>
</span>
<div class="flex justify-between items-center w-full">
<div class="flex space-x-2">
<HoppButtonPrimary
:label="t('action.save')"
outline
@click="addRequest"
/>
<HoppButtonSecondary
:label="t('action.cancel')"
outline
filled
@click="hideModal"
/>
</div>
<div
v-if="lastTraceID && !submittedFeedback"
class="flex items-center gap-2"
>
<p>{{ t("ai_experiments.feedback_cta_request_name") }}</p>
<template v-if="!isSubmitFeedbackPending">
<HoppButtonSecondary
:icon="IconThumbsUp"
outline
@click="
async () => {
if (lastTraceID) {
await submitFeedback('positive', lastTraceID)
submittedFeedback = true
}
}
"
/>
<HoppButtonSecondary
:icon="IconThumbsDown"
outline
@click="
() => {
if (lastTraceID) {
submitFeedback('negative', lastTraceID)
submittedFeedback = true
}
}
"
/>
</template>
<template v-else>
<HoppSmartSpinner />
</template>
</div>
<div v-if="submittedFeedback">
<p>{{ t("ai_experiments.feedback_thank_you") }}</p>
</div>
</div>
</template>
</HoppSmartModal>
</template>
@@ -54,9 +95,14 @@ import { HoppRESTRequest } from "@hoppscotch/data"
import { useService } from "dioc/vue"
import { ref, watch } from "vue"
import { useRequestNameGeneration } from "~/composables/ai-experiments"
import {
useRequestNameGeneration,
useSubmitFeedback,
} from "~/composables/ai-experiments"
import { GQLTabService } from "~/services/tab/graphql"
import IconSparkle from "~icons/lucide/sparkles"
import IconThumbsUp from "~icons/lucide/thumbs-up"
import IconThumbsDown from "~icons/lucide/thumbs-down"
const toast = useToast()
const t = useI18n()
@@ -86,8 +132,22 @@ const {
generateRequestName,
isGenerateRequestNamePending,
canDoRequestNameGeneration,
lastTraceID,
} = useRequestNameGeneration(editingName)
watch(
() => props.show,
(newVal) => {
if (!newVal) {
submittedFeedback.value = false
lastTraceID.value = null
}
}
)
const submittedFeedback = ref(false)
const { submitFeedback, isSubmitFeedbackPending } = useSubmitFeedback()
watch(
() => props.show,
(show) => {

View File

@@ -30,19 +30,60 @@
</div>
</template>
<template #footer>
<span class="flex space-x-2">
<HoppButtonPrimary
:label="`${t('action.save')}`"
outline
@click="saveRequest"
/>
<HoppButtonSecondary
:label="`${t('action.cancel')}`"
outline
filled
@click="hideModal"
/>
</span>
<div class="flex justify-between items-center w-full">
<div class="flex space-x-2">
<HoppButtonPrimary
:label="`${t('action.save')}`"
outline
@click="saveRequest"
/>
<HoppButtonSecondary
:label="`${t('action.cancel')}`"
outline
filled
@click="hideModal"
/>
</div>
<div
v-if="lastTraceID && !submittedFeedback"
class="flex items-center gap-2"
>
<p>{{ t("ai_experiments.feedback_cta_request_name") }}</p>
<template v-if="!isSubmitFeedbackPending">
<HoppButtonSecondary
:icon="IconThumbsUp"
outline
@click="
async () => {
if (lastTraceID) {
await submitFeedback('positive', lastTraceID)
submittedFeedback = true
}
}
"
/>
<HoppButtonSecondary
:icon="IconThumbsDown"
outline
@click="
() => {
if (lastTraceID) {
submitFeedback('negative', lastTraceID)
submittedFeedback = true
}
}
"
/>
</template>
<template v-else>
<HoppSmartSpinner />
</template>
</div>
<div v-if="submittedFeedback">
<p>{{ t("ai_experiments.feedback_thank_you") }}</p>
</div>
</div>
</template>
</HoppSmartModal>
</template>
@@ -52,9 +93,14 @@ import { useI18n } from "@composables/i18n"
import { useToast } from "@composables/toast"
import { HoppGQLRequest } from "@hoppscotch/data"
import { ref, watch } from "vue"
import { useRequestNameGeneration } from "~/composables/ai-experiments"
import {
useRequestNameGeneration,
useSubmitFeedback,
} from "~/composables/ai-experiments"
import { editGraphqlRequest } from "~/newstore/collections"
import IconSparkle from "~icons/lucide/sparkles"
import IconThumbsUp from "~icons/lucide/thumbs-up"
import IconThumbsDown from "~icons/lucide/thumbs-down"
const t = useI18n()
const toast = useToast()
@@ -85,8 +131,22 @@ const {
canDoRequestNameGeneration,
generateRequestName,
isGenerateRequestNamePending,
lastTraceID,
} = useRequestNameGeneration(editingName)
watch(
() => props.show,
(newVal) => {
if (!newVal) {
submittedFeedback.value = false
lastTraceID.value = null
}
}
)
const submittedFeedback = ref(false)
const { submitFeedback, isSubmitFeedbackPending } = useSubmitFeedback()
const saveRequest = () => {
if (!editingName.value) {
toast.error(`${t("collection.invalid_name")}`)

View File

@@ -42,6 +42,13 @@
:icon="prettifyIcon"
@click="prettifyRequestBody"
/>
<HoppButtonSecondary
v-if="shouldEnableAIFeatures"
v-tippy="{ theme: 'tooltip' }"
:title="t('ai_experiments.modify_with_ai')"
:icon="IconSparkles"
@click="showModifyBodyModal"
/>
<label for="payload">
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
@@ -62,6 +69,13 @@
<div class="h-full relative flex flex-col flex-1">
<div ref="rawBodyParameters" class="absolute inset-0"></div>
</div>
<AiexperimentsModifyBodyModal
v-if="isModifyBodyModalOpen"
:current-body="codemirrorValue ?? ''"
@close-modal="isModifyBodyModalOpen = false"
@update-body="(updatedBody) => (codemirrorValue = updatedBody)"
></AiexperimentsModifyBodyModal>
</div>
</template>
@@ -73,6 +87,7 @@ import IconFilePlus from "~icons/lucide/file-plus"
import IconWand2 from "~icons/lucide/wand-2"
import IconCheck from "~icons/lucide/check"
import IconInfo from "~icons/lucide/info"
import IconSparkles from "~icons/lucide/sparkles"
import { computed, reactive, Ref, ref, watch } from "vue"
import * as TO from "fp-ts/TaskOption"
import { pipe } from "fp-ts/function"
@@ -90,6 +105,7 @@ import xmlFormat from "xml-formatter"
import { useNestedSetting } from "~/composables/settings"
import { toggleNestedSetting } from "~/newstore/settings"
import * as LJSON from "lossless-json"
import { useAIExperiments } from "~/composables/ai-experiments"
type PossibleContentTypes = Exclude<
ValidContentTypes,
@@ -203,6 +219,14 @@ const prettifyRequestBody = () => {
}
}
const isModifyBodyModalOpen = ref(false)
const showModifyBodyModal = () => {
isModifyBodyModalOpen.value = true
}
const { shouldEnableAIFeatures } = useAIExperiments()
const prettifyXML = (xml: string) => {
return xmlFormat(xml, {
indentation: " ",

View File

@@ -38,6 +38,8 @@ export const useRequestNameGeneration = (targetNameRef: Ref<string>) => {
return ENABLE_AI_EXPERIMENTS.value && !!platform.experiments?.aiExperiments
})
const lastTraceID = ref<string | null>(null)
const generateRequestName = async (
requestContext: HoppRESTRequest | HoppGQLRequest | null
) => {
@@ -65,7 +67,8 @@ export const useRequestNameGeneration = (targetNameRef: Ref<string>) => {
return
}
targetNameRef.value = result.right
targetNameRef.value = result.right.request_name
lastTraceID.value = result.right.trace_id
isGenerateRequestNamePending.value = false
}
@@ -74,5 +77,122 @@ export const useRequestNameGeneration = (targetNameRef: Ref<string>) => {
generateRequestName,
isGenerateRequestNamePending,
canDoRequestNameGeneration,
lastTraceID,
}
}
export const useAIExperiments = () => {
const currentUser = useReadonlyStream(
platform.auth.getCurrentUserStream(),
platform.auth.getCurrentUser()
)
const ENABLE_AI_EXPERIMENTS = useSetting("ENABLE_AI_EXPERIMENTS")
const shouldEnableAIFeatures = computed(() => {
// Request generation applies only to the authenticated state
if (!currentUser.value) {
return false
}
return ENABLE_AI_EXPERIMENTS.value && !!platform.experiments?.aiExperiments
})
return {
shouldEnableAIFeatures,
}
}
export const useModifyRequestBody = (
currentRequestBody: string,
userPromptRef: Ref<string>,
generatedRequestBodyRef: Ref<string>
) => {
const toast = useToast()
const t = useI18n()
const lastTraceID = ref<string | null>(null)
const isModifyRequestBodyPending = ref(false)
const modifyRequestBodyForPlatform =
platform.experiments?.aiExperiments?.modifyRequestBody
const modifyRequestBody = async () => {
isModifyRequestBodyPending.value = true
if (!modifyRequestBodyForPlatform) {
toast.error(t("request.modify_request_body_error"))
isModifyRequestBodyPending.value = false
return
}
const result = await modifyRequestBodyForPlatform(
currentRequestBody ?? "",
userPromptRef.value
)
if (result && E.isLeft(result)) {
toast.error(t("request.modify_request_body_error"))
isModifyRequestBodyPending.value = false
return
}
generatedRequestBodyRef.value = result.right.modified_body
lastTraceID.value = result.right.trace_id
isModifyRequestBodyPending.value = false
return result.right
}
return {
modifyRequestBody,
isModifyRequestBodyPending,
lastTraceID,
}
}
export const useSubmitFeedback = () => {
const submitFeedbackForPlatform =
platform.experiments?.aiExperiments?.submitFeedback
const t = useI18n()
const toast = useToast()
const isSubmitFeedbackPending = ref(false)
const submitFeedback = async (
rating: "positive" | "negative",
traceID: string
) => {
if (!submitFeedbackForPlatform) {
toast.error(t("ai_experiments.feedback_failure"))
return
}
isSubmitFeedbackPending.value = true
const res = await submitFeedbackForPlatform(
rating === "positive" ? 1 : -1,
traceID
)
if (E.isLeft(res)) {
toast.error(t("ai_experiments.feedback_failure"))
isSubmitFeedbackPending.value = false
return
}
isSubmitFeedbackPending.value = false
toast.success(t("ai_experiments.feedback_success"))
return E.right(undefined)
}
return {
submitFeedback,
isSubmitFeedbackPending,
}
}

View File

@@ -3,8 +3,30 @@ import * as E from "fp-ts/Either"
export type ExperimentsPlatformDef = {
aiExperiments?: {
enableAIExperiments: boolean
generateRequestName: (
requestInfo: string
) => Promise<E.Either<string, string>>
generateRequestName?: (requestInfo: string) => Promise<
E.Either<
string,
{
request_name: string
trace_id: string
}
>
>
modifyRequestBody?: (
requestBody: string,
userPrompt: string
) => Promise<
E.Either<
string,
{
modified_body: string
trace_id: string
}
>
>
submitFeedback?: (
rating: -1 | 1,
traceID: string
) => Promise<E.Either<string, void>>
}
}