Spaces:
Sleeping
Sleeping
| /** | |
| * @license | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { create } from 'zustand'; | |
| import { agriculturalTools } from './tools/agricultural-tools'; | |
| export type Template = 'agricultural-advisor'; | |
| const toolsets: Record<Template, FunctionCall[]> = { | |
| 'agricultural-advisor': agriculturalTools, | |
| }; | |
| import { | |
| AGRICULTURAL_AGENT_PROMPT, | |
| SCAVENGER_HUNT_PROMPT, | |
| } from './constants.ts'; | |
| const systemPrompts: Record<Template, string> = { | |
| 'agricultural-advisor': AGRICULTURAL_AGENT_PROMPT, | |
| }; | |
| import { DEFAULT_LIVE_API_MODEL, DEFAULT_VOICE } from './constants'; | |
| import { | |
| GenerateContentResponse, | |
| FunctionResponse, | |
| FunctionResponseScheduling, | |
| LiveServerToolCall, | |
| GroundingChunk, | |
| } from '@google/genai'; | |
| import { Map3DCameraProps } from '@/components/map-3d'; | |
| /** | |
| * Personas | |
| */ | |
| export const SCAVENGER_HUNT_PERSONA = | |
| 'ClueMaster Cory, the Scavenger Hunt Creator'; | |
| export const personas: Record<string, { prompt: string; voice: string }> = { | |
| [SCAVENGER_HUNT_PERSONA]: { | |
| prompt: SCAVENGER_HUNT_PROMPT, | |
| voice: 'Puck', | |
| }, | |
| }; | |
| /** | |
| * Settings | |
| */ | |
| export const useSettings = create<{ | |
| systemPrompt: string; | |
| model: string; | |
| voice: string; | |
| isEasterEggMode: boolean; | |
| activePersona: string; | |
| setSystemPrompt: (prompt: string) => void; | |
| setModel: (model: string) => void; | |
| setVoice: (voice: string) => void; | |
| setPersona: (persona: string) => void; | |
| activateEasterEggMode: () => void; | |
| }>(set => ({ | |
| systemPrompt: systemPrompts['agricultural-advisor'], | |
| model: DEFAULT_LIVE_API_MODEL, | |
| voice: DEFAULT_VOICE, | |
| isEasterEggMode: false, | |
| activePersona: SCAVENGER_HUNT_PERSONA, | |
| setSystemPrompt: prompt => set({ systemPrompt: prompt }), | |
| setModel: model => set({ model }), | |
| setVoice: voice => set({ voice }), | |
| setPersona: (persona: string) => { | |
| if (personas[persona]) { | |
| set({ | |
| activePersona: persona, | |
| systemPrompt: personas[persona].prompt, | |
| voice: personas[persona].voice, | |
| }); | |
| } | |
| }, | |
| activateEasterEggMode: () => { | |
| set(state => { | |
| if (!state.isEasterEggMode) { | |
| const persona = SCAVENGER_HUNT_PERSONA; | |
| return { | |
| isEasterEggMode: true, | |
| activePersona: persona, | |
| systemPrompt: personas[persona].prompt, | |
| voice: personas[persona].voice, | |
| model: 'gemini-live-2.5-flash-preview', // gemini-2.5-flash-preview-native-audio-dialog | |
| }; | |
| } | |
| return {}; | |
| }); | |
| }, | |
| })); | |
| /** | |
| * UI | |
| */ | |
| export const useUI = create<{ | |
| isSidebarOpen: boolean; | |
| toggleSidebar: () => void; | |
| showSystemMessages: boolean; | |
| toggleShowSystemMessages: () => void; | |
| }>(set => ({ | |
| isSidebarOpen: false, | |
| toggleSidebar: () => set(state => ({ isSidebarOpen: !state.isSidebarOpen })), | |
| showSystemMessages: false, | |
| toggleShowSystemMessages: () => | |
| set(state => ({ showSystemMessages: !state.showSystemMessages })), | |
| })); | |
| /** | |
| * Tools | |
| */ | |
| export interface FunctionCall { | |
| name: string; | |
| description?: string; | |
| parameters?: any; | |
| isEnabled: boolean; | |
| scheduling?: FunctionResponseScheduling; | |
| } | |
| export const useTools = create<{ | |
| tools: FunctionCall[]; | |
| template: Template; | |
| setTemplate: (template: Template) => void; | |
| }>(set => ({ | |
| tools: agriculturalTools, | |
| template: 'agricultural-advisor', | |
| setTemplate: (template: Template) => { | |
| set({ tools: toolsets[template], template }); | |
| useSettings.getState().setSystemPrompt(systemPrompts[template]); | |
| }, | |
| })); | |
| /** | |
| * Logs | |
| */ | |
| export interface LiveClientToolResponse { | |
| functionResponses?: FunctionResponse[]; | |
| } | |
| // FIX: Update GroundingChunk to match the type from @google/genai, where uri and title are optional. | |
| // export interface GroundingChunk { | |
| // web?: { | |
| // uri?: string; | |
| // title?: string; | |
| // }; | |
| // maps?: { | |
| // uri?: string; | |
| // title?: string; | |
| // placeId: string; | |
| // placeAnswerSources?: any; | |
| // }; | |
| // } | |
| export interface ConversationTurn { | |
| timestamp: Date; | |
| role: 'user' | 'agent' | 'system'; | |
| text: string; | |
| isFinal: boolean; | |
| toolUseRequest?: LiveServerToolCall; | |
| toolUseResponse?: LiveClientToolResponse; | |
| groundingChunks?: GroundingChunk[]; | |
| toolResponse?: GenerateContentResponse; | |
| } | |
| export const useLogStore = create<{ | |
| turns: ConversationTurn[]; | |
| isAwaitingFunctionResponse: boolean; | |
| addTurn: (turn: Omit<ConversationTurn, 'timestamp'>) => void; | |
| updateLastTurn: (update: Partial<ConversationTurn>) => void; | |
| mergeIntoLastAgentTurn: ( | |
| update: Omit<ConversationTurn, 'timestamp' | 'role'>, | |
| ) => void; | |
| clearTurns: () => void; | |
| setIsAwaitingFunctionResponse: (isAwaiting: boolean) => void; | |
| }>((set, get) => ({ | |
| turns: [], | |
| isAwaitingFunctionResponse: false, | |
| addTurn: (turn: Omit<ConversationTurn, 'timestamp'>) => | |
| set(state => ({ | |
| turns: [...state.turns, { ...turn, timestamp: new Date() }], | |
| })), | |
| updateLastTurn: (update: Partial<Omit<ConversationTurn, 'timestamp'>>) => { | |
| set(state => { | |
| if (state.turns.length === 0) { | |
| return state; | |
| } | |
| const newTurns = [...state.turns]; | |
| const lastTurn = { ...newTurns[newTurns.length - 1], ...update }; | |
| newTurns[newTurns.length - 1] = lastTurn; | |
| return { turns: newTurns }; | |
| }); | |
| }, | |
| mergeIntoLastAgentTurn: ( | |
| update: Omit<ConversationTurn, 'timestamp' | 'role'>, | |
| ) => { | |
| set(state => { | |
| const turns = state.turns; | |
| const lastAgentTurnIndex = turns.map(t => t.role).lastIndexOf('agent'); | |
| if (lastAgentTurnIndex === -1) { | |
| // Fallback: add a new turn. | |
| return { | |
| turns: [ | |
| ...turns, | |
| { ...update, role: 'agent', timestamp: new Date() } as ConversationTurn, | |
| ], | |
| }; | |
| } | |
| const lastAgentTurn = turns[lastAgentTurnIndex]; | |
| const mergedTurn: ConversationTurn = { | |
| ...lastAgentTurn, | |
| text: lastAgentTurn.text + (update.text || ''), | |
| isFinal: update.isFinal, | |
| groundingChunks: [ | |
| ...(lastAgentTurn.groundingChunks || []), | |
| ...(update.groundingChunks || []), | |
| ], | |
| toolResponse: update.toolResponse || lastAgentTurn.toolResponse, | |
| }; | |
| // Rebuild the turns array, replacing the old agent turn. | |
| const newTurns = [...turns]; | |
| newTurns[lastAgentTurnIndex] = mergedTurn; | |
| return { turns: newTurns }; | |
| }); | |
| }, | |
| clearTurns: () => set({ turns: [] }), | |
| setIsAwaitingFunctionResponse: isAwaiting => | |
| set({ isAwaitingFunctionResponse: isAwaiting }), | |
| })); | |
| /** | |
| * Map Entities | |
| */ | |
| export interface MapMarker { | |
| position: { | |
| lat: number; | |
| lng: number; | |
| altitude: number; | |
| }; | |
| label: string; | |
| showLabel: boolean; | |
| placeId?: string; | |
| } | |
| export interface MapRectangularOverlay { | |
| center: { | |
| lat: number; | |
| lng: number; | |
| altitude: number; | |
| }; | |
| corners: { | |
| northEast: { lat: number; lng: number; altitude: number }; | |
| northWest: { lat: number; lng: number; altitude: number }; | |
| southEast: { lat: number; lng: number; altitude: number }; | |
| southWest: { lat: number; lng: number; altitude: number }; | |
| }; | |
| width: number; // in meters | |
| height: number; // in meters | |
| label: string; | |
| color: string; | |
| } | |
| export const useMapStore = create<{ | |
| markers: MapMarker[]; | |
| rectangularOverlays: MapRectangularOverlay[]; | |
| cameraTarget: Map3DCameraProps | null; | |
| preventAutoFrame: boolean; | |
| setMarkers: (markers: MapMarker[]) => void; | |
| clearMarkers: () => void; | |
| setRectangularOverlays: (overlays: MapRectangularOverlay[]) => void; | |
| clearRectangularOverlays: () => void; | |
| setCameraTarget: (target: Map3DCameraProps | null) => void; | |
| setPreventAutoFrame: (prevent: boolean) => void; | |
| }>(set => ({ | |
| markers: [], | |
| rectangularOverlays: [], | |
| cameraTarget: null, | |
| preventAutoFrame: false, | |
| setMarkers: markers => set({ markers }), | |
| clearMarkers: () => set({ markers: [] }), | |
| setRectangularOverlays: overlays => set({ rectangularOverlays: overlays }), | |
| clearRectangularOverlays: () => set({ rectangularOverlays: [] }), | |
| setCameraTarget: target => set({ cameraTarget: target }), | |
| setPreventAutoFrame: prevent => set({ preventAutoFrame: prevent }), | |
| })); |