added usage notes

This commit is contained in:
Mahesh Sanikommmu 2025-08-16 20:15:39 -07:00
parent 91d957f478
commit fddc21aab2
7 changed files with 645 additions and 421 deletions

288
CONTRIBUTE.md Normal file
View 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
View file

@ -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)

View file

@ -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>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB