npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wovvmap-ai-assistant

v1.0.3

Published

Wovv AI Assistant Cross-platform Package

Readme

@wovvmap/ai-assistant

A headless React SDK for integrating the WovVMall AI Navigation Assistant (PrismOne) into web and mobile maps.

This package is designed as a headless core, meaning it provides all the API services, state management, automatic kiosk session timeouts, and parsing logic, but leaves the UI rendering entirely up to the developer.


Installation

Install the package via npm or yarn:

npm install @wovvmap/ai-assistant
# or
yarn add @wovvmap/ai-assistant

Make sure react is installed in your host project as a peer dependency.


Core Concepts

useAIAssistant React Hook

The primary entry point is the useAIAssistant hook. It handles:

  • Session initiation and auto-renewals.
  • Idle timeouts for kiosk deployments (auto-clears chat history after 2 minutes of inactivity).
  • Sending user queries and parsing response formats (cards, suggestions, route directions, clarifications).
  • Error boundaries and typing loaders.

Configuration Options

When calling useAIAssistant(options), you can configure the hook with:

| Option | Type | Required | Default | Description | | :--- | :--- | :--- | :--- | :--- | | mapId | string | Yes | - | The ID of the mall or map. | | channel | 'web' \| 'mobile' \| 'kiosk' | Yes | - | Deployment channel. Kiosk auto-clears on 2m inactivity. | | baseUrl | string | No | 'https://mapai.wovvtech.site' | The base URL of the Wovv AI backend. | | maxHistory | number | No | 6 | Maximum messages context history limit. | | initialGreeting | string | No | - | Optional greeting message to populate chat. | | onNavigate | (toKey: string) => void | No | - | Triggered when the user clicks a card navigation button. | | onShowDirection | (fromKey: string, toKey: string) => void | No | - | Triggered when the user requests route directions. | | onClearRoute | () => void | No | - | Triggered if routing states need to be reset. |


Hook Return Values

The hook returns:

const {
  messages,      // Array of ChatMessage objects (contains user/assistant/error items)
  isTyping,      // Boolean indicating if assistant is typing
  error,         // Error string or null if successful
  sendMessage,   // Function to send a text query: (text: string) => void
  clearChat      // Function to reset chat history: () => void
} = useAIAssistant(options);

Usage Examples

1. React Web Implementation

Here is a basic implementation of a customized Chat Modal on the Web (using Tailwind CSS):

import React, { useState, useRef, useEffect } from 'react';
import { useAIAssistant } from '@wovvmap/ai-assistant';

export function WebAIChatModal({ mapId, onClose }) {
  const [input, setInput] = useState('');
  const messagesEndRef = useRef<HTMLDivElement>(null);

  const { messages, isTyping, error, sendMessage } = useAIAssistant({
    mapId,
    channel: 'kiosk',
    onNavigate: (toKey) => {
      console.log('Navigate to store key:', toKey);
      // Implement map navigation logic
    },
    onShowDirection: (fromKey, toKey) => {
      console.log(`Draw path from ${fromKey} to ${toKey}`);
      // Implement map routing path logic
    }
  });

  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages, isTyping]);

  const handleSend = () => {
    if (!input.trim() || isTyping) return;
    sendMessage(input.trim());
    setInput('');
  };

  return (
    <div className="flex h-96 w-80 flex-col bg-white border rounded-xl shadow-lg">
      {/* Header */}
      <div className="p-3 border-b flex justify-between">
        <span className="font-bold">PrismOne Assistant</span>
        <button onClick={onClose}>×</button>
      </div>

      {/* Messages */}
      <div className="flex-1 overflow-y-auto p-3 space-y-2">
        {messages.map((msg) => (
          <div
            key={msg.id}
            className={`p-2 rounded-lg text-sm max-w-[85%] ${
              msg.role === 'user'
                ? 'bg-blue-600 text-white ml-auto'
                : msg.role === 'error'
                ? 'bg-red-100 text-red-700'
                : 'bg-gray-100 text-gray-800'
            }`}
          >
            {msg.text}

            {/* Render Store Cards */}
            {msg.cards && msg.cards.map((card) => (
              <div key={card.key} className="mt-2 p-2 bg-white border rounded">
                <p className="font-semibold text-xs">{card.name}</p>
                <button
                  onClick={() => onNavigate && onNavigate(card.key)}
                  className="mt-1 text-xs text-blue-600 underline"
                >
                  Show on Map
                </button>
              </div>
            ))}
          </div>
        ))}
        {isTyping && <div className="text-xs text-gray-400 italic">Thinking...</div>}
        {error && <div className="text-xs text-red-500">Error: {error}</div>}
        <div ref={messagesEndRef} />
      </div>

      {/* Input */}
      <div className="p-2 border-t flex gap-1">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask me anything..."
          className="flex-1 border rounded px-2 text-sm"
        />
        <button onClick={handleSend} className="bg-blue-600 text-white px-3 py-1 rounded">
          Send
        </button>
      </div>
    </div>
  );
}

2. React Native (Mobile) Implementation

import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet } from 'react-native';
import { useAIAssistant } from '@wovvmap/ai-assistant';

export function MobileAIChat({ mapId }) {
  const [input, setInput] = useState('');
  const { messages, isTyping, sendMessage } = useAIAssistant({
    mapId,
    channel: 'mobile',
    onNavigate: (key) => {
      // Handle mobile navigation events
    }
  });

  const handleSend = () => {
    if (!input.trim()) return;
    sendMessage(input.trim());
    setInput('');
  };

  return (
    <View style={styles.container}>
      <ScrollView style={styles.chatArea}>
        {messages.map((msg) => (
          <View
            key={msg.id}
            style={[
              styles.bubble,
              msg.role === 'user' ? styles.userBubble : styles.assistantBubble,
            ]}
          >
            <Text style={msg.role === 'user' ? styles.userText : styles.assistantText}>
              {msg.text}
            </Text>
          </View>
        ))}
      </ScrollView>
      <View style={styles.inputRow}>
        <TextInput
          value={input}
          onChangeText={setInput}
          style={styles.input}
          placeholder="Ask PrismOne..."
        />
        <TouchableOpacity onPress={handleSend} style={styles.sendButton}>
          <Text style={styles.sendText}>Send</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#fff' },
  chatArea: { flex: 1, padding: 12 },
  bubble: { padding: 10, borderRadius: 12, marginVertical: 4, maxWidth: '80%' },
  userBubble: { backgroundColor: '#A2298C', alignSelf: 'flex-end' },
  assistantBubble: { backgroundColor: '#f0f0f0', alignSelf: 'flex-start' },
  userText: { color: '#fff' },
  assistantText: { color: '#333' },
  inputRow: { flexDirection: 'row', padding: 8, borderTopWidth: 1, borderColor: '#eee' },
  input: { flex: 1, height: 40, borderWidth: 1, borderColor: '#ddd', borderRadius: 8, paddingHorizontal: 10 },
  sendButton: { backgroundColor: '#A2298C', justifyContent: 'center', paddingHorizontal: 16, borderRadius: 8, marginLeft: 6 },
  sendText: { color: '#fff', fontWeight: 'bold' },
});

License

MIT