Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8d7c13f
Convert vuex to pinia for product-expert
n-lark Mar 17, 2026
d410e61
Merge remote-tracking branch 'origin/6827-pinia-task-10-product-exper…
n-lark Mar 17, 2026
67202be
Fix account bridge imports
n-lark Mar 17, 2026
120e7bd
Fix circular dep in cypress tests
n-lark Mar 17, 2026
68a8c9c
Fix additional circular dependencies
n-lark Mar 17, 2026
8f6cef3
Update the guard for selectedNodes within context expert
n-lark Mar 17, 2026
4d1160e
Merge in latest and update from expert/scalability changes
n-lark Mar 19, 2026
a882f66
Clean up store and fix spec
n-lark Mar 19, 2026
288f2ce
Update pinia store, specs & usage
n-lark Mar 19, 2026
32aad4b
Merge remote-tracking branch 'origin/6827-pinia-task-10-product-exper…
n-lark Mar 19, 2026
18aa36d
Fix issues with circular dep and lazy imports
n-lark Mar 19, 2026
c0cea90
Add todo for removing lazy require"
n-lark Mar 19, 2026
fb72300
Merge branch '6827-pinia-task-10-product-expert-operator-agent' into …
cstns Mar 23, 2026
fe9ac28
Merge and update name of ff-agent to insights
n-lark Mar 23, 2026
53170f2
Merge branch '6827-pinia-task-10-product-expert-operator-agent' into …
n-lark Mar 25, 2026
a36f7fc
Remove expert-context store, update specs, fix issue with importing c…
n-lark Mar 25, 2026
7626265
Change insights to support agent
n-lark Mar 25, 2026
82f1c3e
Change operator agent to insights
n-lark Mar 25, 2026
9c5192d
Fix issue with stale loading text when swapping modes
n-lark Mar 25, 2026
650d144
Merge branch 'main' into 6828-pinia-task-11-product-expert
n-lark Mar 26, 2026
a9a058f
Refactor `reset` method to use `$reset` and remove unused `persist` c…
cstns Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion frontend/src/api/client.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import axios from 'axios'

import Alerts from '../services/alerts.js'
import store from '../store/index.js'

