mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-22 06:53:57 +00:00
added usage notes
This commit is contained in:
parent
91d957f478
commit
fddc21aab2
7 changed files with 645 additions and 421 deletions
288
CONTRIBUTE.md
Normal file
288
CONTRIBUTE.md
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
# Contributing to supermemory
|
||||
|
||||
Thank you for your interest in contributing to supermemory! We welcome contributions from developers of all skill levels. This guide will help you get started with contributing to our AI-powered memory layer API.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following installed:
|
||||
|
||||
- **Bun** (>= 1.2.17) - Our preferred package manager
|
||||
- **Git** for version control
|
||||
|
||||
### Setting Up the Development Environment
|
||||
|
||||
1. **Fork and Clone the Repository**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-username/supermemory-app.git
|
||||
cd supermemory-app
|
||||
```
|
||||
|
||||
2. **Install Dependencies**
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
3. **Set Up Environment Variables**
|
||||
|
||||
```bash
|
||||
# Copy the example environment file
|
||||
cp .env.example .env.local
|
||||
|
||||
# Edit the file with your configuration
|
||||
# You'll need to add your API keys and database URLs
|
||||
```
|
||||
|
||||
4. **Start the Development Server**
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
This will start all applications in the monorepo. The web app will be available at `http://localhost:3000`.
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
supermemory is organized as a monorepo using Turbo:
|
||||
|
||||
```
|
||||
supermemory-app/
|
||||
├── apps/
|
||||
│ └── web/ # Next.js web application
|
||||
├── packages/
|
||||
│ ├── ui/ # Shared UI components
|
||||
│ ├── lib/ # Shared utilities and logic
|
||||
│ ├── hooks/ # Shared React hooks
|
||||
│ ├── validation/ # Zod schemas and validation
|
||||
│ ├── eslint-config/ # ESLint configurations
|
||||
│ └── typescript-config/ # TypeScript configurations
|
||||
└── turbo.json # Turbo configuration
|
||||
```
|
||||
|
||||
## 🛠️ Development Workflow
|
||||
|
||||
### Available Scripts
|
||||
|
||||
- `bun run dev` - Start development servers for all apps
|
||||
- `bun run build` - Build all applications
|
||||
- `bun run format-lint` - Format and lint code using Biome
|
||||
- `bun run check-types` - Type check all packages
|
||||
|
||||
### Code Quality
|
||||
|
||||
We use several tools to maintain code quality:
|
||||
|
||||
- **Biome** for linting and formatting
|
||||
- **TypeScript** for type safety
|
||||
- **Turbo** for build optimization
|
||||
|
||||
Before submitting a PR, ensure your code passes all checks:
|
||||
|
||||
```bash
|
||||
bun run format-lint
|
||||
bun run check-types
|
||||
bun run build
|
||||
```
|
||||
|
||||
### Tech Stack
|
||||
|
||||
- **Frontend**: Next.js 15, React 19, TypeScript
|
||||
- **Styling**: Tailwind CSS, Radix UI components
|
||||
- **State Management**: Zustand, TanStack Query
|
||||
- **Build Tool**: Turbo (monorepo)
|
||||
- **Package Manager**: Bun
|
||||
- **Deployment**: Cloudflare (OpenNext.js)
|
||||
|
||||
## 🎯 How to Contribute
|
||||
|
||||
### Types of Contributions
|
||||
|
||||
We welcome various types of contributions:
|
||||
|
||||
- 🐛 **Bug fixes**
|
||||
- ✨ **New features**
|
||||
- 🎨 **UI/UX enhancements**
|
||||
- ⚡ **Performance optimizations**
|
||||
|
||||
### Finding Issues to Work On
|
||||
|
||||
1. Check our [Issues](https://github.com/supermemoryai/supermemory/issues) page
|
||||
2. Look for issues labeled `good first issue` for beginners
|
||||
3. Issues labeled `help wanted` are great for contributors
|
||||
4. Feel free to propose new features by opening an issue first
|
||||
|
||||
### Making Changes
|
||||
|
||||
1. **Create a Branch**
|
||||
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
# or
|
||||
git checkout -b fix/your-bug-fix
|
||||
```
|
||||
|
||||
2. **Make Your Changes**
|
||||
- Follow our coding standards (see below)
|
||||
- Write clear, concise commit messages
|
||||
- Add tests if applicable
|
||||
- Update documentation if needed
|
||||
|
||||
3. **Test Your Changes**
|
||||
```bash
|
||||
bun run dev # Test locally
|
||||
bun run build # Ensure it builds
|
||||
bun run format-lint # Check formatting
|
||||
bun run check-types # Check types
|
||||
```
|
||||
|
||||
## 📝 Coding Standards
|
||||
|
||||
### General Guidelines
|
||||
|
||||
- Use **TypeScript** for all new code
|
||||
- Follow the existing code style and patterns
|
||||
- Write self-documenting code with clear variable names
|
||||
- Add JSDoc comments for complex functions
|
||||
- Keep functions small and focused
|
||||
|
||||
### Component Guidelines
|
||||
|
||||
- Use functional components with hooks
|
||||
- Prefer composition over inheritance
|
||||
- Extract reusable logic into custom hooks
|
||||
- Use proper TypeScript types for props
|
||||
|
||||
### File Naming
|
||||
|
||||
- Use `kebab-case` for file names
|
||||
- Use `PascalCase` for component files
|
||||
- Use `camelCase` for utility functions
|
||||
|
||||
### Import Organization
|
||||
|
||||
```typescript
|
||||
// 1. React and Next.js imports
|
||||
import React from 'react';
|
||||
import { NextPage } from 'next';
|
||||
|
||||
// 2. Third-party libraries
|
||||
import { clsx } from 'clsx';
|
||||
import { motion } from 'motion';
|
||||
|
||||
// 3. Internal packages
|
||||
import { Button } from '@repo/ui';
|
||||
import { useAuth } from '@repo/lib';
|
||||
|
||||
// 4. Relative imports
|
||||
import { Header } from './header';
|
||||
import { Footer } from './footer';
|
||||
```
|
||||
|
||||
## 🔄 Pull Request Process
|
||||
|
||||
### Before Submitting
|
||||
|
||||
1. Ensure your branch is up to date with `main`
|
||||
2. Run all quality checks
|
||||
3. Test your changes thoroughly
|
||||
4. Update documentation if needed
|
||||
|
||||
### PR Guidelines
|
||||
|
||||
1. **Title**: Use a clear, descriptive title
|
||||
- ✅ `feat: add semantic search to memory graph`
|
||||
- ✅ `fix: resolve authentication redirect loop`
|
||||
- ❌ `update stuff`
|
||||
|
||||
2. **Description**: Include:
|
||||
- What changes you made and why
|
||||
- Screenshots for UI changes
|
||||
- Any breaking changes
|
||||
- Related issue numbers
|
||||
|
||||
3. **Size**: Keep PRs focused and reasonably sized
|
||||
- Prefer multiple small PRs over one large PR
|
||||
- Each PR should address a single concern
|
||||
|
||||
### Review Process
|
||||
|
||||
1. All PRs require at least one review
|
||||
2. Address feedback promptly and professionally
|
||||
3. Be open to suggestions and improvements
|
||||
4. Maintain a collaborative attitude
|
||||
|
||||
## 🐛 Reporting Issues
|
||||
|
||||
### Bug Reports
|
||||
|
||||
When reporting bugs, please include:
|
||||
|
||||
- **Environment**: OS, Node.js version, browser
|
||||
- **Steps to reproduce** the issue
|
||||
- **Expected behavior**
|
||||
- **Actual behavior**
|
||||
- **Screenshots** if applicable
|
||||
- **Error messages** or console logs
|
||||
|
||||
### Feature Requests
|
||||
|
||||
For feature requests, please provide:
|
||||
|
||||
- **Problem statement**: What problem does this solve?
|
||||
- **Proposed solution**: How should it work?
|
||||
- **Alternatives considered**: Other approaches you've thought of
|
||||
- **Additional context**: Any relevant information
|
||||
|
||||
## 🏗️ Architecture Guidelines
|
||||
|
||||
### State Management
|
||||
|
||||
- Use **Zustand** for global state
|
||||
- Use **TanStack Query** for server state
|
||||
- Keep state as local as possible
|
||||
- Use proper TypeScript types for state
|
||||
|
||||
### API Integration
|
||||
|
||||
- Use the existing API client patterns
|
||||
- Handle loading and error states properly
|
||||
- Implement proper error boundaries
|
||||
- Use optimistic updates where appropriate
|
||||
|
||||
### Performance
|
||||
|
||||
- Use React.memo() for expensive components
|
||||
- Implement proper loading states
|
||||
- Optimize images and assets
|
||||
- Use code splitting where beneficial
|
||||
|
||||
## 🤝 Community Guidelines
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
- Be respectful and inclusive
|
||||
- Welcome newcomers and help them learn
|
||||
- Focus on constructive feedback
|
||||
- Maintain professionalism in all interactions
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Discord**: Join our community server
|
||||
- **GitHub Discussions**: For questions and ideas
|
||||
- **Issues**: For bug reports and feature requests
|
||||
- **Email**: [dhravya@supermemory.com](mailto:dhravya@supermemory.com)
|
||||
|
||||
## 📄 License
|
||||
|
||||
By contributing to supermemory, you agree that your contributions will be licensed under the same license as the project.
|
||||
|
||||
## 🙏 Recognition
|
||||
|
||||
All contributors will be recognized in our README and release notes. We appreciate every contribution, no matter how small!
|
||||
|
||||
---
|
||||
|
||||
Thank you for contributing to supermemory! Together, we're building the future of AI-powered knowledge management. 🚀
|
||||
114
README.md
114
README.md
|
|
@ -1,4 +1,4 @@
|
|||
<div align="center" style="padding-bottom:10px;padding-top:10px">
|
||||
<div align="center" style="padding-bottom:20px;padding-top:20px">
|
||||
<img src="logo.svg" alt="supermemory Logo" width="400" />
|
||||
</div>
|
||||
|
||||
|
|
@ -6,94 +6,70 @@
|
|||
<img src="apps/web/public/landing-page.jpeg" alt="supermemory" width="100%" />
|
||||
</div>
|
||||
|
||||
## ✨ Features
|
||||
## Features
|
||||
|
||||
### Core Functionality
|
||||
- **Add Memories from Any Content**: Easily add memories from URLs, PDFs, and plain text—just paste, upload, or link.
|
||||
- **Chat with Your Memories**: Converse with your stored content using natural language chat.
|
||||
- **Supermemory MCP Integration**: Seamlessly connect with all major AI tools (Claude, Cursor, etc.) via Supermemory MCP.
|
||||
- **Graph View for All Memories**: Visualize and explore your memories and their connections in an interactive graph mode.
|
||||
|
||||
## 🏗️ Architecture
|
||||
- **[Add Memories from Any Content](#add-memory)**: Easily add memories from URLs, PDFs, and plain text—just paste, upload, or link.
|
||||
- **[Chat with Your Memories](#chat-memories)**: Converse with your stored content using natural language chat.
|
||||
- **[Supermemory MCP Integration](#mcp-integration)**: Seamlessly connect with all major AI tools (Claude, Cursor, etc.) via Supermemory MCP.
|
||||
|
||||
This is a **Turborepo monorepo**
|
||||
## How do i use this?
|
||||
|
||||
### Technology Stack
|
||||
- **Frontend**: Next.js 15 with React 19
|
||||
- **Backend**: Hono API framework on Cloudflare Workers
|
||||
- **Database**: PostgreSQL with Drizzle ORM
|
||||
- **Authentication**: Better Auth with organization support
|
||||
- **Package Manager**: Bun
|
||||
- **Monorepo**: Turbo for build optimization
|
||||
- **Styling**: Tailwind CSS with Radix UI components
|
||||
- **Monitoring**: Sentry for error tracking and performance monitoring
|
||||
Go to [app.supermemory.ai](https://app.supermemory.ai) and sign into with your account
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
├── apps/
|
||||
│ └── web/ # Next.js web application
|
||||
├── packages/ # Shared packages and utilities
|
||||
├── CLAUDE.md # Development guidelines for AI assistants
|
||||
├── turbo.json # Turborepo configuration
|
||||
└── package.json # Root package configuration
|
||||
```
|
||||
1. <a id="add-memory"></a>Start Adding Memory with your choose of format (Note, Link, File)
|
||||
<div align="center" style="padding-bottom:10px;padding-top:10px">
|
||||
<img src="apps/web/public/add-memory.png" alt="supermemory" width="100%" />
|
||||
</div>
|
||||
|
||||
## 🚀 Getting Started
|
||||
2. You can also Connect to your favourite services (Notion, Google Drive, OneDrive)
|
||||
<div align="center" style="padding-bottom:10px;padding-top:10px">
|
||||
<img src="apps/web/public/add-connections.png" alt="supermemory" width="100%" />
|
||||
</div>
|
||||
|
||||
### Prerequisites
|
||||
- **Bun** package manager
|
||||
3. <a id="chat-memories"></a>Once Memories are added, you can chat with Supermemory by clicking on "Open Chat" and retrieve info from your saved memories
|
||||
<div align="center" style="padding-bottom:10px;padding-top:10px">
|
||||
<img src="apps/web/public/chat.png" alt="supermemory" width="100%" />
|
||||
</div>
|
||||
|
||||
### Installation
|
||||
4. <a id="mcp-integration"></a>Add MCP to your AI Tools (by clicking on "Connect to your AI" and select the AI tool you are trying to integrate)
|
||||
<div align="center" style="padding-bottom:10px;padding-top:10px">
|
||||
<img src="apps/web/public/mcp.png" alt="supermemory" width="100%" />
|
||||
</div>
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone https://github.com/supermemoryai/supermemory-app.git
|
||||
cd supermemory
|
||||
```
|
||||
## Support
|
||||
|
||||
2. **Install dependencies**
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
Have questions or feedback? We're here to help:
|
||||
|
||||
3. **Environment Setup**
|
||||
|
||||
Create environment files for each app:
|
||||
```bash
|
||||
# Copy environment templates
|
||||
cp apps/web/.env.example apps/web/.env.local
|
||||
```
|
||||
- Email: [dhravya@supermemory.com](mailto:dhravya@supermemory.com)
|
||||
- Documentation: [docs.supermemory.ai](https://docs.supermemory.ai)
|
||||
|
||||
### Development
|
||||
## Contributing
|
||||
|
||||
#### Start all applications in development mode:
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
We welcome contributions from developers of all skill levels! Whether you're fixing bugs, adding features, or improving documentation, your help makes supermemory better for everyone.
|
||||
|
||||
This will start:
|
||||
- Web app at `http://localhost:3000`
|
||||
- API endpoints available through the web app
|
||||
### Quick Start for Contributors
|
||||
|
||||
1. **Fork and clone** the repository
|
||||
2. **Install dependencies** with `bun install`
|
||||
3. **Set up your environment** by copying `.env.example` to `.env.local`
|
||||
4. **Start developing** with `bun run dev`
|
||||
|
||||
## 🧪 Development Workflow
|
||||
For detailed guidelines, development setup, coding standards, and the complete contribution workflow, please see our [**Contributing Guide**](CONTRIBUTE.md).
|
||||
|
||||
### Code Quality
|
||||
- **Linting & Formatting**: Uses Biome for consistent code style
|
||||
- **Type Safety**: Strict TypeScript configuration across all packages
|
||||
### Ways to Contribute
|
||||
|
||||
## 🤝 Contributing
|
||||
- 🐛 **Bug fixes** - Help us squash those pesky issues
|
||||
- ✨ **New features** - Add functionality that users will love
|
||||
- 🎨 **UI/UX improvements** - Make the interface more intuitive
|
||||
- ⚡ **Performance optimizations** - Help us make supermemory faster
|
||||
|
||||
### Development Guidelines
|
||||
- Follow the code style enforced by Biome
|
||||
- Write tests for new features
|
||||
- Update documentation when adding new functionality
|
||||
- Ensure all checks pass before submitting PRs
|
||||
Check out our [Issues](https://github.com/supermemoryai/supermemory/issues) page for `good first issue` and `help wanted` labels to get started!
|
||||
|
||||
## Updates & Roadmap
|
||||
|
||||
## 💬 Support & Community
|
||||
|
||||
- **Issues**: [GitHub Issues](https://github.com/supermemoryai/supermemory-app/issues)
|
||||
- **Email**: [dhravya@supermemory.com](mailto:dhravya@supermemory.com)
|
||||
- **Twitter**: [@supermemoryai](https://x.com/supermemoryai)
|
||||
Stay up to date with the latest improvements:
|
||||
|
||||
- [Changelog](https://docs.supermemory.ai/changelog/overview)
|
||||
- [X](https://x.com/supermemoryai)
|
||||
|
|
|
|||
|
|
@ -1,382 +1,342 @@
|
|||
"use client"
|
||||
'use client';
|
||||
|
||||
import { $fetch } from "@lib/api"
|
||||
import { $fetch } from '@lib/api';
|
||||
import {
|
||||
fetchConnectionsFeature,
|
||||
fetchConsumerProProduct,
|
||||
} from "@repo/lib/queries"
|
||||
import { Button } from "@repo/ui/components/button"
|
||||
fetchConnectionsFeature,
|
||||
fetchConsumerProProduct,
|
||||
} from '@repo/lib/queries';
|
||||
import { Button } from '@repo/ui/components/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@repo/ui/components/dialog"
|
||||
import { Skeleton } from "@repo/ui/components/skeleton"
|
||||
import type { ConnectionResponseSchema } from "@repo/validation/api"
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons"
|
||||
import { useCustomer } from "autumn-js/react"
|
||||
import { Plus, Trash2 } from "lucide-react"
|
||||
import { AnimatePresence, motion } from "motion/react"
|
||||
import Link from "next/link"
|
||||
import { useEffect, useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
import type { z } from "zod"
|
||||
import { useProject } from "@/stores"
|
||||
import { analytics } from "@/lib/analytics"
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@repo/ui/components/dialog';
|
||||
import { Skeleton } from '@repo/ui/components/skeleton';
|
||||
import type { ConnectionResponseSchema } from '@repo/validation/api';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { GoogleDrive, Notion, OneDrive } from '@ui/assets/icons';
|
||||
import { useCustomer } from 'autumn-js/react';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import Link from 'next/link';
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import type { z } from 'zod';
|
||||
import { useProject } from '@/stores';
|
||||
import { analytics } from '@/lib/analytics';
|
||||
|
||||
// Define types
|
||||
type Connection = z.infer<typeof ConnectionResponseSchema>
|
||||
type Connection = z.infer<typeof ConnectionResponseSchema>;
|
||||
|
||||
// Connector configurations
|
||||
const CONNECTORS = {
|
||||
"google-drive": {
|
||||
title: "Google Drive",
|
||||
description: "Connect your Google Docs, Sheets, and Slides",
|
||||
icon: GoogleDrive,
|
||||
},
|
||||
notion: {
|
||||
title: "Notion",
|
||||
description: "Import your Notion pages and databases",
|
||||
icon: Notion,
|
||||
},
|
||||
onedrive: {
|
||||
title: "OneDrive",
|
||||
description: "Access your Microsoft Office documents",
|
||||
icon: OneDrive,
|
||||
},
|
||||
} as const
|
||||
'google-drive': {
|
||||
title: 'Google Drive',
|
||||
description: 'Connect your Google Docs, Sheets, and Slides',
|
||||
icon: GoogleDrive,
|
||||
},
|
||||
notion: {
|
||||
title: 'Notion',
|
||||
description: 'Import your Notion pages and databases',
|
||||
icon: Notion,
|
||||
},
|
||||
onedrive: {
|
||||
title: 'OneDrive',
|
||||
description: 'Access your Microsoft Office documents',
|
||||
icon: OneDrive,
|
||||
},
|
||||
} as const;
|
||||
|
||||
type ConnectorProvider = keyof typeof CONNECTORS
|
||||
type ConnectorProvider = keyof typeof CONNECTORS;
|
||||
|
||||
export function ConnectionsTabContent() {
|
||||
const queryClient = useQueryClient()
|
||||
const [showAddDialog, setShowAddDialog] = useState(false)
|
||||
const { selectedProject } = useProject()
|
||||
const autumn = useCustomer()
|
||||
const queryClient = useQueryClient();
|
||||
const { selectedProject } = useProject();
|
||||
const autumn = useCustomer();
|
||||
|
||||
const handleUpgrade = async () => {
|
||||
try {
|
||||
await autumn.attach({
|
||||
productId: "consumer_pro",
|
||||
successUrl: "https://app.supermemory.ai/",
|
||||
})
|
||||
window.location.reload()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
const handleUpgrade = async () => {
|
||||
try {
|
||||
await autumn.attach({
|
||||
productId: 'consumer_pro',
|
||||
successUrl: 'https://app.supermemory.ai/',
|
||||
});
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const { data: connectionsCheck } = fetchConnectionsFeature(autumn as any)
|
||||
const connectionsUsed = connectionsCheck?.balance ?? 0
|
||||
const connectionsLimit = connectionsCheck?.included_usage ?? 0
|
||||
const { data: connectionsCheck } = fetchConnectionsFeature(autumn as any);
|
||||
const connectionsUsed = connectionsCheck?.balance ?? 0;
|
||||
const connectionsLimit = connectionsCheck?.included_usage ?? 0;
|
||||
|
||||
const { data: proCheck } = fetchConsumerProProduct(autumn as any)
|
||||
const isProUser = proCheck?.allowed ?? false
|
||||
const { data: proCheck } = fetchConsumerProProduct(autumn as any);
|
||||
const isProUser = proCheck?.allowed ?? false;
|
||||
|
||||
const canAddConnection = connectionsUsed < connectionsLimit
|
||||
const canAddConnection = connectionsUsed < connectionsLimit;
|
||||
|
||||
// Fetch connections
|
||||
const {
|
||||
data: connections = [],
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ["connections"],
|
||||
queryFn: async () => {
|
||||
const response = await $fetch("@post/connections/list", {
|
||||
body: {
|
||||
containerTags: [],
|
||||
},
|
||||
})
|
||||
// Fetch connections
|
||||
const {
|
||||
data: connections = [],
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ['connections'],
|
||||
queryFn: async () => {
|
||||
const response = await $fetch('@post/connections/list', {
|
||||
body: {
|
||||
containerTags: [],
|
||||
},
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error?.message || "Failed to load connections")
|
||||
}
|
||||
if (response.error) {
|
||||
throw new Error(
|
||||
response.error?.message || 'Failed to load connections'
|
||||
);
|
||||
}
|
||||
|
||||
return response.data as Connection[]
|
||||
},
|
||||
staleTime: 30 * 1000,
|
||||
refetchInterval: 60 * 1000,
|
||||
})
|
||||
return response.data as Connection[];
|
||||
},
|
||||
staleTime: 30 * 1000,
|
||||
refetchInterval: 60 * 1000,
|
||||
});
|
||||
|
||||
// Show error toast if connections fail to load
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
toast.error("Failed to load connections", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
})
|
||||
}
|
||||
}, [error])
|
||||
// Show error toast if connections fail to load
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
toast.error('Failed to load connections', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
// Add connection mutation
|
||||
const addConnectionMutation = useMutation({
|
||||
mutationFn: async (provider: ConnectorProvider) => {
|
||||
// Check if user can add connections
|
||||
if (!canAddConnection && !isProUser) {
|
||||
throw new Error(
|
||||
"Free plan doesn't include connections. Upgrade to Pro for unlimited connections.",
|
||||
)
|
||||
}
|
||||
// Add connection mutation
|
||||
const addConnectionMutation = useMutation({
|
||||
mutationFn: async (provider: ConnectorProvider) => {
|
||||
// Check if user can add connections
|
||||
if (!canAddConnection && !isProUser) {
|
||||
throw new Error(
|
||||
"Free plan doesn't include connections. Upgrade to Pro for unlimited connections."
|
||||
);
|
||||
}
|
||||
|
||||
const response = await $fetch("@post/connections/:provider", {
|
||||
params: { provider },
|
||||
body: {
|
||||
redirectUrl: window.location.href,
|
||||
containerTags: [selectedProject],
|
||||
},
|
||||
})
|
||||
const response = await $fetch('@post/connections/:provider', {
|
||||
params: { provider },
|
||||
body: {
|
||||
redirectUrl: window.location.href,
|
||||
containerTags: [selectedProject],
|
||||
},
|
||||
});
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: its fine
|
||||
if ("data" in response && !("error" in response.data!)) {
|
||||
return response.data
|
||||
}
|
||||
// biome-ignore lint/style/noNonNullAssertion: its fine
|
||||
if ('data' in response && !('error' in response.data!)) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
throw new Error(response.error?.message || "Failed to connect")
|
||||
},
|
||||
onSuccess: (data, provider) => {
|
||||
analytics.connectionAdded(provider)
|
||||
analytics.connectionAuthStarted()
|
||||
if (data?.authLink) {
|
||||
window.location.href = data.authLink
|
||||
}
|
||||
},
|
||||
onError: (error, provider) => {
|
||||
analytics.connectionAuthFailed()
|
||||
toast.error(`Failed to connect ${provider}`, {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
})
|
||||
},
|
||||
})
|
||||
throw new Error(response.error?.message || 'Failed to connect');
|
||||
},
|
||||
onSuccess: (data, provider) => {
|
||||
analytics.connectionAdded(provider);
|
||||
analytics.connectionAuthStarted();
|
||||
if (data?.authLink) {
|
||||
window.location.href = data.authLink;
|
||||
}
|
||||
},
|
||||
onError: (error, provider) => {
|
||||
analytics.connectionAuthFailed();
|
||||
toast.error(`Failed to connect ${provider}`, {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Delete connection mutation
|
||||
const deleteConnectionMutation = useMutation({
|
||||
mutationFn: async (connectionId: string) => {
|
||||
await $fetch(`@delete/connections/${connectionId}`)
|
||||
},
|
||||
onSuccess: () => {
|
||||
analytics.connectionDeleted()
|
||||
toast.success(
|
||||
"Connection removal has started. supermemory will permanently delete the documents in the next few minutes.",
|
||||
)
|
||||
queryClient.invalidateQueries({ queryKey: ["connections"] })
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error("Failed to remove connection", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
})
|
||||
},
|
||||
})
|
||||
// Delete connection mutation
|
||||
const deleteConnectionMutation = useMutation({
|
||||
mutationFn: async (connectionId: string) => {
|
||||
await $fetch(`@delete/connections/${connectionId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
analytics.connectionDeleted();
|
||||
toast.success(
|
||||
'Connection removal has started. supermemory will permanently delete the documents in the next few minutes.'
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ['connections'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error('Failed to remove connection', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const getProviderIcon = (provider: string) => {
|
||||
const connector = CONNECTORS[provider as ConnectorProvider]
|
||||
if (connector) {
|
||||
const Icon = connector.icon
|
||||
return <Icon className="h-10 w-10" />
|
||||
}
|
||||
return <span className="text-2xl">📎</span>
|
||||
}
|
||||
const getProviderIcon = (provider: string) => {
|
||||
const connector = CONNECTORS[provider as ConnectorProvider];
|
||||
if (connector) {
|
||||
const Icon = connector.icon;
|
||||
return <Icon className="h-10 w-10" />;
|
||||
}
|
||||
return <span className="text-2xl">📎</span>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<p className="text-sm text-white/70">
|
||||
Connect your favorite services to import documents
|
||||
</p>
|
||||
{!isProUser && (
|
||||
<p className="text-xs text-white/50 mt-1">
|
||||
Connections require a Pro subscription
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
|
||||
<Button
|
||||
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
|
||||
disabled={!canAddConnection && !isProUser}
|
||||
onClick={() => setShowAddDialog(true)}
|
||||
size="sm"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add
|
||||
</Button>
|
||||
</motion.div>
|
||||
</div>
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-white/70">
|
||||
Connect your favorite services to import documents
|
||||
</p>
|
||||
{!isProUser && (
|
||||
<p className="text-xs text-white/50 mt-1">
|
||||
Connections require a Pro subscription
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Show upgrade prompt for free users */}
|
||||
{!isProUser && (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg"
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
>
|
||||
<p className="text-sm text-yellow-400 mb-2">
|
||||
🔌 Connections are a Pro feature
|
||||
</p>
|
||||
<p className="text-xs text-white/60 mb-3">
|
||||
Connect Google Drive, Notion, OneDrive and more to automatically
|
||||
sync your documents.
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
className="bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-400 border-yellow-500/30"
|
||||
onClick={handleUpgrade}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
{/* Show upgrade prompt for free users */}
|
||||
{!isProUser && (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg"
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
>
|
||||
<p className="text-sm text-yellow-400 mb-2">
|
||||
🔌 Connections are a Pro feature
|
||||
</p>
|
||||
<p className="text-xs text-white/60 mb-3">
|
||||
Connect Google Drive, Notion, OneDrive and more to automatically
|
||||
sync your documents.
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
className="bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-400 border-yellow-500/30"
|
||||
onClick={handleUpgrade}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
{[...Array(2)].map((_, i) => (
|
||||
<motion.div
|
||||
animate={{ opacity: 1 }}
|
||||
className="p-4 bg-white/5 rounded-lg"
|
||||
initial={{ opacity: 0 }}
|
||||
key={`skeleton-${Date.now()}-${i}`}
|
||||
transition={{ delay: i * 0.1 }}
|
||||
>
|
||||
<Skeleton className="h-12 w-full bg-white/10" />
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
) : connections.length === 0 ? (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="text-center py-8"
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
transition={{ type: "spring", damping: 20 }}
|
||||
>
|
||||
<p className="text-white/50 mb-4">No connections yet</p>
|
||||
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
|
||||
<Button
|
||||
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
|
||||
onClick={() => setShowAddDialog(true)}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
Add Your First Connection
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div className="space-y-2">
|
||||
<AnimatePresence>
|
||||
{connections.map((connection, index) => (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="flex items-center justify-between p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-colors"
|
||||
exit={{ opacity: 0, x: 20 }}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
key={connection.id}
|
||||
layout
|
||||
transition={{ delay: index * 0.05 }}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<motion.div
|
||||
animate={{ rotate: 0, opacity: 1 }}
|
||||
initial={{ rotate: -180, opacity: 0 }}
|
||||
transition={{ delay: index * 0.05 + 0.2 }}
|
||||
>
|
||||
{getProviderIcon(connection.provider)}
|
||||
</motion.div>
|
||||
<div>
|
||||
<p className="font-medium text-white capitalize">
|
||||
{connection.provider.replace("-", " ")}
|
||||
</p>
|
||||
{connection.email && (
|
||||
<p className="text-sm text-white/60">
|
||||
{connection.email}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
>
|
||||
<Button
|
||||
className="text-white/50 hover:text-red-400"
|
||||
disabled={deleteConnectionMutation.isPending}
|
||||
onClick={() =>
|
||||
deleteConnectionMutation.mutate(connection.id)
|
||||
}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
)}
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
{[...Array(2)].map((_, i) => (
|
||||
<motion.div
|
||||
animate={{ opacity: 1 }}
|
||||
className="p-4 bg-white/5 rounded-lg"
|
||||
initial={{ opacity: 0 }}
|
||||
key={`skeleton-${Date.now()}-${i}`}
|
||||
transition={{ delay: i * 0.1 }}
|
||||
>
|
||||
<Skeleton className="h-12 w-full bg-white/10" />
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
) : connections.length === 0 ? (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="text-center py-4"
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
transition={{ type: 'spring', damping: 20 }}
|
||||
>
|
||||
<p className="text-white/50 mb-2">No connections yet</p>
|
||||
<p className="text-xs text-white/40">
|
||||
Choose a service below to connect
|
||||
</p>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div className="space-y-2">
|
||||
<AnimatePresence>
|
||||
{connections.map((connection, index) => (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="flex items-center justify-between p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-colors"
|
||||
exit={{ opacity: 0, x: 20 }}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
key={connection.id}
|
||||
layout
|
||||
transition={{ delay: index * 0.05 }}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<motion.div
|
||||
animate={{ rotate: 0, opacity: 1 }}
|
||||
initial={{ rotate: -180, opacity: 0 }}
|
||||
transition={{ delay: index * 0.05 + 0.2 }}
|
||||
>
|
||||
{getProviderIcon(connection.provider)}
|
||||
</motion.div>
|
||||
<div>
|
||||
<p className="font-medium text-white capitalize">
|
||||
{connection.provider.replace('-', ' ')}
|
||||
</p>
|
||||
{connection.email && (
|
||||
<p className="text-sm text-white/60">
|
||||
{connection.email}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
>
|
||||
<Button
|
||||
className="text-white/50 hover:text-red-400"
|
||||
disabled={deleteConnectionMutation.isPending}
|
||||
onClick={() =>
|
||||
deleteConnectionMutation.mutate(connection.id)
|
||||
}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Add Connection Dialog */}
|
||||
<AnimatePresence>
|
||||
{showAddDialog && (
|
||||
<Dialog onOpenChange={setShowAddDialog} open={showAddDialog}>
|
||||
<DialogContent className="sm:max-w-2xl bg-black/90 backdrop-blur-xl border-white/10 text-white">
|
||||
<motion.div
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a Connection</DialogTitle>
|
||||
<DialogDescription className="text-white/60">
|
||||
Choose a service to connect and import your documents
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-3 py-4">
|
||||
{Object.entries(CONNECTORS).map(
|
||||
([provider, config], index) => {
|
||||
const Icon = config.icon
|
||||
return (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
key={provider}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<Button
|
||||
className="justify-start h-auto p-4 bg-white/5 hover:bg-white/10 border-white/10 text-white w-full"
|
||||
disabled={addConnectionMutation.isPending}
|
||||
onClick={() => {
|
||||
addConnectionMutation.mutate(
|
||||
provider as ConnectorProvider,
|
||||
)
|
||||
setShowAddDialog(false)
|
||||
// onClose?.()
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
<Icon className="h-8 w-8 mr-3" />
|
||||
<div className="text-left">
|
||||
<div className="font-medium">{config.title}</div>
|
||||
<div className="text-sm text-white/60 mt-0.5">
|
||||
{config.description}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</motion.div>
|
||||
)
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
{/* Available Connections Section */}
|
||||
<div className="mt-6">
|
||||
<h3 className="text-lg font-medium text-white mb-4">
|
||||
Available Connections
|
||||
</h3>
|
||||
<div className="grid gap-3">
|
||||
{Object.entries(CONNECTORS).map(([provider, config], index) => {
|
||||
const Icon = config.icon;
|
||||
return (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
key={provider}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<Button
|
||||
className="justify-start h-auto p-4 bg-white/5 hover:bg-white/10 border-white/10 text-white w-full"
|
||||
disabled={addConnectionMutation.isPending}
|
||||
onClick={() => {
|
||||
addConnectionMutation.mutate(provider as ConnectorProvider);
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
<Icon className="h-8 w-8 mr-3" />
|
||||
<div className="text-left">
|
||||
<div className="font-medium">{config.title}</div>
|
||||
<div className="text-sm text-white/60 mt-0.5">
|
||||
{config.description}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
BIN
apps/web/public/add-connections.png
Normal file
BIN
apps/web/public/add-connections.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 331 KiB |
BIN
apps/web/public/add-memory.png
Normal file
BIN
apps/web/public/add-memory.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 363 KiB |
BIN
apps/web/public/chat.png
Normal file
BIN
apps/web/public/chat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 334 KiB |
BIN
apps/web/public/mcp.png
Normal file
BIN
apps/web/public/mcp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 412 KiB |
Loading…
Add table
Add a link
Reference in a new issue