Agricrop / lib /state.ts
Rohanbagulwar
Commitingcode
7c2c194
/**
* @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 }),
}));