const client = axios.create({
headers: {
Expand All @@ -20,6 +19,10 @@ client.interceptors.response.use(function (response) {
}

// This is an error response from our own API (or failure to reach it)
// Lazy require to avoid circular dependency:
// api/client.js → store/index.js → account/index.js → routes.js → page components → api modules → api/client.js
// TODO: remove once the `account` Vuex module is migrated to Pinia — replace with a direct import of the Pinia account store.
const store = require('../store/index.js').default
if (error.code === 'ERR_NETWORK') {
// Backend failed to respond
store.dispatch('account/setOffline', true)
Expand Down
11 changes: 4 additions & 7 deletions frontend/src/components/ExpertButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
</template>

<script>
import { mapState } from 'pinia'
import { mapActions } from 'vuex'
import { mapActions, mapState } from 'pinia'

import { useProductExpertStore } from '@/stores/product-expert.js'
import { useUxDrawersStore } from '@/stores/ux-drawers.js'

export default {
Expand All @@ -28,12 +28,9 @@ export default {
}
},
methods: {
...mapActions('product/expert', ['openAssistantDrawer']),
...mapActions(useProductExpertStore, ['openAssistantDrawer']),
onClick () {
const openOptions = {
openPinned: this.rightDrawer.expertState.pinned
}
this.openAssistantDrawer(openOptions)
this.openAssistantDrawer({ openPinned: this.rightDrawer.expertState.pinned })
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/drawers/RightDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@

<script>
import { mapActions, mapState } from 'pinia'
import { mapActions as mapVuexActions } from 'vuex'

import { useProductExpertStore } from '@/stores/product-expert.js'
import { useUxDrawersStore } from '@/stores/ux-drawers.js'

const DRAWER_MIN_WIDTH = 310
Expand Down Expand Up @@ -204,7 +204,7 @@ export default {
}
},
methods: {
...mapVuexActions('product/expert', ['openAssistantDrawer']),
...mapActions(useProductExpertStore, ['openAssistantDrawer']),
...mapActions(useUxDrawersStore, ['closeRightDrawer', 'togglePinDrawer']),
closeDrawer () {
if (this.rightDrawer.state && this.rightDrawer.closeOnClickOutside) {
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/drawers/expert/ExpertDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@
<script>
import { LockClosedIcon, LockOpenIcon, XIcon } from '@heroicons/vue/solid'
import { mapActions, mapState } from 'pinia'
import { mapActions as mapVuexActions, mapState as mapVuexState } from 'vuex'

import ToggleButtonGroup from '../../elements/ToggleButtonGroup.vue'

import ExpertPanel from '../../expert/Expert.vue'

import { useProductExpertStore } from '@/stores/product-expert.js'
import { useUxDrawersStore } from '@/stores/ux-drawers.js'

export default {
Expand All @@ -62,11 +62,11 @@ export default {
inject: ['togglePinWithWidth', 'shouldAllowPinning'],
computed: {
...mapState(useUxDrawersStore, ['rightDrawer']),
...mapVuexState('product/expert', ['agentMode']),
...mapState(useProductExpertStore, ['agentMode']),
agentModeButtons () {
return [
{ title: 'Support', value: 'ff-agent' },
{ title: 'Insights', value: 'operator-agent' }
{ title: 'Support', value: 'support-agent' },
{ title: 'Insights', value: 'insights-agent' }
]
},
isPinned () {
Expand All @@ -89,7 +89,7 @@ export default {
},
methods: {
...mapActions(useUxDrawersStore, ['closeRightDrawer']),
...mapVuexActions('product/expert', ['setAgentMode']),
...mapActions(useProductExpertStore, ['setAgentMode']),
closeDrawer () {
this.closeRightDrawer()
},
Expand Down
39 changes: 16 additions & 23 deletions frontend/src/components/expert/Expert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

<script>
import { mapActions, mapState } from 'pinia'
import { mapGetters, mapActions as mapVuexActions, mapState as mapVuexState } from 'vuex'

import ExpertChatInput from './components/ExpertChatInput.vue'
import ExpertMessages from './components/ExpertMessages.vue'
Expand All @@ -33,6 +32,8 @@ import InfoBanner from './components/InfoBanner.vue'
import UpdateBanner from './components/UpdateBanner.vue'

import { useProductAssistantStore } from '@/stores/product-assistant.js'
import { useProductExpertInsightsAgentStore } from '@/stores/product-expert-insights-agent.js'
import { useProductExpertStore } from '@/stores/product-expert.js'
import { useUxDrawersStore } from '@/stores/ux-drawers.js'

export default {
Expand Down Expand Up @@ -69,16 +70,11 @@ export default {
}
},
computed: {
...mapVuexState('product/expert', [
'agentMode'
]),
...mapGetters('product/expert', [
...mapState(useProductExpertStore, [
'abortController',
'agentMode',
'messages',
'isSessionExpired',
'isFfAgent',
'isOperatorAgent',
'hasSelectedCapabilities'
'isInsightsAgent'
]),
...mapState(useUxDrawersStore, {
isPinned: state => state.rightDrawer.fixed
Expand All @@ -92,7 +88,7 @@ export default {
return this.agentMode
},
set (value) {
this.$store.dispatch('product/expert/setAgentMode', value)
this.setAgentMode(value)
}
},
isInstanceRunning () {
Expand All @@ -104,15 +100,11 @@ export default {
watch: {
agentMode: {
immediate: true,
async handler (newMode) {
if (this.isOperatorAgent) {
await this.$store.dispatch(
`product/expert/${newMode}/getCapabilities`
)
async handler () {
if (this.isInsightsAgent) {
await this.getCapabilities()
}
await this.$store.dispatch(
'product/expert/addWelcomeMessageIfNeeded'
)
this.addWelcomeMessageIfNeeded()
}
},
'instance.meta.state': {
Expand All @@ -135,9 +127,7 @@ export default {
if (this.isEditorContext) {
// Delay to ensure drawer is open and visible before typing animation starts
setTimeout(() => {
this.$store.dispatch(
'product/expert/addWelcomeMessageIfNeeded'
)
this.addWelcomeMessageIfNeeded()
}, 1000)
}
},
Expand All @@ -149,10 +139,13 @@ export default {
this.resetSessionTimer()
},
methods: {
...mapVuexActions('product/expert', [
...mapActions(useProductExpertStore, [
'setAgentMode',
'setAbortController',
'resetSessionTimer'
'resetSessionTimer',
'addWelcomeMessageIfNeeded'
]),
...mapActions(useProductExpertInsightsAgentStore, ['getCapabilities']),
...mapActions(useProductAssistantStore, ['reset']),
handleStopGeneration () {
if (this.abortController) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@

<script>
import { ListboxOption } from '@headlessui/vue'
import { mapActions, mapGetters, mapState } from 'vuex'
import { mapActions, mapState } from 'pinia'

import { OPERATOR_AGENT } from '../../../store/modules/product/expert/agents.js'
import FfCheckbox from '../../../ui-components/components/form/Checkbox.vue'
import FfListbox from '../../../ui-components/components/form/ListBox.vue'

import { useProductExpertInsightsAgentStore } from '@/stores/product-expert-insights-agent.js'

export default {
name: 'CapabilitiesSelector',
components: {
Expand All @@ -72,10 +73,10 @@ export default {
FfListbox
},
computed: {
...mapState(`product/expert/${OPERATOR_AGENT}`, [
'selectedCapabilities'
...mapState(useProductExpertInsightsAgentStore, [
'selectedCapabilities',
'capabilities'
]),
...mapGetters(`product/expert/${OPERATOR_AGENT}`, ['capabilities']),
hasCapabilities () {
return this.capabilities && this.capabilities.length > 0
},
Expand All @@ -89,7 +90,7 @@ export default {
}
},
methods: {
...mapActions(`product/expert/${OPERATOR_AGENT}`, [
...mapActions(useProductExpertInsightsAgentStore, [
'setSelectedCapabilities'
]),
onCheckboxClick (option) {
Expand Down
24 changes: 12 additions & 12 deletions frontend/src/components/expert/components/ExpertChatInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
Start over
</button>
<div class="right-buttons">
<capabilities-selector v-if="isOperatorAgent" />
<capabilities-selector v-if="isInsightsAgent" />
</div>
</div>
<div class="input-wrapper" :class="{ 'focused': isTextareaFocused }">
Expand All @@ -34,7 +34,7 @@

<div class="actions">
<div class="left">
<context-selector v-if="isImmersive && !isOperatorAgent" />
<context-selector v-if="isImmersive && !isInsightsAgent" />
</div>

<div class="right">
Expand Down Expand Up @@ -63,7 +63,6 @@

<script>
import { mapActions, mapState } from 'pinia'
import { mapGetters, mapActions as mapVuexActions } from 'vuex'

import { useResizingHelper } from '../../../composables/ResizingHelper.js'

Expand All @@ -73,6 +72,7 @@ import CapabilitiesSelector from './CapabilitiesSelector.vue'
import ContextSelector from './context-selection/index.vue'

import { useProductAssistantStore } from '@/stores/product-assistant.js'
import { useProductExpertStore } from '@/stores/product-expert.js'
import { useUxDrawersStore } from '@/stores/ux-drawers.js'

export default {
Expand Down Expand Up @@ -116,31 +116,31 @@ export default {
'immersiveInstance',
'immersiveDevice'
]),
...mapGetters('product/expert', [
...mapState(useUxDrawersStore, ['rightDrawer']),
...mapState(useProductExpertStore, [
'messages',
'isSessionExpired',
'isOperatorAgent',
'isInsightsAgent',
'hasSelectedCapabilities',
'hasMessages',
'isWaitingForResponse'
]),
isInputDisabled () {
if (this.isSessionExpired) return true
if (this.isWaitingForResponse) return true
return this.isOperatorAgent && !this.hasSelectedCapabilities
return this.isInsightsAgent && !this.hasSelectedCapabilities
},
isDrawerPinned () {
const uxDrawerStore = useUxDrawersStore()
return uxDrawerStore.rightDrawer.fixed
return this.rightDrawer.fixed
},
canSend () {
return this.inputText.trim().length > 0 && !this.isInputDisabled
},
placeholderText () {
if (this.isOperatorAgent && !this.hasSelectedCapabilities) {
if (this.isInsightsAgent && !this.hasSelectedCapabilities) {
return 'Select a resource to get started'
}
return this.isOperatorAgent
return this.isInsightsAgent
? 'Tell us what you want to know about'
: 'Tell us what you need help with'
},
Expand All @@ -158,7 +158,7 @@ export default {
},
methods: {
...mapActions(useProductAssistantStore, ['resetContextSelection']),
...mapVuexActions('product/expert', ['startOver', 'handleQuery', 'handleMessageResponse']),
...mapActions(useProductExpertStore, ['startOver', 'handleQuery', 'handleMessageResponse']),
async handleSend () {
if (!this.canSend) return

Expand Down Expand Up @@ -190,7 +190,7 @@ export default {

this.inputText = ''
// When in support mode, reset/restore assistant context selection (opt-out by default)
if (!this.isOperatorAgent) {
if (!this.isInsightsAgent) {
this.resetContextSelection()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
</template>

<script>
import { mapState } from 'vuex'
import { mapState } from 'pinia'

import { FF_AGENT, OPERATOR_AGENT } from '../../../store/modules/product/expert/agents.js'
import { INSIGHTS_AGENT, SUPPORT_AGENT } from '@/stores/product-expert-agents.js'

import { useProductExpertStore } from '@/stores/product-expert.js'

export default {
name: 'ExpertLoadingIndicator',
Expand All @@ -23,14 +25,14 @@ export default {
showMessage: false,
currentMessageIndex: 0,
messageVariants: {
[FF_AGENT]: [
[SUPPORT_AGENT]: [
'Ingesting the docs...',
'Reading the blog...',
'Searching through FlowFuse knowledge base',
'Analyzing your question...',
'Finding the best answer...'
],
[OPERATOR_AGENT]: [
[INSIGHTS_AGENT]: [
'Connecting to MCP resources...',
'Querying your Node-RED instances...',
'Instructing MCP tooling...',
Expand All @@ -51,7 +53,7 @@ export default {
}
},
computed: {
...mapState('product/expert', ['loadingVariant']),
...mapState(useProductExpertStore, ['loadingVariant']),
messages () {
return this.messageVariants[this.loadingVariant]
},
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/components/expert/components/ExpertMessages.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
</template>

<script>

import { mapState } from 'pinia'
import { markRaw } from 'vue'
import { mapGetters } from 'vuex'

import ExpertLoadingIndicator from './ExpertLoadingIndicator.vue'

import AiMessage from './messages/AiMessage.vue'
import HumanMessage from './messages/HumanMessage.vue'
import SystemMessage from './messages/SystemMessage.vue'

import { useProductExpertStore } from '@/stores/product-expert.js'

export default {
name: 'ExpertMessages',
components: { ExpertLoadingIndicator },
Expand All @@ -32,7 +35,7 @@ export default {
}
},
computed: {
...mapGetters('product/expert', ['messages', 'isWaitingForResponse']),
...mapState(useProductExpertStore, ['messages', 'isWaitingForResponse']),
messageTypes () {
return {
ai: markRaw(AiMessage),
Expand Down
Loading
Loading