diff --git a/frontend/src/components/expert/components/ExpertChatInput.vue b/frontend/src/components/expert/components/ExpertChatInput.vue index 5d165ee52c..f349c20f8d 100644 --- a/frontend/src/components/expert/components/ExpertChatInput.vue +++ b/frontend/src/components/expert/components/ExpertChatInput.vue @@ -17,6 +17,39 @@
+ +
  • + Follow-up questions +

    + When a request needs more detail, choose how the Expert asks for it. +

    + +
  • +
  • + Plan mode +

    + Create a plan before making changes. +

    + +
  • +
    @@ -65,6 +98,7 @@ import { mapActions, mapState } from 'pinia' import ResizeBar from '../../ResizeBar.vue' +import ToggleButtonGroup from '../../elements/ToggleButtonGroup.vue' import CapabilitiesSelector from './CapabilitiesSelector.vue' import ContextSelector from './context-selection/index.vue' @@ -80,7 +114,8 @@ export default { components: { CapabilitiesSelector, ContextSelector, - ResizeBar + ResizeBar, + ToggleButtonGroup }, inject: { togglePinWithWidth: { @@ -123,8 +158,39 @@ export default { 'isInsightsAgent', 'hasSelectedCapabilities', 'hasMessages', - 'isWaitingForResponse' + 'isWaitingForResponse', + 'pendingInput', + 'questionCadence', + 'planMode' ]), + questionCadenceButtons () { + return [ + { title: 'All at once', value: 'all' }, + { title: 'One at a time', value: 'one' } + ] + }, + questionCadenceWrapper: { + get () { + return this.questionCadence + }, + set (value) { + this.setQuestionCadence(value) + } + }, + planModeButtons () { + return [ + { title: 'Off', value: 'off' }, + { title: 'On', value: 'on' } + ] + }, + planModeWrapper: { + get () { + return this.planMode ? 'on' : 'off' + }, + set (value) { + this.setPlanMode(value === 'on') + } + }, isInputDisabled () { if (this.isSessionExpired) return true if (this.isWaitingForResponse) return true @@ -148,6 +214,17 @@ export default { return this.isImmersiveDevice || this.isImmersiveInstance } }, + watch: { + pendingInput (text) { + if (text) { + this.inputText = text + this.setPendingInput('') + this.$nextTick(() => { + this.$refs.textarea.focus() + }) + } + } + }, mounted () { this.bindResizer({ component: this.$refs.resizeTarget, @@ -158,7 +235,7 @@ export default { }, methods: { ...mapActions(useProductAssistantStore, ['resetContextSelection']), - ...mapActions(useProductExpertStore, ['startOver', 'handleQuery', 'handleMessageResponse']), + ...mapActions(useProductExpertStore, ['startOver', 'handleQuery', 'handleMessageResponse', 'setPendingInput', 'setQuestionCadence', 'setPlanMode']), async handleSend () { if (!this.canSend) return @@ -232,6 +309,7 @@ export default { .right-buttons { display: flex; gap: 0.5rem; + align-items: center; } button { @@ -350,3 +428,42 @@ button { } } + + + diff --git a/frontend/src/components/expert/components/messages/AiMessage.vue b/frontend/src/components/expert/components/messages/AiMessage.vue index 0fc8817324..2169233b5c 100644 --- a/frontend/src/components/expert/components/messages/AiMessage.vue +++ b/frontend/src/components/expert/components/messages/AiMessage.vue @@ -1,18 +1,23 @@ + + diff --git a/frontend/src/components/expert/components/messages/components/resource-cards/StandardResourceCard.vue b/frontend/src/components/expert/components/messages/components/resource-cards/StandardResourceCard.vue index 11a0f45c01..52fbf44d95 100644 --- a/frontend/src/components/expert/components/messages/components/resource-cards/StandardResourceCard.vue +++ b/frontend/src/components/expert/components/messages/components/resource-cards/StandardResourceCard.vue @@ -43,7 +43,7 @@ export default { emits: ['streaming-complete'], data () { return { - resourceUrl: this.resource.metadata?.streamable.source || this.resource.streamable.url, + resourceUrl: this.resource.metadata?.streamable?.source || this.resource.url?.streamable, resourceTitle: { ...this.resource.title }, resourceMetadataSource: this.resource.metadata?.source } diff --git a/frontend/src/components/expert/components/messages/components/resources/QuestionsList.vue b/frontend/src/components/expert/components/messages/components/resources/QuestionsList.vue new file mode 100644 index 0000000000..00a0daf5a1 --- /dev/null +++ b/frontend/src/components/expert/components/messages/components/resources/QuestionsList.vue @@ -0,0 +1,304 @@ + + + + + diff --git a/frontend/src/stores/context.js b/frontend/src/stores/context.js index cc89ea8c69..d22eee5f9f 100644 --- a/frontend/src/stores/context.js +++ b/frontend/src/stores/context.js @@ -57,7 +57,9 @@ export const useContextStore = defineStore('context', { pageName: null, rawRoute: {}, selectedNodes: null, - scope: 'ff-app' + scope: 'ff-app', + questionCadence: useProductExpertStore().questionCadence, + planMode: useProductExpertStore().planMode } } @@ -106,7 +108,9 @@ export const useContextStore = defineStore('context', { nodeRedVersion: assistantStore.nodeRedVersion, rawRoute, selectedNodes, - scope + scope, + questionCadence: useProductExpertStore().questionCadence, + planMode: useProductExpertStore().planMode } } }, diff --git a/frontend/src/stores/product-expert.js b/frontend/src/stores/product-expert.js index a1925a0ed6..1a3a5a2cf2 100644 --- a/frontend/src/stores/product-expert.js +++ b/frontend/src/stores/product-expert.js @@ -29,7 +29,10 @@ export const useProductExpertStore = defineStore('product-expert', { agentMode: SUPPORT_AGENT, // support-agent or insights-agent loadingVariant: SUPPORT_AGENT, shouldWakeUpAssistant: false, + questionCadence: 'all', // 'all' = ask every clarifying question at once, 'one' = one at a time + planMode: false, inFlightUpdates: [], + pendingInput: '', _seenTransactionIds: new Map() }), getters: { @@ -176,6 +179,9 @@ export const useProductExpertStore = defineStore('product-expert', { .then(() => { this.loadingVariant = this.agentMode }) } }, + setPendingInput (text) { + this.pendingInput = text + }, async handleQuery ({ query }) { const agentStore = this._agentStore @@ -499,6 +505,17 @@ export const useProductExpertStore = defineStore('product-expert', { this.agentMode = mode this.loadingVariant = mode }, + /** + * Sets how clarifying questions are asked: all at once or one at a time. + * @param {'all' | 'one'} cadence + */ + setQuestionCadence (cadence) { + if (!['all', 'one'].includes(cadence)) return + this.questionCadence = cadence + }, + setPlanMode (enabled) { + this.planMode = !!enabled + }, /** * Adds a system message to the application's message store. * @@ -1063,7 +1080,7 @@ export const useProductExpertStore = defineStore('product-expert', { } }, persist: { - pick: ['shouldWakeUpAssistant'], + pick: ['shouldWakeUpAssistant', 'questionCadence', 'planMode'], storage: localStorage } })