Skip to content

Commit

Permalink
feat: modify body with ai & feedback on ai requests `hoppscotch-commo…
Browse files Browse the repository at this point in the history
…n` bindings (#4386)

Co-authored-by: jamesgeorge007 <[email protected]>
  • Loading branch information
amk-dev and jamesgeorge007 authored Sep 30, 2024
1 parent 5e9f874 commit 0b5a424
Show file tree
Hide file tree
Showing 13 changed files with 791 additions and 79 deletions.
10 changes: 9 additions & 1 deletion packages/hoppscotch-common/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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?"
}
}
1 change: 1 addition & 0 deletions packages/hoppscotch-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand All @@ -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>
Expand All @@ -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()
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 0b5a424

Please sign in to comment.