mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-08 03:08:21 +00:00
fixes and improvements
This commit is contained in:
parent
5995effa32
commit
244f924c04
11 changed files with 147 additions and 1781 deletions
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
title: "Convex"
|
||||
sidebarTitle: "Convex"
|
||||
description: "Add semantic memory and RAG to your Convex apps with reactive queries and smart caching"
|
||||
description: "Add semantic memory and RAG to your Convex apps with reactive queries"
|
||||
icon: "database"
|
||||
---
|
||||
|
||||
Supermemory integrates with [Convex](https://convex.dev) as a native component, giving you AI-powered semantic memory with reactive real-time queries, smart caching, and full dashboard visibility.
|
||||
Supermemory integrates with [Convex](https://convex.dev) as a native component, giving you AI-powered semantic memory with reactive real-time queries and full dashboard visibility.
|
||||
|
||||
<Card title="@supermemory/convex-component on npm" icon="npm" href="https://www.npmjs.com/package/@supermemory/convex-component">
|
||||
Check out the NPM page for more details
|
||||
|
|
@ -14,7 +14,7 @@ Supermemory integrates with [Convex](https://convex.dev) as a native component,
|
|||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @supermemory/convex-component convex
|
||||
npm install @supermemory/tools
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
|
@ -46,18 +46,18 @@ npx convex env set SUPERMEMORY_API_KEY your-api-key
|
|||
<Tabs>
|
||||
<Tab title="React Hooks">
|
||||
```tsx
|
||||
import { useAddMemory, useSupermemorySearch } from "@supermemory/convex-component/react";
|
||||
import { addMemory, searchMemories } from "@supermemory/convex-component/react";
|
||||
|
||||
function ChatApp() {
|
||||
const addMemory = useAddMemory();
|
||||
const { results, isLoading, search } = useSupermemorySearch({
|
||||
const add = addMemory();
|
||||
const { results, isLoading, search } = searchMemories({
|
||||
q: "user preferences",
|
||||
containerTag: "user_123",
|
||||
searchMode: "hybrid"
|
||||
});
|
||||
|
||||
const handleSendMessage = async (message: string) => {
|
||||
await addMemory({
|
||||
await add({
|
||||
content: message,
|
||||
containerTag: "user_123"
|
||||
});
|
||||
|
|
@ -112,14 +112,14 @@ npx convex env set SUPERMEMORY_API_KEY your-api-key
|
|||
|
||||
## React Hooks
|
||||
|
||||
### `useAddMemory()`
|
||||
### `addMemory()`
|
||||
|
||||
Add memories to Supermemory through a Convex action.
|
||||
|
||||
```tsx
|
||||
const addMemory = useAddMemory();
|
||||
const add = addMemory();
|
||||
|
||||
await addMemory({
|
||||
await add({
|
||||
content: "Meeting notes from Q1 planning",
|
||||
containerTag: "user_123",
|
||||
customId: "meeting_2024_q1",
|
||||
|
|
@ -127,12 +127,12 @@ await addMemory({
|
|||
});
|
||||
```
|
||||
|
||||
### `useSupermemorySearch(args)`
|
||||
### `searchMemories(args)`
|
||||
|
||||
Reactive semantic search with loading and error states.
|
||||
|
||||
```tsx
|
||||
const { results, isLoading, error, search } = useSupermemorySearch({
|
||||
const { results, isLoading, error, search } = searchMemories({
|
||||
q: "project updates",
|
||||
containerTag: "user_123",
|
||||
searchMode: "hybrid",
|
||||
|
|
@ -143,12 +143,12 @@ const { results, isLoading, error, search } = useSupermemorySearch({
|
|||
await search({ q: "new query", containerTag: "user_123" });
|
||||
```
|
||||
|
||||
### `useSupermemoryProfile(args)`
|
||||
### `getProfile(args)`
|
||||
|
||||
Get user profile with static and dynamic facts.
|
||||
|
||||
```tsx
|
||||
const { profile, isLoading, refresh } = useSupermemoryProfile({
|
||||
const { profile, isLoading, refresh } = getProfile({
|
||||
containerTag: "user_123",
|
||||
q: "recent preferences"
|
||||
});
|
||||
|
|
@ -157,68 +157,18 @@ const { profile, isLoading, refresh } = useSupermemoryProfile({
|
|||
await refresh();
|
||||
```
|
||||
|
||||
### `useDocumentList(args?)`
|
||||
|
||||
Reactively list all documents added to Supermemory.
|
||||
|
||||
```tsx
|
||||
const documents = useDocumentList({ containerTag: "user_123", limit: 20 });
|
||||
```
|
||||
|
||||
### `useMemories(args)`
|
||||
### `listMemories(args)`
|
||||
|
||||
List memories by container tag and source.
|
||||
|
||||
```tsx
|
||||
const memories = useMemories({
|
||||
const memories = listMemories({
|
||||
containerTag: "user_123",
|
||||
source: "manual", // "chat" | "document" | "manual"
|
||||
limit: 50
|
||||
});
|
||||
```
|
||||
|
||||
### `useApiStats(args?)`
|
||||
|
||||
Get API call statistics for dashboard visibility.
|
||||
|
||||
```tsx
|
||||
const stats = useApiStats({ containerTag: "user_123" });
|
||||
|
||||
// stats.totalCalls, stats.successfulCalls, stats.averageResponseTime
|
||||
```
|
||||
|
||||
### `useApiLogs(args?)`
|
||||
|
||||
View recent API call logs for debugging.
|
||||
|
||||
```tsx
|
||||
const logs = useApiLogs({ endpoint: "search", limit: 50 });
|
||||
```
|
||||
|
||||
## Client SDK
|
||||
|
||||
For non-React environments or server-side code:
|
||||
|
||||
```typescript
|
||||
import { ConvexHttpClient } from "convex/browser";
|
||||
import { createSupermemoryClient } from "@supermemory/convex-component";
|
||||
|
||||
const convex = new ConvexHttpClient(process.env.CONVEX_URL!);
|
||||
const client = createSupermemoryClient(convex);
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `client.add(args)` | Add content to Supermemory |
|
||||
| `client.search(args)` | Semantic search across memories |
|
||||
| `client.profile(args)` | Get user profile (static + dynamic facts) |
|
||||
| `client.listDocuments(args?)` | List indexed documents |
|
||||
| `client.getDocumentByCustomId(id)` | Get document by custom ID |
|
||||
| `client.getApiLogs(args?)` | View API call logs |
|
||||
| `client.getApiStats(args?)` | Get aggregated statistics |
|
||||
| `client.searchCached(args)` | Local text search in Convex cache |
|
||||
| `client.cleanCache()` | Remove expired cache entries |
|
||||
|
||||
## AI SDK Integration
|
||||
|
||||
Use Supermemory with Vercel AI SDK through the Convex backend.
|
||||
|
|
@ -253,120 +203,3 @@ const result = await generateText({
|
|||
| `promptTemplate` | function | — | Custom memory formatting function |
|
||||
| `verbose` | boolean | `false` | Enable debug logging |
|
||||
|
||||
### Tools (Agent-Based Memory)
|
||||
|
||||
```typescript
|
||||
import { streamText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { supermemoryConvexTools } from "@supermemory/convex-component/ai-sdk";
|
||||
import { ConvexHttpClient } from "convex/browser";
|
||||
|
||||
const convex = new ConvexHttpClient(process.env.CONVEX_URL!);
|
||||
|
||||
const result = await streamText({
|
||||
model: openai("gpt-4"),
|
||||
prompt: "Remember that I love TypeScript",
|
||||
tools: supermemoryConvexTools(convex, "user_123")
|
||||
});
|
||||
```
|
||||
|
||||
The tools include:
|
||||
- **`searchMemories`** — Semantic search through user's memories and past conversations
|
||||
- **`addMemory`** — Store new information about the user for future recall
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Container Tags
|
||||
|
||||
Use container tags to isolate memories by user, session, or project:
|
||||
|
||||
```typescript
|
||||
// Per user
|
||||
await client.add({ content: "...", containerTag: "user_alice" });
|
||||
await client.add({ content: "...", containerTag: "user_bob" });
|
||||
|
||||
// Per project
|
||||
await client.add({ content: "...", containerTag: "project_123" });
|
||||
```
|
||||
|
||||
### Metadata and Custom IDs
|
||||
|
||||
```typescript
|
||||
// Add with metadata
|
||||
await client.add({
|
||||
content: "Design doc for feature X",
|
||||
containerTag: "user_123",
|
||||
customId: "design_doc_v2",
|
||||
metadata: { type: "document", priority: "high" }
|
||||
});
|
||||
|
||||
// Update existing content using the same customId
|
||||
await client.add({
|
||||
content: "Updated design doc for feature X",
|
||||
containerTag: "user_123",
|
||||
customId: "design_doc_v2" // Same ID = update
|
||||
});
|
||||
```
|
||||
|
||||
### Search with Filters
|
||||
|
||||
```typescript
|
||||
const results = await client.search({
|
||||
q: "design documents",
|
||||
containerTag: "user_123",
|
||||
searchMode: "hybrid",
|
||||
threshold: 0.3,
|
||||
rerank: true,
|
||||
filters: {
|
||||
AND: [
|
||||
{ key: "type", value: "document" },
|
||||
{ key: "priority", value: "high" }
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Smart Caching
|
||||
|
||||
The component automatically caches API responses in Convex:
|
||||
|
||||
| Cache | TTL | Purpose |
|
||||
|-------|-----|---------|
|
||||
| Search results | 5 minutes | Reduce redundant search API calls |
|
||||
| User profiles | 2 minutes | Keep profiles fresh while limiting calls |
|
||||
|
||||
Cached data is served reactively — all connected clients see updates in real-time. Clean expired entries manually:
|
||||
|
||||
```typescript
|
||||
const cleanCache = useCleanCache();
|
||||
await cleanCache();
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Your React / Next.js App │
|
||||
│ useSupermemorySearch, useAddMemory, useApiStats... │
|
||||
└──────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Convex Backend │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
|
||||
│ │ Queries │ │Mutations │ │ Actions │ │
|
||||
│ │(Reactive)│ │ (Cache) │ │ (Supermemory API) │ │
|
||||
│ └─────┬────┘ └────┬─────┘ └────────┬─────────┘ │
|
||||
│ └────────────┬┘ │ │
|
||||
│ ┌──────────────────▼──────────────────┘ │
|
||||
│ │ Convex Tables: searchCache, profileCache, │
|
||||
│ │ documents, apiLogs, memories, analytics │
|
||||
│ └──────────────────────────────────────────────────│
|
||||
└──────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Supermemory API (supermemory.ai) │
|
||||
│ Semantic memory · User profiles · Hybrid search │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ Supermemory Convex Component integrates [Supermemory](https://supermemory.ai)'s
|
|||
- **User Profiles**: Automatic extraction of static and dynamic user facts
|
||||
- **Hybrid Search**: Combine memory extraction with document chunk search
|
||||
- **Reactive Queries**: Auto-updating UI components via Convex
|
||||
- **Smart Caching**: Reduce API calls with intelligent Convex-based caching
|
||||
- **Dashboard Visibility**: See all Supermemory API calls in your Convex dashboard
|
||||
- **Dashboard Visibility**: See all memories, API calls, chat sessions, and analytics in your Convex dashboard
|
||||
- **TypeScript First**: Fully typed SDK and React hooks
|
||||
|
||||
## Installation
|
||||
|
|
@ -55,11 +54,11 @@ npx convex env set SUPERMEMORY_API_KEY your-api-key
|
|||
#### React/Next.js with Hooks
|
||||
|
||||
```tsx
|
||||
import { useAddMemory, useSupermemorySearch } from "@supermemory/convex-component/react";
|
||||
import { addMemory, searchMemories } from "@supermemory/convex-component/react";
|
||||
|
||||
function ChatApp() {
|
||||
const addMemory = useAddMemory();
|
||||
const { results, isLoading, search } = useSupermemorySearch({
|
||||
const add = addMemory();
|
||||
const { results, isLoading, search } = searchMemories({
|
||||
q: "user preferences",
|
||||
containerTag: "user_123",
|
||||
searchMode: "hybrid"
|
||||
|
|
@ -67,7 +66,7 @@ function ChatApp() {
|
|||
|
||||
const handleSendMessage = async (message: string) => {
|
||||
// Add to memory
|
||||
await addMemory({
|
||||
await add({
|
||||
content: `User: ${message}`,
|
||||
containerTag: "user_123"
|
||||
});
|
||||
|
|
@ -128,14 +127,14 @@ console.log("Dynamic context:", profile.profile.dynamic);
|
|||
|
||||
### React Hooks
|
||||
|
||||
#### `useAddMemory(componentPath?)`
|
||||
#### `addMemory(componentPath?)`
|
||||
|
||||
Hook to add memories to Supermemory.
|
||||
Add memories to Supermemory.
|
||||
|
||||
```tsx
|
||||
const addMemory = useAddMemory();
|
||||
const add = addMemory();
|
||||
|
||||
await addMemory({
|
||||
await add({
|
||||
content: "Meeting notes from Q1 planning",
|
||||
containerTag: "user_123",
|
||||
customId: "meeting_2024_q1",
|
||||
|
|
@ -143,12 +142,12 @@ await addMemory({
|
|||
});
|
||||
```
|
||||
|
||||
#### `useSupermemorySearch(args, componentPath?)`
|
||||
#### `searchMemories(args, componentPath?)`
|
||||
|
||||
Hook for reactive semantic search.
|
||||
Search Supermemory with reactive results.
|
||||
|
||||
```tsx
|
||||
const { results, isLoading, error, search } = useSupermemorySearch({
|
||||
const { results, isLoading, error, search } = searchMemories({
|
||||
q: "project updates",
|
||||
containerTag: "user_123",
|
||||
searchMode: "hybrid",
|
||||
|
|
@ -159,12 +158,12 @@ const { results, isLoading, error, search } = useSupermemorySearch({
|
|||
await search({ q: "new query", containerTag: "user_123" });
|
||||
```
|
||||
|
||||
#### `useSupermemoryProfile(args, componentPath?)`
|
||||
#### `getProfile(args, componentPath?)`
|
||||
|
||||
Hook to get user profile with context.
|
||||
Get user profile with context.
|
||||
|
||||
```tsx
|
||||
const { profile, isLoading, refresh } = useSupermemoryProfile({
|
||||
const { profile, isLoading, refresh } = getProfile({
|
||||
containerTag: "user_123",
|
||||
q: "recent preferences"
|
||||
});
|
||||
|
|
@ -173,29 +172,23 @@ const { profile, isLoading, refresh } = useSupermemoryProfile({
|
|||
await refresh();
|
||||
```
|
||||
|
||||
#### `useDocumentList(args?, componentPath?)`
|
||||
#### `listMemories(args, componentPath?)`
|
||||
|
||||
Hook to list documents reactively.
|
||||
List memories reactively. Includes content and extracted memories.
|
||||
|
||||
```tsx
|
||||
const documents = useDocumentList({
|
||||
containerTag: "user_123",
|
||||
limit: 20
|
||||
});
|
||||
```
|
||||
|
||||
#### `useApiStats(args?, componentPath?)`
|
||||
|
||||
Hook to get API statistics for dashboard.
|
||||
|
||||
```tsx
|
||||
const stats = useApiStats({ containerTag: "user_123" });
|
||||
const memories = listMemories({ containerTag: "user_123", limit: 20 });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Total Calls: {stats?.totalCalls}</p>
|
||||
<p>Success Rate: {((stats?.successfulCalls / stats?.totalCalls) * 100).toFixed(1)}%</p>
|
||||
<p>Avg Response: {stats?.averageResponseTime.toFixed(0)}ms</p>
|
||||
{memories?.map(m => (
|
||||
<div key={m._id}>
|
||||
<p>{m.content}</p>
|
||||
{m.extractedMemories?.map((em, i) => (
|
||||
<span key={i}>{em}</span>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
|
@ -218,17 +211,8 @@ const results = await client.search({ q: "...", containerTag: "user_123" });
|
|||
// Get profile
|
||||
const profile = await client.profile({ containerTag: "user_123" });
|
||||
|
||||
// List documents
|
||||
const docs = await client.listDocuments({ containerTag: "user_123" });
|
||||
|
||||
// Get API logs
|
||||
const logs = await client.getApiLogs({ limit: 50 });
|
||||
|
||||
// Get stats
|
||||
const stats = await client.getApiStats();
|
||||
|
||||
// Clean cache
|
||||
await client.cleanCache();
|
||||
// List memories
|
||||
const memories = await client.listMemories({ containerTag: "user_123" });
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
|
@ -294,22 +278,13 @@ await addMemory({
|
|||
});
|
||||
```
|
||||
|
||||
### Cache Management
|
||||
|
||||
The component automatically caches search results (5 min) and profiles (2 min). Clean manually:
|
||||
|
||||
```typescript
|
||||
const cleanCache = useCleanCache();
|
||||
await cleanCache(); // Removes all expired entries
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Your Next.js/React App │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ useSupermemorySearch, useAddMemory, etc. │ │
|
||||
│ │ searchMemories, addMemory, listMemories │ │
|
||||
│ └────────────────────┬─────────────────────────────────┘ │
|
||||
└───────────────────────┼──────────────────────────────────────┘
|
||||
│
|
||||
|
|
@ -318,18 +293,19 @@ await cleanCache(); // Removes all expired entries
|
|||
│ Convex Backend │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ Queries │ │ Mutations │ │ Actions │ │
|
||||
│ │ (Reactive) │ │ (Tx Cache) │ │ (Supermemory │ │
|
||||
│ │ (Reactive) │ │ (Storage) │ │ (Supermemory │ │
|
||||
│ │ │ │ │ │ API Calls) │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └─────────────────┼─────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼──────────────────────────────┐ │
|
||||
│ │ Convex Tables (Smart Cache) │ │
|
||||
│ │ - searchCache: Search results with TTL │ │
|
||||
│ │ - profileCache: User profiles with TTL │ │
|
||||
│ │ - documents: Document metadata │ │
|
||||
│ │ - apiLogs: API call logs for dashboard │ │
|
||||
│ │ Convex Tables │ │
|
||||
│ │ - memories: Content + extracted memories │ │
|
||||
│ │ - chatSessions: Conversation history │ │
|
||||
│ │ - analytics: Usage tracking │ │
|
||||
│ │ - apiLogs: API call logs for dashboard │ │
|
||||
│ │ - config: Component configuration │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
|
|
@ -346,62 +322,15 @@ await cleanCache(); // Removes all expired entries
|
|||
## Why This Architecture?
|
||||
|
||||
1. **Best of Both Worlds**: Supermemory's AI-powered memory + Convex's reactive sync
|
||||
2. **Dashboard Visibility**: See all API calls, cache hits, errors in Convex dashboard
|
||||
3. **Performance**: Smart caching reduces Supermemory API calls by ~80%
|
||||
4. **Real-time**: Search results update reactively across all connected clients
|
||||
5. **DX**: Install one package, 3 lines of config, start building
|
||||
2. **Dashboard Visibility**: See all API calls, memories, chat sessions, and analytics in Convex dashboard
|
||||
3. **Real-time**: Memories and search results update reactively across all connected clients
|
||||
4. **DX**: Install one package, 3 lines of config, start building
|
||||
|
||||
## Examples
|
||||
|
||||
Check out the `/example` directory for a complete Next.js chat app with:
|
||||
- Real-time semantic search
|
||||
- User profile extraction
|
||||
- Conversation memory
|
||||
- API analytics dashboard
|
||||
|
||||
## AI SDK Integration
|
||||
|
||||
Use Supermemory with Vercel AI SDK through the Convex backend:
|
||||
|
||||
### Middleware (Automatic Context Injection)
|
||||
|
||||
```typescript
|
||||
import { generateText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { withSupermemory } from "@supermemory/convex-component/ai-sdk";
|
||||
import { ConvexHttpClient } from "convex/browser";
|
||||
|
||||
const convex = new ConvexHttpClient(process.env.CONVEX_URL!);
|
||||
|
||||
const modelWithMemory = withSupermemory(
|
||||
openai("gpt-4"),
|
||||
convex,
|
||||
"user_123",
|
||||
{ mode: "full", addMemory: "always" }
|
||||
);
|
||||
|
||||
const result = await generateText({
|
||||
model: modelWithMemory,
|
||||
messages: [{ role: "user", content: "What do you know about me?" }]
|
||||
});
|
||||
```
|
||||
|
||||
### Tools (Agent-Based Memory)
|
||||
|
||||
```typescript
|
||||
import { streamText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { supermemoryConvexTools } from "@supermemory/convex-component/ai-sdk";
|
||||
import { ConvexHttpClient } from "convex/browser";
|
||||
|
||||
const convex = new ConvexHttpClient(process.env.CONVEX_URL!);
|
||||
|
||||
const result = await streamText({
|
||||
model: openai("gpt-4"),
|
||||
prompt: "Remember that I love TypeScript",
|
||||
tools: supermemoryConvexTools(convex, "user_123")
|
||||
});
|
||||
```
|
||||
Check out the `/example` directory for complete examples:
|
||||
- `basic-usage.ts` - Vanilla TypeScript client usage
|
||||
- `react-example.tsx` - React hooks with reactive UI
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
|
|
@ -1,255 +0,0 @@
|
|||
# 🚀 Supermemory Convex Component - Ready to Ship!
|
||||
|
||||
## ✅ What's Complete
|
||||
|
||||
### 📦 Package Structure
|
||||
- **Location**: `packages/tools/src/convex-component/`
|
||||
- **Package name**: `@supermemory/convex-component`
|
||||
- **Version**: `0.1.0`
|
||||
|
||||
### 🏗️ Components Built
|
||||
|
||||
#### 1. Convex Backend (`src/component/`)
|
||||
- ✅ `convex.config.ts` - Component definition
|
||||
- ✅ `schema.ts` - 5 Convex tables (searchCache, profileCache, documents, apiLogs, config)
|
||||
- ✅ `actions.ts` - 3 actions (add, search, profile) calling Supermemory API
|
||||
- ✅ `queries.ts` - 7 reactive queries for cache access
|
||||
- ✅ `mutations.ts` - 6 mutations for cache management
|
||||
- ✅ `lib.ts` - Internal utilities (API key retrieval)
|
||||
|
||||
#### 2. TypeScript Client SDK (`src/client/`)
|
||||
- ✅ `index.ts` - Full typed client with 11 methods
|
||||
- ✅ Type exports for all interfaces
|
||||
- ✅ Works with any JS/TS project
|
||||
|
||||
#### 3. React Hooks (`src/react/`)
|
||||
- ✅ `index.tsx` - 10 React hooks
|
||||
- `useAddMemory()` - Add memories
|
||||
- `useSupermemorySearch()` - Reactive search
|
||||
- `useSupermemoryProfile()` - User profiles
|
||||
- `useDocumentList()` - List documents
|
||||
- `useDocument()` - Get by custom ID
|
||||
- `useApiLogs()` - View API logs
|
||||
- `useApiStats()` - Dashboard stats
|
||||
- `useCleanCache()` - Cache management
|
||||
- `useUpdateDocumentStatus()` - Status updates
|
||||
- `useSetApiKey()` - API key config
|
||||
|
||||
#### 4. Documentation
|
||||
- ✅ `README.md` - Complete API reference (12KB)
|
||||
- ✅ `USAGE_GUIDE.md` - Step-by-step integration guide (13KB)
|
||||
- ✅ `example/` - Code examples
|
||||
- `basic-usage.ts` - Vanilla TypeScript
|
||||
- `react-example.tsx` - React components
|
||||
|
||||
#### 5. Test Application
|
||||
- ✅ `test-app/` - Full React + Vite app
|
||||
- Complete UI for testing all features
|
||||
- Add memories, search, view profiles, see stats
|
||||
- Ready to run with `npm run dev`
|
||||
|
||||
### 📊 Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ User's Next.js/React App │
|
||||
│ (useAddMemory, useSupermemorySearch) │
|
||||
└──────────────┬──────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Convex Backend Component │
|
||||
│ ┌─────────┐ ┌──────────┐ ┌────────┐ │
|
||||
│ │ Queries │ │Mutations │ │Actions │ │
|
||||
│ │(reactive)│ │(cache) │ │(API) │ │
|
||||
│ └────┬────┘ └────┬─────┘ └───┬────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────▼────────────▼────────────▼────┐ │
|
||||
│ │ Convex Tables (Smart Cache) │ │
|
||||
│ │ - searchCache, profileCache │ │
|
||||
│ │ - documents, apiLogs, config │ │
|
||||
│ └───────────────────────────────────┘ │
|
||||
└──────────────┬──────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Supermemory API (supermemory.ai) │
|
||||
│ - Semantic search │
|
||||
│ - Memory extraction │
|
||||
│ - User profiles │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 🎯 Key Features
|
||||
|
||||
1. **3-Line Setup** - Easiest Supermemory integration ever
|
||||
2. **Smart Caching** - Reduces API calls by ~80%
|
||||
3. **Reactive UI** - Auto-updates via Convex subscriptions
|
||||
4. **Dashboard Visibility** - See everything in Convex dashboard
|
||||
5. **Full TypeScript** - Completely typed, no any's
|
||||
6. **Zero Backend** - No server setup needed
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Status
|
||||
- ✅ TypeScript compilation passing
|
||||
- ✅ Dependencies installed (`supermemory@4.21.1`, `convex@1.35.1`)
|
||||
- ✅ Package structure validated
|
||||
- ✅ Test app created
|
||||
- ⚠️ **Needs live testing** - Run test-app with real Convex deployment
|
||||
|
||||
### To Test
|
||||
|
||||
```bash
|
||||
cd packages/tools/src/convex-component/test-app
|
||||
npm install
|
||||
npx convex dev # Follow prompts to create deployment
|
||||
npm run dev # Open http://localhost:5173
|
||||
```
|
||||
|
||||
**Test checklist**:
|
||||
- [ ] Add memories
|
||||
- [ ] Search and verify results
|
||||
- [ ] Check profile extraction
|
||||
- [ ] Verify stats update
|
||||
- [ ] Confirm caching works
|
||||
- [ ] Check Convex dashboard shows tables/logs
|
||||
|
||||
## 📦 Publishing to npm
|
||||
|
||||
### Prerequisites
|
||||
1. **npm account** with access to `@supermemory` org
|
||||
2. **Build the package**:
|
||||
```bash
|
||||
cd packages/tools/src/convex-component
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Publish Steps
|
||||
|
||||
```bash
|
||||
# 1. Build
|
||||
cd packages/tools/src/convex-component
|
||||
npm run build
|
||||
|
||||
# 2. Login to npm
|
||||
npm login
|
||||
|
||||
# 3. Publish
|
||||
npm publish --access public
|
||||
|
||||
# 4. Verify
|
||||
npm info @supermemory/convex-component
|
||||
```
|
||||
|
||||
### After Publishing
|
||||
|
||||
Update installation docs to use:
|
||||
```bash
|
||||
npm install @supermemory/convex-component
|
||||
```
|
||||
|
||||
## 📝 Next Steps
|
||||
|
||||
### 1. Integration Testing (PRIORITY)
|
||||
- [ ] Run test-app with real Convex deployment
|
||||
- [ ] Verify all hooks work end-to-end
|
||||
- [ ] Test error handling
|
||||
- [ ] Validate cache expiration
|
||||
|
||||
### 2. Documentation
|
||||
- [ ] Add to main Supermemory docs site
|
||||
- [ ] Create integration guide on docs.supermemory.ai
|
||||
- [ ] Add to integrations page
|
||||
|
||||
### 3. Marketing
|
||||
- [ ] Create demo video (3-5 min)
|
||||
- Show installation
|
||||
- Demonstrate adding memory
|
||||
- Show search with results
|
||||
- Highlight dashboard visibility
|
||||
- [ ] Twitter/X announcement
|
||||
- [ ] Discord announcement
|
||||
- [ ] Blog post on supermemory.ai
|
||||
|
||||
### 4. GitHub PR
|
||||
- [ ] Create PR to main branch
|
||||
- [ ] Update monorepo README
|
||||
- [ ] Add changelog entry
|
||||
- [ ] Link from docs
|
||||
|
||||
## 🎬 Demo Video Script
|
||||
|
||||
**Title**: "Add AI Memory to Convex in 3 Lines of Code"
|
||||
|
||||
**Script** (3 minutes):
|
||||
|
||||
1. **Intro** (15s)
|
||||
- "Want to add semantic memory to your Convex app?"
|
||||
- "Supermemory Convex Component makes it dead simple"
|
||||
|
||||
2. **Installation** (30s)
|
||||
- Show: `npm install @supermemory/convex-component`
|
||||
- Add to convex.config.ts (3 lines)
|
||||
- Set API key
|
||||
|
||||
3. **Add Memory** (45s)
|
||||
- Use `useAddMemory()` hook
|
||||
- Add conversation to memory
|
||||
- Show it appear in Convex dashboard
|
||||
|
||||
4. **Search** (60s)
|
||||
- Use `useSupermemorySearch()` hook
|
||||
- Search for "user preferences"
|
||||
- Show results with similarity scores
|
||||
- Highlight reactive updates
|
||||
|
||||
5. **Dashboard** (30s)
|
||||
- Open Convex dashboard
|
||||
- Show tables: searchCache, documents, apiLogs
|
||||
- Show API statistics
|
||||
|
||||
6. **Outro** (15s)
|
||||
- "That's it! Supermemory + Convex = AI memory made easy"
|
||||
- "Link in description"
|
||||
|
||||
## 💡 Marketing Angles
|
||||
|
||||
### For Convex Users
|
||||
> "Add state-of-the-art semantic memory to your Convex app. Zero backend setup. See every API call in your dashboard. Ships in 3 lines of code."
|
||||
|
||||
### For Supermemory Users
|
||||
> "Use Supermemory with Convex's reactive database. Get real-time UI updates, smart caching, and amazing dashboard visibility. Easiest integration ever."
|
||||
|
||||
### For AI App Builders
|
||||
> "Build AI apps with long-term memory. Convex handles sync, Supermemory handles intelligence. Just plug and play."
|
||||
|
||||
## 📊 Success Metrics
|
||||
|
||||
**Week 1 Targets**:
|
||||
- 50+ npm downloads
|
||||
- 5+ GitHub stars
|
||||
- 3+ people testing in Discord
|
||||
|
||||
**Month 1 Targets**:
|
||||
- 500+ npm downloads
|
||||
- 25+ GitHub stars
|
||||
- 10+ production deployments
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- **Package**: `packages/tools/src/convex-component/`
|
||||
- **Test App**: `packages/tools/src/convex-component/test-app/`
|
||||
- **Supermemory Docs**: https://supermemory.ai/docs
|
||||
- **Convex Docs**: https://docs.convex.dev/components
|
||||
- **npm**: https://www.npmjs.com/package/@supermemory/convex-component (after publish)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Ready to Ship!
|
||||
|
||||
The Supermemory Convex Component is **production-ready**. All code is complete, tested for types, and documented.
|
||||
|
||||
**Next action**: Run the test-app to validate everything works with a real Convex deployment, then publish to npm!
|
||||
|
||||
**LFG! 🚀**
|
||||
|
|
@ -1,587 +0,0 @@
|
|||
# Supermemory Convex Component - Complete Usage Guide
|
||||
|
||||
This guide walks you through everything you need to know to use the Supermemory Convex Component in your application.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Installation](#installation)
|
||||
2. [Setup](#setup)
|
||||
3. [Basic Usage](#basic-usage)
|
||||
4. [React Hooks API](#react-hooks-api)
|
||||
5. [Client SDK API](#client-sdk-api)
|
||||
6. [Advanced Patterns](#advanced-patterns)
|
||||
7. [Convex Dashboard](#convex-dashboard)
|
||||
8. [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @supermemory/convex-component convex
|
||||
# or
|
||||
bun add @supermemory/convex-component convex
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
### Step 1: Initialize Convex (if you haven't already)
|
||||
|
||||
```bash
|
||||
npx convex dev
|
||||
```
|
||||
|
||||
### Step 2: Configure the Component
|
||||
|
||||
Create or update `convex/convex.config.ts`:
|
||||
|
||||
```typescript
|
||||
import { defineApp } from "convex/server";
|
||||
import supermemory from "@supermemory/convex-component/convex.config";
|
||||
|
||||
const app = defineApp();
|
||||
app.use(supermemory, { name: "supermemory" });
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
### Step 3: Set Your Supermemory API Key
|
||||
|
||||
You have two options:
|
||||
|
||||
**Option A: Environment Variable (Recommended)**
|
||||
|
||||
```bash
|
||||
# Add to .env.local
|
||||
SUPERMEMORY_API_KEY=your-api-key-here
|
||||
```
|
||||
|
||||
**Option B: Store in Convex**
|
||||
|
||||
```bash
|
||||
npx convex run supermemory:mutations.setApiKey '{"apiKey": "your-api-key-here"}'
|
||||
```
|
||||
|
||||
Get your API key from [supermemory.ai/dashboard](https://supermemory.ai/dashboard).
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Adding Memories
|
||||
|
||||
```typescript
|
||||
import { createSupermemoryClient } from "@supermemory/convex-component";
|
||||
import { ConvexHttpClient } from "convex/browser";
|
||||
|
||||
const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
|
||||
const supermemory = createSupermemoryClient(convex);
|
||||
|
||||
// Add a simple memory
|
||||
await supermemory.add({
|
||||
content: "User prefers dark mode and uses TypeScript",
|
||||
containerTag: "user_alice",
|
||||
});
|
||||
|
||||
// Add a conversation
|
||||
await supermemory.add({
|
||||
content: `User: What's your favorite framework?
|
||||
Assistant: I love Next.js for full-stack development.
|
||||
User: Me too! The app router is amazing.`,
|
||||
containerTag: "user_alice",
|
||||
customId: "conversation_001", // For updates
|
||||
});
|
||||
```
|
||||
|
||||
### Searching Memories
|
||||
|
||||
```typescript
|
||||
const results = await supermemory.search({
|
||||
q: "framework preferences",
|
||||
containerTag: "user_alice",
|
||||
searchMode: "hybrid", // or "memories"
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
console.log(results.results);
|
||||
// [
|
||||
// {
|
||||
// id: "mem_xyz",
|
||||
// memory: "User loves Next.js for full-stack development",
|
||||
// similarity: 0.92,
|
||||
// ...
|
||||
// }
|
||||
// ]
|
||||
```
|
||||
|
||||
### Getting User Profiles
|
||||
|
||||
```typescript
|
||||
const profile = await supermemory.profile({
|
||||
containerTag: "user_alice",
|
||||
q: "preferences", // Optional: context for search
|
||||
});
|
||||
|
||||
console.log("Static facts:", profile.profile.static);
|
||||
// ["User prefers dark mode", "User uses TypeScript"]
|
||||
|
||||
console.log("Dynamic context:", profile.profile.dynamic);
|
||||
// ["Recently discussed Next.js framework"]
|
||||
```
|
||||
|
||||
## React Hooks API
|
||||
|
||||
### useAddMemory
|
||||
|
||||
Add memories with a simple hook:
|
||||
|
||||
```tsx
|
||||
import { useAddMemory } from "@supermemory/convex-component/react";
|
||||
|
||||
function ChatInput({ userId }) {
|
||||
const addMemory = useAddMemory();
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const handleSend = async () => {
|
||||
await addMemory({
|
||||
content: message,
|
||||
containerTag: userId,
|
||||
metadata: { type: "chat" },
|
||||
});
|
||||
setMessage("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input value={message} onChange={(e) => setMessage(e.target.value)} />
|
||||
<button onClick={handleSend}>Send</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### useSupermemorySearch
|
||||
|
||||
Reactive search with automatic UI updates:
|
||||
|
||||
```tsx
|
||||
import { useSupermemorySearch } from "@supermemory/convex-component/react";
|
||||
|
||||
function SearchResults({ userId }) {
|
||||
const [query, setQuery] = useState("");
|
||||
const { results, isLoading, error, search } = useSupermemorySearch(null);
|
||||
|
||||
const handleSearch = () => {
|
||||
search({
|
||||
q: query,
|
||||
containerTag: userId,
|
||||
searchMode: "hybrid",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search memories..."
|
||||
/>
|
||||
<button onClick={handleSearch} disabled={isLoading}>
|
||||
{isLoading ? "Searching..." : "Search"}
|
||||
</button>
|
||||
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
|
||||
{results && (
|
||||
<div>
|
||||
<p>
|
||||
Found {results.total} results in {results.timing}ms
|
||||
{results.cached && " (cached)"}
|
||||
</p>
|
||||
{results.results.map((result) => (
|
||||
<div key={result.id}>
|
||||
<p>{result.memory || result.chunk}</p>
|
||||
<small>Similarity: {(result.similarity * 100).toFixed(1)}%</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### useSupermemoryProfile
|
||||
|
||||
Get user profile with reactive updates:
|
||||
|
||||
```tsx
|
||||
import { useSupermemoryProfile } from "@supermemory/convex-component/react";
|
||||
|
||||
function UserContextPanel({ userId }) {
|
||||
const { profile, isLoading, refresh } = useSupermemoryProfile({
|
||||
containerTag: userId,
|
||||
});
|
||||
|
||||
if (isLoading) return <div>Loading profile...</div>;
|
||||
if (!profile) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>User Context</h2>
|
||||
<button onClick={() => refresh()}>Refresh</button>
|
||||
|
||||
<section>
|
||||
<h3>Static Facts (Always True)</h3>
|
||||
<ul>
|
||||
{profile.profile.static.map((fact, i) => (
|
||||
<li key={i}>{fact}</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Dynamic Context (Recent)</h3>
|
||||
<ul>
|
||||
{profile.profile.dynamic.map((fact, i) => (
|
||||
<li key={i}>{fact}</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### useDocumentList
|
||||
|
||||
List documents reactively:
|
||||
|
||||
```tsx
|
||||
import { useDocumentList } from "@supermemory/convex-component/react";
|
||||
|
||||
function DocumentHistory({ userId }) {
|
||||
const documents = useDocumentList({ containerTag: userId, limit: 20 });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Memory History</h2>
|
||||
{documents?.map((doc) => (
|
||||
<div key={doc._id}>
|
||||
<p>{doc.contentPreview}</p>
|
||||
<small>
|
||||
{doc.status} • {new Date(doc.addedAt).toLocaleString()}
|
||||
</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### useApiStats
|
||||
|
||||
Dashboard statistics:
|
||||
|
||||
```tsx
|
||||
import { useApiStats } from "@supermemory/convex-component/react";
|
||||
|
||||
function ApiDashboard({ userId }) {
|
||||
const stats = useApiStats({ containerTag: userId });
|
||||
|
||||
if (!stats) return <div>Loading...</div>;
|
||||
|
||||
const successRate = (stats.successfulCalls / stats.totalCalls) * 100;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>API Statistics</h2>
|
||||
<div className="stats-grid">
|
||||
<div>
|
||||
<strong>{stats.totalCalls}</strong>
|
||||
<span>Total Calls</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{successRate.toFixed(1)}%</strong>
|
||||
<span>Success Rate</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{stats.averageResponseTime.toFixed(0)}ms</strong>
|
||||
<span>Avg Response</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Calls by Endpoint</h3>
|
||||
{Object.entries(stats.callsByEndpoint).map(([endpoint, count]) => (
|
||||
<div key={endpoint}>
|
||||
{endpoint}: {count}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Client SDK API
|
||||
|
||||
### Complete API Reference
|
||||
|
||||
```typescript
|
||||
const supermemory = createSupermemoryClient(convex);
|
||||
|
||||
// Add content
|
||||
await supermemory.add({
|
||||
content: string,
|
||||
containerTag: string,
|
||||
customId?: string,
|
||||
metadata?: Record<string, any>
|
||||
});
|
||||
|
||||
// Search
|
||||
await supermemory.search({
|
||||
q: string,
|
||||
containerTag: string,
|
||||
searchMode?: "hybrid" | "memories",
|
||||
limit?: number,
|
||||
threshold?: number,
|
||||
rerank?: boolean,
|
||||
filters?: Record<string, any>
|
||||
});
|
||||
|
||||
// Get profile
|
||||
await supermemory.profile({
|
||||
containerTag: string,
|
||||
q?: string
|
||||
});
|
||||
|
||||
// List documents
|
||||
await supermemory.listDocuments({
|
||||
containerTag?: string,
|
||||
limit?: number
|
||||
});
|
||||
|
||||
// Get document by custom ID
|
||||
await supermemory.getDocumentByCustomId(customId: string);
|
||||
|
||||
// Get API logs
|
||||
await supermemory.getApiLogs({
|
||||
endpoint?: string,
|
||||
containerTag?: string,
|
||||
limit?: number
|
||||
});
|
||||
|
||||
// Get stats
|
||||
await supermemory.getApiStats({
|
||||
containerTag?: string
|
||||
});
|
||||
|
||||
// Search cached documents
|
||||
await supermemory.searchCached({
|
||||
searchText: string,
|
||||
containerTag?: string,
|
||||
limit?: number
|
||||
});
|
||||
|
||||
// Clean expired cache
|
||||
await supermemory.cleanCache();
|
||||
|
||||
// Update document status
|
||||
await supermemory.updateDocumentStatus({
|
||||
documentId: string,
|
||||
status: "queued" | "processed" | "failed"
|
||||
});
|
||||
|
||||
// Set API key
|
||||
await supermemory.setApiKey(apiKey: string);
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Multi-Tenant Applications
|
||||
|
||||
Organize memories by user, session, or organization:
|
||||
|
||||
```typescript
|
||||
// Per user
|
||||
await supermemory.add({
|
||||
content: "User data",
|
||||
containerTag: `user_${userId}`,
|
||||
});
|
||||
|
||||
// Per organization
|
||||
await supermemory.add({
|
||||
content: "Company knowledge",
|
||||
containerTag: `org_${orgId}`,
|
||||
});
|
||||
|
||||
// Per session
|
||||
await supermemory.add({
|
||||
content: "Chat session",
|
||||
containerTag: `session_${sessionId}`,
|
||||
});
|
||||
```
|
||||
|
||||
### Metadata Filtering
|
||||
|
||||
Add rich metadata and filter searches:
|
||||
|
||||
```typescript
|
||||
// Add with metadata
|
||||
await supermemory.add({
|
||||
content: "Product design doc for feature X",
|
||||
containerTag: "user_123",
|
||||
metadata: {
|
||||
type: "document",
|
||||
category: "design",
|
||||
priority: "high",
|
||||
team: "product",
|
||||
tags: ["feature-x", "q1-2024"],
|
||||
},
|
||||
});
|
||||
|
||||
// Search with filters
|
||||
const results = await supermemory.search({
|
||||
q: "design documents",
|
||||
containerTag: "user_123",
|
||||
filters: {
|
||||
AND: [
|
||||
{ key: "type", value: "document" },
|
||||
{ key: "priority", value: "high" },
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Updating Content with Custom IDs
|
||||
|
||||
Use custom IDs to update existing memories:
|
||||
|
||||
```typescript
|
||||
// Initial message
|
||||
await supermemory.add({
|
||||
content: "User: Hello\nAssistant: Hi!",
|
||||
containerTag: "user_123",
|
||||
customId: "conversation_abc",
|
||||
});
|
||||
|
||||
// Add new messages (Supermemory handles the diff)
|
||||
await supermemory.add({
|
||||
content: "User: How are you?\nAssistant: I'm great!",
|
||||
containerTag: "user_123",
|
||||
customId: "conversation_abc", // Same ID = update
|
||||
});
|
||||
```
|
||||
|
||||
### Building a Chatbot with Memory
|
||||
|
||||
```tsx
|
||||
import { useAddMemory, useSupermemoryProfile } from "@supermemory/convex-component/react";
|
||||
|
||||
function AIChatbot({ userId }) {
|
||||
const addMemory = useAddMemory();
|
||||
const { profile, refresh } = useSupermemoryProfile({ containerTag: userId });
|
||||
const [conversation, setConversation] = useState([]);
|
||||
|
||||
const sendMessage = async (userMessage: string) => {
|
||||
// Get user context
|
||||
await refresh();
|
||||
|
||||
// Build context-aware prompt
|
||||
const context = `
|
||||
Static profile: ${profile?.profile.static.join(", ")}
|
||||
Dynamic context: ${profile?.profile.dynamic.join(", ")}
|
||||
`;
|
||||
|
||||
// Call your LLM with context
|
||||
const aiResponse = await callLLM({
|
||||
systemPrompt: `User context:\n${context}`,
|
||||
messages: conversation,
|
||||
userMessage,
|
||||
});
|
||||
|
||||
// Store conversation in memory
|
||||
await addMemory({
|
||||
content: `User: ${userMessage}\nAssistant: ${aiResponse}`,
|
||||
containerTag: userId,
|
||||
customId: `conversation_${Date.now()}`,
|
||||
});
|
||||
|
||||
// Update UI
|
||||
setConversation([...conversation, { user: userMessage, ai: aiResponse }]);
|
||||
};
|
||||
|
||||
return <ChatUI messages={conversation} onSend={sendMessage} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Convex Dashboard
|
||||
|
||||
The Supermemory component gives you full visibility in your Convex dashboard:
|
||||
|
||||
### Tables You'll See
|
||||
|
||||
1. **searchCache**: Cached search results with TTL
|
||||
2. **profileCache**: Cached user profiles
|
||||
3. **documents**: All memories/documents added
|
||||
4. **apiLogs**: Every API call made to Supermemory
|
||||
5. **config**: Configuration (API key, etc.)
|
||||
|
||||
### Viewing API Logs
|
||||
|
||||
```typescript
|
||||
// In Convex dashboard, run:
|
||||
const logs = await ctx.db.query("apiLogs").order("desc").take(100);
|
||||
|
||||
// Or use the client:
|
||||
const logs = await supermemory.getApiLogs({ limit: 100 });
|
||||
```
|
||||
|
||||
### Cache Management
|
||||
|
||||
Caches automatically expire:
|
||||
- Search cache: 5 minutes
|
||||
- Profile cache: 2 minutes
|
||||
|
||||
Clean manually:
|
||||
|
||||
```typescript
|
||||
await supermemory.cleanCache();
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Cannot find module '@supermemory/convex-component/convex.config'"
|
||||
|
||||
Make sure you've installed the package and it's in your `package.json`.
|
||||
|
||||
### "Supermemory API key not configured"
|
||||
|
||||
Set the `SUPERMEMORY_API_KEY` environment variable or use `setApiKey()`.
|
||||
|
||||
### Search results are empty
|
||||
|
||||
Check that:
|
||||
1. You've added content with `add()`
|
||||
2. The `containerTag` matches
|
||||
3. Wait a few seconds for Supermemory to process the content
|
||||
|
||||
### TypeScript errors in hooks
|
||||
|
||||
Make sure you have the correct React version (18+ or 19+) and convex version (1.35+).
|
||||
|
||||
### Cache not updating
|
||||
|
||||
Caches expire automatically. To force refresh:
|
||||
```typescript
|
||||
// Clean all caches
|
||||
await supermemory.cleanCache();
|
||||
|
||||
// Or refetch profile
|
||||
await refresh(); // in useSupermemoryProfile
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Check out the `/example` directory for complete examples
|
||||
- Read the main [README.md](./README.md) for API reference
|
||||
- Join our [Discord](https://discord.gg/supermemory) for support
|
||||
|
||||
---
|
||||
|
||||
Built with ❤️ by the Supermemory team
|
||||
|
|
@ -62,15 +62,15 @@ export interface ProfileResponse {
|
|||
}
|
||||
}
|
||||
|
||||
export interface Document {
|
||||
export interface Memory {
|
||||
_id: string
|
||||
documentId: string
|
||||
customId?: string
|
||||
content: string
|
||||
containerTag: string
|
||||
contentPreview: string
|
||||
source: "chat" | "document" | "manual"
|
||||
supermemoryId?: string
|
||||
extractedMemories?: string[]
|
||||
createdAt: number
|
||||
metadata?: Record<string, any>
|
||||
status: "queued" | "processed" | "failed"
|
||||
addedAt: number
|
||||
}
|
||||
|
||||
export interface ApiLog {
|
||||
|
|
@ -123,7 +123,6 @@ export function createSupermemoryClient(
|
|||
client: ConvexClient,
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
// Helper to construct function references
|
||||
const action = (name: string): FunctionReference<"action"> => {
|
||||
return `${componentPath}:actions.${name}` as any
|
||||
}
|
||||
|
|
@ -132,21 +131,17 @@ export function createSupermemoryClient(
|
|||
return `${componentPath}:queries.${name}` as any
|
||||
}
|
||||
|
||||
const mutation = (name: string): FunctionReference<"mutation"> => {
|
||||
return `${componentPath}:mutations.${name}` as any
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Add content to Supermemory
|
||||
* Stores text, conversations, files, or URLs for semantic search
|
||||
* Stores text and extracts memories from it
|
||||
*/
|
||||
add: async (args: AddMemoryArgs) => {
|
||||
return await client.action(action("add"), args)
|
||||
},
|
||||
|
||||
/**
|
||||
* Search memories and documents
|
||||
* Search memories
|
||||
* Performs semantic search across all content
|
||||
*/
|
||||
search: async (args: SearchMemoriesArgs): Promise<SearchResponse> => {
|
||||
|
|
@ -155,35 +150,26 @@ export function createSupermemoryClient(
|
|||
|
||||
/**
|
||||
* Get user profile with context
|
||||
* Retrieves static/dynamic facts about a user plus relevant memories
|
||||
* Retrieves static/dynamic facts about a user
|
||||
*/
|
||||
profile: async (args: ProfileArgs): Promise<ProfileResponse> => {
|
||||
return await client.action(action("profile"), args)
|
||||
},
|
||||
|
||||
/**
|
||||
* List documents added to Supermemory
|
||||
* Query documents with optional filtering
|
||||
* List memories for a user
|
||||
* Includes content and extracted memories
|
||||
*/
|
||||
listDocuments: async (args?: {
|
||||
containerTag?: string
|
||||
listMemories: async (args: {
|
||||
containerTag: string
|
||||
source?: "chat" | "document" | "manual"
|
||||
limit?: number
|
||||
}): Promise<Document[]> => {
|
||||
return await client.query(query("listDocuments"), args || {})
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a document by custom ID
|
||||
*/
|
||||
getDocumentByCustomId: async (
|
||||
customId: string,
|
||||
): Promise<Document | null> => {
|
||||
return await client.query(query("getDocumentByCustomId"), { customId })
|
||||
}): Promise<Memory[]> => {
|
||||
return await client.query(query("listMemories"), args)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get API call logs
|
||||
* View recent Supermemory API calls for debugging
|
||||
*/
|
||||
getApiLogs: async (args?: {
|
||||
endpoint?: string
|
||||
|
|
@ -195,7 +181,6 @@ export function createSupermemoryClient(
|
|||
|
||||
/**
|
||||
* Get API statistics
|
||||
* Aggregate stats for dashboard visibility
|
||||
*/
|
||||
getApiStats: async (args?: {
|
||||
containerTag?: string
|
||||
|
|
@ -203,16 +188,6 @@ export function createSupermemoryClient(
|
|||
}): Promise<ApiStats> => {
|
||||
return await client.query(query("getApiStats"), args || {})
|
||||
},
|
||||
|
||||
/**
|
||||
* Update document status
|
||||
*/
|
||||
updateDocumentStatus: async (args: {
|
||||
documentId: string
|
||||
status: "queued" | "processed" | "failed"
|
||||
}) => {
|
||||
return await client.mutation(mutation("updateDocumentStatus"), args)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,9 @@ import { v } from "convex/values"
|
|||
import Supermemory from "supermemory"
|
||||
import { internal } from "./_generated/api"
|
||||
|
||||
/**
|
||||
* Supermemory Actions
|
||||
*
|
||||
* Actions handle non-deterministic operations like calling external APIs.
|
||||
* These functions call the Supermemory REST API and cache results in Convex.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add content to Supermemory
|
||||
* Stores text, conversations, files, or URLs in Supermemory for semantic search
|
||||
* Stores text and then searches for the extracted memories from it
|
||||
*/
|
||||
export const add = action({
|
||||
args: {
|
||||
|
|
@ -25,11 +18,10 @@ export const add = action({
|
|||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
// Get API key from config
|
||||
const apiKey = await ctx.runQuery(internal.lib.getApiKey)
|
||||
const client = new Supermemory({ apiKey })
|
||||
|
||||
// Call Supermemory API
|
||||
// Add content to Supermemory
|
||||
const result = await client.add({
|
||||
content: args.content,
|
||||
containerTag: args.containerTag,
|
||||
|
|
@ -39,22 +31,31 @@ export const add = action({
|
|||
|
||||
const responseTime = Date.now() - startTime
|
||||
|
||||
// Store document metadata in Convex
|
||||
await ctx.runMutation(internal.mutations.storeDocument, {
|
||||
documentId: result.id,
|
||||
customId: args.customId,
|
||||
containerTag: args.containerTag,
|
||||
contentPreview: args.content.substring(0, 200),
|
||||
metadata: args.metadata,
|
||||
status: result.status === "queued" ? "queued" : "processed",
|
||||
})
|
||||
// Search for extracted memories from this content
|
||||
let extractedMemories: string[] = []
|
||||
try {
|
||||
// Small delay to let Supermemory process
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
const searchResult = await client.search.memories({
|
||||
q: args.content,
|
||||
containerTag: args.containerTag,
|
||||
searchMode: "memories",
|
||||
limit: 10,
|
||||
})
|
||||
extractedMemories = (searchResult.results || [])
|
||||
.map((r: any) => r.memory || r.chunk || "")
|
||||
.filter((m: string) => m.length > 0)
|
||||
} catch {
|
||||
// Extraction might not be ready yet, that's ok
|
||||
}
|
||||
|
||||
// Store memory in dashboard table
|
||||
// Store memory with extracted memories
|
||||
await ctx.runMutation(internal.mutations.storeMemory, {
|
||||
content: args.content,
|
||||
containerTag: args.containerTag,
|
||||
source: "manual",
|
||||
supermemoryId: result.id,
|
||||
extractedMemories,
|
||||
metadata: args.metadata,
|
||||
})
|
||||
|
||||
|
|
@ -64,7 +65,7 @@ export const add = action({
|
|||
incrementMemories: 1,
|
||||
})
|
||||
|
||||
// Log API call (truncate content to avoid 1MiB field limit)
|
||||
// Log API call
|
||||
const logBody = { ...args, content: args.content.substring(0, 500) }
|
||||
await ctx.runMutation(internal.mutations.logApiCall, {
|
||||
endpoint: "add",
|
||||
|
|
@ -78,7 +79,6 @@ export const add = action({
|
|||
} catch (error) {
|
||||
const responseTime = Date.now() - startTime
|
||||
|
||||
// Log error (wrapped to prevent logging failure from masking the real error)
|
||||
try {
|
||||
const logBody = { ...args, content: args.content.substring(0, 500) }
|
||||
await ctx.runMutation(internal.mutations.logApiCall, {
|
||||
|
|
@ -101,7 +101,6 @@ export const add = action({
|
|||
|
||||
/**
|
||||
* Search memories and documents
|
||||
* Performs semantic search across all content in Supermemory
|
||||
*/
|
||||
export const search = action({
|
||||
args: {
|
||||
|
|
@ -123,11 +122,9 @@ export const search = action({
|
|||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
// Get API key from config
|
||||
const apiKey = await ctx.runQuery(internal.lib.getApiKey)
|
||||
const client = new Supermemory({ apiKey })
|
||||
|
||||
// Call Supermemory API
|
||||
const result = await client.search.memories({
|
||||
q: args.q,
|
||||
containerTag: args.containerTag,
|
||||
|
|
@ -147,7 +144,7 @@ export const search = action({
|
|||
responseTime,
|
||||
})
|
||||
|
||||
// Log API call (exclude large filters from log)
|
||||
// Log API call
|
||||
const logBody = { q: args.q, containerTag: args.containerTag, searchMode: args.searchMode, limit: args.limit }
|
||||
await ctx.runMutation(internal.mutations.logApiCall, {
|
||||
endpoint: "search",
|
||||
|
|
@ -183,7 +180,6 @@ export const search = action({
|
|||
|
||||
/**
|
||||
* Get user profile with context
|
||||
* Retrieves static/dynamic facts about a user plus relevant memories
|
||||
*/
|
||||
export const profile = action({
|
||||
args: {
|
||||
|
|
@ -194,11 +190,9 @@ export const profile = action({
|
|||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
// Get API key from config
|
||||
const apiKey = await ctx.runQuery(internal.lib.getApiKey)
|
||||
const client = new Supermemory({ apiKey })
|
||||
|
||||
// Call Supermemory API
|
||||
const result = await client.profile({
|
||||
containerTag: args.containerTag,
|
||||
q: args.q,
|
||||
|
|
@ -206,7 +200,6 @@ export const profile = action({
|
|||
|
||||
const responseTime = Date.now() - startTime
|
||||
|
||||
// Log API call
|
||||
await ctx.runMutation(internal.mutations.logApiCall, {
|
||||
endpoint: "profile",
|
||||
containerTag: args.containerTag,
|
||||
|
|
|
|||
|
|
@ -9,15 +9,10 @@ export { add, search, profile } from "./actions"
|
|||
export {
|
||||
getApiStats,
|
||||
getApiLogs,
|
||||
listDocuments,
|
||||
getDocumentByCustomId,
|
||||
listMemories,
|
||||
getChatSessions,
|
||||
getChatSession,
|
||||
getAnalytics,
|
||||
getDashboardOverview,
|
||||
} from "./queries"
|
||||
export {
|
||||
updateDocumentStatus,
|
||||
trackChatMessage,
|
||||
} from "./mutations"
|
||||
export { trackChatMessage } from "./mutations"
|
||||
|
|
|
|||
|
|
@ -1,73 +1,8 @@
|
|||
import { internalMutation, mutation } from "./_generated/server"
|
||||
import { v } from "convex/values"
|
||||
|
||||
/**
|
||||
* Supermemory Mutations
|
||||
*
|
||||
* Mutations handle all database writes in transactions.
|
||||
* These functions update the Convex cache with Supermemory data.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Store document metadata
|
||||
* Tracks documents/memories added to Supermemory
|
||||
*/
|
||||
export const storeDocument = internalMutation({
|
||||
args: {
|
||||
documentId: v.string(),
|
||||
customId: v.optional(v.string()),
|
||||
containerTag: v.string(),
|
||||
contentPreview: v.string(),
|
||||
metadata: v.optional(v.any()),
|
||||
status: v.union(
|
||||
v.literal("queued"),
|
||||
v.literal("processed"),
|
||||
v.literal("failed"),
|
||||
),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
// Check if document with this customId or documentId exists
|
||||
const existingByCustomId = args.customId
|
||||
? await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_custom_id", (q) => q.eq("customId", args.customId))
|
||||
.first()
|
||||
: null
|
||||
|
||||
const existingByDocId = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("documentId", args.documentId))
|
||||
.first()
|
||||
|
||||
const existing = existingByCustomId || existingByDocId
|
||||
|
||||
if (existing) {
|
||||
// Update existing document
|
||||
await ctx.db.patch(existing._id, {
|
||||
documentId: args.documentId,
|
||||
customId: args.customId,
|
||||
contentPreview: args.contentPreview,
|
||||
metadata: args.metadata,
|
||||
status: args.status,
|
||||
})
|
||||
} else {
|
||||
// Create new document entry
|
||||
await ctx.db.insert("documents", {
|
||||
documentId: args.documentId,
|
||||
customId: args.customId,
|
||||
containerTag: args.containerTag,
|
||||
contentPreview: args.contentPreview,
|
||||
metadata: args.metadata,
|
||||
status: args.status,
|
||||
addedAt: Date.now(),
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Log API call
|
||||
* Records API calls for debugging and analytics
|
||||
*/
|
||||
export const logApiCall = internalMutation({
|
||||
args: {
|
||||
|
|
@ -95,39 +30,8 @@ export const logApiCall = internalMutation({
|
|||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Update document status
|
||||
* Updates the processing status of a document
|
||||
*/
|
||||
export const updateDocumentStatus = mutation({
|
||||
args: {
|
||||
documentId: v.string(),
|
||||
status: v.union(
|
||||
v.literal("queued"),
|
||||
v.literal("processed"),
|
||||
v.literal("failed"),
|
||||
),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const doc = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_document_id", (q) => q.eq("documentId", args.documentId))
|
||||
.first()
|
||||
|
||||
if (!doc) {
|
||||
throw new Error(`Document ${args.documentId} not found`)
|
||||
}
|
||||
|
||||
await ctx.db.patch(doc._id, { status: args.status })
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Initialize or update API key
|
||||
* Stores the Supermemory API key in Convex
|
||||
*
|
||||
* SECURITY NOTE: This is an internal mutation. Use `npx convex env set SUPERMEMORY_API_KEY`
|
||||
* for production, or call this from a server-side admin endpoint with proper auth checks.
|
||||
*/
|
||||
export const setApiKey = internalMutation({
|
||||
args: {
|
||||
|
|
@ -151,8 +55,7 @@ export const setApiKey = internalMutation({
|
|||
})
|
||||
|
||||
/**
|
||||
* Store a memory
|
||||
* Tracks individual memories in the dashboard
|
||||
* Store a memory with extracted memories
|
||||
*/
|
||||
export const storeMemory = internalMutation({
|
||||
args: {
|
||||
|
|
@ -164,6 +67,7 @@ export const storeMemory = internalMutation({
|
|||
v.literal("manual"),
|
||||
),
|
||||
supermemoryId: v.optional(v.string()),
|
||||
extractedMemories: v.optional(v.array(v.string())),
|
||||
metadata: v.optional(v.any()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
|
|
@ -172,15 +76,30 @@ export const storeMemory = internalMutation({
|
|||
containerTag: args.containerTag,
|
||||
source: args.source,
|
||||
supermemoryId: args.supermemoryId,
|
||||
extractedMemories: args.extractedMemories,
|
||||
createdAt: Date.now(),
|
||||
metadata: args.metadata,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Update extracted memories for an existing memory
|
||||
*/
|
||||
export const updateExtractedMemories = internalMutation({
|
||||
args: {
|
||||
memoryId: v.id("memories"),
|
||||
extractedMemories: v.array(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.memoryId, {
|
||||
extractedMemories: args.extractedMemories,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Create or update chat session
|
||||
* Tracks conversation history with memory usage
|
||||
*/
|
||||
export const updateChatSession = internalMutation({
|
||||
args: {
|
||||
|
|
@ -196,7 +115,6 @@ export const updateChatSession = internalMutation({
|
|||
handler: async (ctx, args) => {
|
||||
const MAX_MESSAGES = 500
|
||||
if (args.sessionId) {
|
||||
// Update existing session
|
||||
const session = await ctx.db.get(args.sessionId)
|
||||
if (session) {
|
||||
const messages = [...session.messages, args.newMessage].slice(
|
||||
|
|
@ -216,7 +134,6 @@ export const updateChatSession = internalMutation({
|
|||
}
|
||||
}
|
||||
|
||||
// Create new session
|
||||
const sessionId = await ctx.db.insert("chatSessions", {
|
||||
containerTag: args.containerTag,
|
||||
messages: [args.newMessage],
|
||||
|
|
@ -230,7 +147,6 @@ export const updateChatSession = internalMutation({
|
|||
|
||||
/**
|
||||
* Public wrapper for updateChatSession
|
||||
* Allows clients to track chat sessions
|
||||
*/
|
||||
export const trackChatMessage = mutation({
|
||||
args: {
|
||||
|
|
@ -246,7 +162,6 @@ export const trackChatMessage = mutation({
|
|||
handler: async (ctx, args) => {
|
||||
const MAX_MESSAGES = 500
|
||||
if (args.sessionId) {
|
||||
// Update existing session
|
||||
const session = await ctx.db.get(args.sessionId)
|
||||
if (session) {
|
||||
const messages = [...session.messages, args.newMessage].slice(
|
||||
|
|
@ -266,7 +181,6 @@ export const trackChatMessage = mutation({
|
|||
}
|
||||
}
|
||||
|
||||
// Create new session
|
||||
const sessionId = await ctx.db.insert("chatSessions", {
|
||||
containerTag: args.containerTag,
|
||||
messages: [args.newMessage],
|
||||
|
|
@ -280,7 +194,6 @@ export const trackChatMessage = mutation({
|
|||
|
||||
/**
|
||||
* Update analytics
|
||||
* Updates usage statistics for a user
|
||||
*/
|
||||
export const updateAnalytics = internalMutation({
|
||||
args: {
|
||||
|
|
@ -297,7 +210,6 @@ export const updateAnalytics = internalMutation({
|
|||
.first()
|
||||
|
||||
if (existing) {
|
||||
// Update existing analytics
|
||||
const updates: any = {
|
||||
lastActive: Date.now(),
|
||||
}
|
||||
|
|
@ -312,7 +224,6 @@ export const updateAnalytics = internalMutation({
|
|||
updates.totalSearches = existing.totalSearches + args.incrementSearches
|
||||
}
|
||||
if (args.incrementSearches && args.responseTime) {
|
||||
// Only update average when we're also incrementing searches
|
||||
const totalTime = existing.avgResponseTime * existing.totalSearches
|
||||
const newTotal = totalTime + args.responseTime
|
||||
const newSearchCount = existing.totalSearches + args.incrementSearches
|
||||
|
|
@ -321,7 +232,6 @@ export const updateAnalytics = internalMutation({
|
|||
|
||||
await ctx.db.patch(existing._id, updates)
|
||||
} else {
|
||||
// Create new analytics entry
|
||||
await ctx.db.insert("analytics", {
|
||||
containerTag: args.containerTag,
|
||||
totalMemories: args.incrementMemories || 0,
|
||||
|
|
|
|||
|
|
@ -1,58 +1,8 @@
|
|||
import { query } from "./_generated/server"
|
||||
import { v } from "convex/values"
|
||||
|
||||
/**
|
||||
* Supermemory Queries
|
||||
*
|
||||
* Queries provide reactive, read-only access to Supermemory data.
|
||||
* Components using these queries will automatically re-render when data changes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* List documents added to Supermemory
|
||||
* Provides visibility into what content has been indexed
|
||||
*/
|
||||
export const listDocuments = query({
|
||||
args: {
|
||||
containerTag: v.optional(v.string()),
|
||||
limit: v.optional(v.number()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const limit = args.limit || 50
|
||||
|
||||
if (args.containerTag) {
|
||||
return await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_container", (q) =>
|
||||
q.eq("containerTag", args.containerTag),
|
||||
)
|
||||
.order("desc")
|
||||
.take(limit)
|
||||
}
|
||||
|
||||
return await ctx.db.query("documents").order("desc").take(limit)
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Get document by custom ID
|
||||
* Find a specific document using your custom identifier
|
||||
*/
|
||||
export const getDocumentByCustomId = query({
|
||||
args: {
|
||||
customId: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_custom_id", (q) => q.eq("customId", args.customId))
|
||||
.first()
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Get API call logs
|
||||
* View recent Supermemory API calls for debugging and analytics
|
||||
*/
|
||||
export const getApiLogs = query({
|
||||
args: {
|
||||
|
|
@ -87,7 +37,6 @@ export const getApiLogs = query({
|
|||
|
||||
/**
|
||||
* Get API statistics
|
||||
* Aggregate stats for dashboard visibility
|
||||
*/
|
||||
export const getApiStats = query({
|
||||
args: {
|
||||
|
|
@ -117,7 +66,6 @@ export const getApiStats = query({
|
|||
callsByEndpoint: {} as Record<string, number>,
|
||||
}
|
||||
|
||||
// Count calls by endpoint
|
||||
for (const log of logs) {
|
||||
stats.callsByEndpoint[log.endpoint] =
|
||||
(stats.callsByEndpoint[log.endpoint] || 0) + 1
|
||||
|
|
@ -129,7 +77,7 @@ export const getApiStats = query({
|
|||
|
||||
/**
|
||||
* List memories for a user
|
||||
* View all memories saved through Supermemory
|
||||
* Includes content and extractedMemories
|
||||
*/
|
||||
export const listMemories = query({
|
||||
args: {
|
||||
|
|
@ -162,7 +110,6 @@ export const listMemories = query({
|
|||
|
||||
/**
|
||||
* Get chat sessions for a user
|
||||
* View conversation history with memory usage
|
||||
*/
|
||||
export const getChatSessions = query({
|
||||
args: {
|
||||
|
|
@ -182,7 +129,6 @@ export const getChatSessions = query({
|
|||
|
||||
/**
|
||||
* Get a specific chat session
|
||||
* View full conversation with memory usage
|
||||
*/
|
||||
export const getChatSession = query({
|
||||
args: {
|
||||
|
|
@ -195,7 +141,6 @@ export const getChatSession = query({
|
|||
|
||||
/**
|
||||
* Get analytics for a user
|
||||
* View usage statistics and metrics
|
||||
*/
|
||||
export const getAnalytics = query({
|
||||
args: {
|
||||
|
|
@ -211,7 +156,6 @@ export const getAnalytics = query({
|
|||
|
||||
/**
|
||||
* Get dashboard overview
|
||||
* Comprehensive view of user's memory usage
|
||||
*/
|
||||
export const getDashboardOverview = query({
|
||||
args: {
|
||||
|
|
@ -235,12 +179,6 @@ export const getDashboardOverview = query({
|
|||
.order("desc")
|
||||
.take(5)
|
||||
|
||||
const recentDocuments = await ctx.db
|
||||
.query("documents")
|
||||
.withIndex("by_container", (q) => q.eq("containerTag", args.containerTag))
|
||||
.order("desc")
|
||||
.take(10)
|
||||
|
||||
return {
|
||||
analytics: analytics || {
|
||||
totalMemories: 0,
|
||||
|
|
@ -251,7 +189,6 @@ export const getDashboardOverview = query({
|
|||
},
|
||||
recentMemories,
|
||||
recentSessions,
|
||||
recentDocuments,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,41 +1,12 @@
|
|||
import { defineSchema, defineTable } from "convex/server"
|
||||
import { v } from "convex/values"
|
||||
|
||||
/**
|
||||
* Convex schema for Supermemory component
|
||||
*
|
||||
* This schema defines tables for caching Supermemory API responses,
|
||||
* enabling reactive queries and reducing API calls.
|
||||
*/
|
||||
export default defineSchema({
|
||||
/**
|
||||
* Metadata about documents/memories added to Supermemory
|
||||
* Tracks what content has been sent to Supermemory for analytics
|
||||
*/
|
||||
documents: defineTable({
|
||||
documentId: v.string(), // Supermemory document ID
|
||||
customId: v.optional(v.string()),
|
||||
containerTag: v.string(),
|
||||
contentPreview: v.string(), // First 200 chars for reference
|
||||
metadata: v.optional(v.any()),
|
||||
status: v.union(
|
||||
v.literal("queued"),
|
||||
v.literal("processed"),
|
||||
v.literal("failed"),
|
||||
),
|
||||
addedAt: v.number(),
|
||||
})
|
||||
.index("by_container", ["containerTag"])
|
||||
.index("by_custom_id", ["customId"])
|
||||
.index("by_document_id", ["documentId"])
|
||||
.index("by_status", ["status"]),
|
||||
|
||||
/**
|
||||
* API call logs for dashboard visibility
|
||||
* Tracks all Supermemory API calls for debugging and analytics
|
||||
*/
|
||||
apiLogs: defineTable({
|
||||
endpoint: v.string(), // "add", "search", "profile", etc.
|
||||
endpoint: v.string(),
|
||||
containerTag: v.optional(v.string()),
|
||||
requestBody: v.optional(v.any()),
|
||||
responseStatus: v.union(
|
||||
|
|
@ -43,7 +14,7 @@ export default defineSchema({
|
|||
v.literal("error"),
|
||||
v.literal("pending"),
|
||||
),
|
||||
responseTime: v.optional(v.number()), // milliseconds
|
||||
responseTime: v.optional(v.number()),
|
||||
errorMessage: v.optional(v.string()),
|
||||
timestamp: v.number(),
|
||||
})
|
||||
|
|
@ -54,7 +25,6 @@ export default defineSchema({
|
|||
|
||||
/**
|
||||
* Component configuration
|
||||
* Stores API key and other settings
|
||||
*/
|
||||
config: defineTable({
|
||||
key: v.string(),
|
||||
|
|
@ -63,7 +33,7 @@ export default defineSchema({
|
|||
|
||||
/**
|
||||
* Memories - Core memory storage
|
||||
* All user memories saved through Supermemory
|
||||
* Stores original content and extracted memories from Supermemory
|
||||
*/
|
||||
memories: defineTable({
|
||||
content: v.string(),
|
||||
|
|
@ -73,7 +43,8 @@ export default defineSchema({
|
|||
v.literal("document"),
|
||||
v.literal("manual"),
|
||||
),
|
||||
supermemoryId: v.optional(v.string()), // ID from Supermemory API
|
||||
supermemoryId: v.optional(v.string()),
|
||||
extractedMemories: v.optional(v.array(v.string())),
|
||||
createdAt: v.number(),
|
||||
metadata: v.optional(v.any()),
|
||||
})
|
||||
|
|
@ -84,7 +55,6 @@ export default defineSchema({
|
|||
|
||||
/**
|
||||
* Chat Sessions - Conversation history with memory usage
|
||||
* Tracks full conversations and which memories were retrieved
|
||||
*/
|
||||
chatSessions: defineTable({
|
||||
containerTag: v.string(),
|
||||
|
|
@ -95,7 +65,7 @@ export default defineSchema({
|
|||
timestamp: v.number(),
|
||||
}),
|
||||
),
|
||||
memoriesRetrieved: v.array(v.string()), // IDs of memories used in this session
|
||||
memoriesRetrieved: v.array(v.string()),
|
||||
createdAt: v.number(),
|
||||
lastMessageAt: v.number(),
|
||||
})
|
||||
|
|
@ -104,7 +74,6 @@ export default defineSchema({
|
|||
|
||||
/**
|
||||
* Analytics - Usage statistics per user
|
||||
* Dashboard metrics for monitoring
|
||||
*/
|
||||
analytics: defineTable({
|
||||
containerTag: v.string(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useAction, useQuery, useMutation } from "convex/react"
|
||||
import { useAction, useQuery } from "convex/react"
|
||||
import { useState, useCallback } from "react"
|
||||
import type { FunctionReference } from "convex/server"
|
||||
import type {
|
||||
|
|
@ -7,7 +7,7 @@ import type {
|
|||
ProfileArgs,
|
||||
SearchResponse,
|
||||
ProfileResponse,
|
||||
Document,
|
||||
Memory,
|
||||
ApiLog,
|
||||
ApiStats,
|
||||
} from "../client/index"
|
||||
|
|
@ -20,25 +20,17 @@ import type {
|
|||
*/
|
||||
|
||||
/**
|
||||
* Hook to add memories to Supermemory
|
||||
*
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
* Add memories to Supermemory
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function ChatApp() {
|
||||
* const addMemory = useAddMemory();
|
||||
*
|
||||
* const handleSend = async (message: string) => {
|
||||
* await addMemory({
|
||||
* content: message,
|
||||
* containerTag: userId
|
||||
* });
|
||||
* };
|
||||
* const add = addMemory();
|
||||
* await add({ content: "Hello", containerTag: "user_123" });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useAddMemory(componentPath = "supermemory") {
|
||||
export function addMemory(componentPath = "supermemory") {
|
||||
const action =
|
||||
`${componentPath}:actions.add` as unknown as FunctionReference<"action">
|
||||
const addAction = useAction(action)
|
||||
|
|
@ -52,34 +44,16 @@ export function useAddMemory(componentPath = "supermemory") {
|
|||
}
|
||||
|
||||
/**
|
||||
* Hook to search Supermemory with reactive results
|
||||
*
|
||||
* @param args - Search arguments
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
* Search Supermemory with reactive results
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function SearchResults({ query, userId }) {
|
||||
* const { results, isLoading, error, search } = useSupermemorySearch({
|
||||
* q: query,
|
||||
* containerTag: userId,
|
||||
* searchMode: "hybrid"
|
||||
* });
|
||||
*
|
||||
* if (isLoading) return <div>Searching...</div>;
|
||||
* if (error) return <div>Error: {error.message}</div>;
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {results?.results.map(r => (
|
||||
* <div key={r.id}>{r.memory || r.chunk}</div>
|
||||
* ))}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* const { results, isLoading, search } = searchMemories({
|
||||
* q: "typescript", containerTag: "user_123"
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function useSupermemorySearch(
|
||||
export function searchMemories(
|
||||
args: SearchMemoriesArgs | null,
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
|
|
@ -110,43 +84,20 @@ export function useSupermemorySearch(
|
|||
[searchAction, args],
|
||||
)
|
||||
|
||||
return {
|
||||
results,
|
||||
isLoading,
|
||||
error,
|
||||
search,
|
||||
}
|
||||
return { results, isLoading, error, search }
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get user profile with reactive updates
|
||||
*
|
||||
* @param args - Profile arguments
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
* Get user profile
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function UserContext({ userId }) {
|
||||
* const { profile, isLoading, refresh } = useSupermemoryProfile({
|
||||
* containerTag: userId,
|
||||
* q: "recent preferences"
|
||||
* });
|
||||
*
|
||||
* if (!profile) return null;
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <h3>Static Facts</h3>
|
||||
* {profile.profile.static.map(fact => <p>{fact}</p>)}
|
||||
*
|
||||
* <h3>Dynamic Context</h3>
|
||||
* {profile.profile.dynamic.map(fact => <p>{fact}</p>)}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* const { profile, isLoading, refresh } = getProfile({
|
||||
* containerTag: "user_123"
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function useSupermemoryProfile(
|
||||
export function getProfile(
|
||||
args: ProfileArgs | null,
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
|
|
@ -177,173 +128,19 @@ export function useSupermemoryProfile(
|
|||
[profileAction, args],
|
||||
)
|
||||
|
||||
return {
|
||||
profile,
|
||||
isLoading,
|
||||
error,
|
||||
refresh,
|
||||
}
|
||||
return { profile, isLoading, error, refresh }
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to list documents reactively
|
||||
*
|
||||
* @param args - List arguments
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
* List memories reactively
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function DocumentList({ userId }) {
|
||||
* const documents = useDocumentList({ containerTag: userId, limit: 20 });
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {documents?.map(doc => (
|
||||
* <div key={doc._id}>
|
||||
* <p>{doc.contentPreview}</p>
|
||||
* <span>Status: {doc.status}</span>
|
||||
* </div>
|
||||
* ))}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* const memories = listMemories({ containerTag: "user_123" });
|
||||
* // memories includes content + extractedMemories for each entry
|
||||
* ```
|
||||
*/
|
||||
export function useDocumentList(
|
||||
args?: { containerTag?: string; limit?: number },
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.listDocuments` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, args || {}) as Document[] | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get a document by custom ID
|
||||
*
|
||||
* @param customId - Custom document identifier
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*/
|
||||
export function useDocument(
|
||||
customId: string | null,
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.getDocumentByCustomId` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, customId ? { customId } : "skip") as
|
||||
| Document
|
||||
| null
|
||||
| undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get API logs reactively
|
||||
*
|
||||
* @param args - Filter arguments
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function ApiLogs() {
|
||||
* const logs = useApiLogs({ limit: 50 });
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {logs?.map(log => (
|
||||
* <div key={log._id}>
|
||||
* {log.endpoint} - {log.responseStatus} ({log.responseTime}ms)
|
||||
* </div>
|
||||
* ))}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useApiLogs(
|
||||
args?: { endpoint?: string; containerTag?: string; limit?: number },
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.getApiLogs` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, args || {}) as ApiLog[] | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get API statistics reactively
|
||||
*
|
||||
* @param args - Filter arguments
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function Dashboard({ userId }) {
|
||||
* const stats = useApiStats({ containerTag: userId });
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>Total Calls: {stats?.totalCalls}</p>
|
||||
* <p>Success Rate: {((stats?.successfulCalls / stats?.totalCalls) * 100).toFixed(1)}%</p>
|
||||
* <p>Avg Response: {stats?.averageResponseTime.toFixed(0)}ms</p>
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useApiStats(
|
||||
args?: { containerTag?: string; limit?: number },
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.getApiStats` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, args || {}) as ApiStats | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to update document status
|
||||
*
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*/
|
||||
export function useUpdateDocumentStatus(componentPath = "supermemory") {
|
||||
const mutation =
|
||||
`${componentPath}:mutations.updateDocumentStatus` as unknown as FunctionReference<"mutation">
|
||||
const updateMutation = useMutation(mutation)
|
||||
|
||||
return useCallback(
|
||||
async (args: {
|
||||
documentId: string
|
||||
status: "queued" | "processed" | "failed"
|
||||
}) => {
|
||||
return await updateMutation(args)
|
||||
},
|
||||
[updateMutation],
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to list memories for a user
|
||||
*
|
||||
* @param args - Filter arguments
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function MemoryList({ userId }) {
|
||||
* const memories = useMemories({ containerTag: userId, limit: 50 });
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {memories?.map(memory => (
|
||||
* <div key={memory._id}>
|
||||
* <p>{memory.content}</p>
|
||||
* <span>Source: {memory.source}</span>
|
||||
* </div>
|
||||
* ))}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useMemories(
|
||||
export function listMemories(
|
||||
args: {
|
||||
containerTag: string
|
||||
source?: "chat" | "document" | "manual"
|
||||
|
|
@ -353,137 +150,9 @@ export function useMemories(
|
|||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.listMemories` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, args) as any[] | undefined
|
||||
return useQuery(query, args) as Memory[] | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get chat sessions for a user
|
||||
*
|
||||
* @param args - Filter arguments
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function ChatHistory({ userId }) {
|
||||
* const sessions = useChatSessions({ containerTag: userId, limit: 10 });
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {sessions?.map(session => (
|
||||
* <div key={session._id}>
|
||||
* <p>{session.messages.length} messages</p>
|
||||
* <p>Last active: {new Date(session.lastMessageAt).toLocaleString()}</p>
|
||||
* </div>
|
||||
* ))}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useChatSessions(
|
||||
args: { containerTag: string; limit?: number },
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.getChatSessions` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, args) as any[] | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get a specific chat session
|
||||
*
|
||||
* @param sessionId - Chat session ID
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*/
|
||||
export function useChatSession(
|
||||
sessionId: string | null,
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.getChatSession` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, sessionId ? { sessionId } : "skip") as
|
||||
| any
|
||||
| null
|
||||
| undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get analytics for a user
|
||||
*
|
||||
* @param containerTag - User identifier
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function Analytics({ userId }) {
|
||||
* const analytics = useAnalytics(userId);
|
||||
*
|
||||
* if (!analytics) return <div>Loading...</div>;
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>Total Memories: {analytics.totalMemories}</p>
|
||||
* <p>Total Chats: {analytics.totalChats}</p>
|
||||
* <p>Total Searches: {analytics.totalSearches}</p>
|
||||
* <p>Avg Response Time: {analytics.avgResponseTime.toFixed(0)}ms</p>
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useAnalytics(
|
||||
containerTag: string | null,
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.getAnalytics` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, containerTag ? { containerTag } : "skip") as
|
||||
| any
|
||||
| null
|
||||
| undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get dashboard overview
|
||||
*
|
||||
* @param containerTag - User identifier
|
||||
* @param componentPath - Path to the component (default: "supermemory")
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function Dashboard({ userId }) {
|
||||
* const overview = useDashboardOverview(userId);
|
||||
*
|
||||
* if (!overview) return <div>Loading...</div>;
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <h2>Analytics</h2>
|
||||
* <p>Total Memories: {overview.analytics.totalMemories}</p>
|
||||
* <p>Total Chats: {overview.analytics.totalChats}</p>
|
||||
*
|
||||
* <h2>Recent Memories</h2>
|
||||
* {overview.recentMemories.map(m => <p key={m._id}>{m.content}</p>)}
|
||||
*
|
||||
* <h2>Recent Sessions</h2>
|
||||
* {overview.recentSessions.map(s => (
|
||||
* <p key={s._id}>{s.messages.length} messages</p>
|
||||
* ))}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function useDashboardOverview(
|
||||
containerTag: string | null,
|
||||
componentPath = "supermemory",
|
||||
) {
|
||||
const query =
|
||||
`${componentPath}:queries.getDashboardOverview` as unknown as FunctionReference<"query">
|
||||
return useQuery(query, containerTag ? { containerTag } : "skip") as
|
||||
| any
|
||||
| undefined
|
||||
}
|
||||
|
||||
// Export all types
|
||||
export type {
|
||||
|
|
@ -492,7 +161,5 @@ export type {
|
|||
ProfileArgs,
|
||||
SearchResponse,
|
||||
ProfileResponse,
|
||||
Document,
|
||||
ApiLog,
|
||||
ApiStats,
|
||||
Memory,
|
||||
} from "../client/index"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue