| 1 |
import { isChangeSiteDesignWorkflowAvailable, makeId } from '@agent/lib/util'; |
| 2 |
import apiFetch from '@wordpress/api-fetch'; |
| 3 |
import { __ } from '@wordpress/i18n'; |
| 4 |
import { create } from 'zustand'; |
| 5 |
import { createJSONStorage, devtools, persist } from 'zustand/middleware'; |
| 6 |
|
| 7 |
const { chatHistory } = window.extAgentData; |
| 8 |
|
| 9 |
const welcomeMessage = [ |
| 10 |
{ |
| 11 |
id: 1, |
| 12 |
type: 'message', |
| 13 |
details: { |
| 14 |
role: 'assistant', |
| 15 |
// translators: this is the initial message in the agent chat, welcoming the user. Keep it short and friendly and follow the same markdown format and emoji. |
| 16 |
content: isChangeSiteDesignWorkflowAvailable() |
| 17 |
? __( |
| 18 |
'#### Your site is ready 🎉\nWant to explore other website designs?', |
| 19 |
'extendify-local', |
| 20 |
) |
| 21 |
: __( |
| 22 |
'#### Your site is ready 🎉\nWant to explore other site colors?', |
| 23 |
'extendify-local', |
| 24 |
), |
| 25 |
}, |
| 26 |
}, |
| 27 |
]; |
| 28 |
const state = (set, get) => ({ |
| 29 |
messages: chatHistory?.length ? chatHistory.toReversed() : welcomeMessage, |
| 30 |
// Messages sent to the api, user and assistant only. Up until the last workflow |
| 31 |
getMessagesForAI: () => { |
| 32 |
const messages = []; |
| 33 |
let foundUserMessage = false; |
| 34 |
for (const { type, details } of get().messages.toReversed()) { |
| 35 |
const finished = |
| 36 |
['completed', 'canceled'].includes(details.status) || |
| 37 |
(['status'].includes(type) && details.type === 'workflow-canceled'); |
| 38 |
if (type === 'workflow' && finished) break; |
| 39 |
if (type === 'workflow-component' && finished) break; |
| 40 |
// This prevents a loop of assistant messages from being at the end |
| 41 |
if (type === 'message' && details.role === 'user') { |
| 42 |
foundUserMessage = true; |
| 43 |
} |
| 44 |
if (type === 'message' && !foundUserMessage) continue; |
| 45 |
if (type === 'message') messages.push(details); |
| 46 |
} |
| 47 |
return messages.toReversed(); |
| 48 |
}, |
| 49 |
getLastAssistantMessage: () => |
| 50 |
get()?.messages?.findLast( |
| 51 |
(message) => |
| 52 |
message.type === 'message' && message.details?.role === 'assistant', |
| 53 |
), |
| 54 |
hasMessages: () => get().messages.length > 0, |
| 55 |
addMessage: (type, details) => { |
| 56 |
const id = makeId(); |
| 57 |
set((state) => { |
| 58 |
// max 150 messages |
| 59 |
const max = Math.max(0, state.messages.length - 149); |
| 60 |
const next = { id, type, details }; |
| 61 |
return { |
| 62 |
// { id: 1, type: message, details: { role: 'user', content: 'Hello' } } |
| 63 |
// { id: 2, type: message, details: { role: 'assistant', content: 'Hi there!' } } |
| 64 |
// { id: 3, type: workflow, details: { name: 'Workflow 1' } } |
| 65 |
// { id: 5, type: status, details: { type: 'calling-agent' } |
| 66 |
messages: [...state.messages.toSpliced(0, max), next], |
| 67 |
}; |
| 68 |
}); |
| 69 |
return id; |
| 70 |
}, |
| 71 |
// pop messages all the way back to the last agent message |
| 72 |
popMessage: () => { |
| 73 |
set((state) => ({ |
| 74 |
messages: state.messages?.slice(0, -1) || [], |
| 75 |
})); |
| 76 |
}, |
| 77 |
clearMessages: () => set({ messages: [] }), |
| 78 |
}); |
| 79 |
|
| 80 |
const path = '/extendify/v1/agent/chat-events'; |
| 81 |
const storage = { |
| 82 |
getItem: async () => await apiFetch({ path }), |
| 83 |
setItem: async (_name, state) => |
| 84 |
await apiFetch({ path, method: 'POST', data: { state } }), |
| 85 |
}; |
| 86 |
|
| 87 |
export const useChatStore = create()( |
| 88 |
persist(devtools(state, { name: 'Extendify Agent Chat' }), { |
| 89 |
name: `extendify-agent-chat-${window.extSharedData.siteId}`, |
| 90 |
storage: createJSONStorage(() => storage), |
| 91 |
skipHydration: true, |
| 92 |
}), |
| 93 |
); |
| 94 |
|