├── requirements.txt
├── agent
├── ai_researcher
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── demo.cpython-312.pyc
│ │ ├── agent.cpython-312.pyc
│ │ ├── model.cpython-312.pyc
│ │ ├── search.cpython-312.pyc
│ │ ├── state.cpython-312.pyc
│ │ ├── steps.cpython-312.pyc
│ │ ├── __init__.cpython-312.pyc
│ │ ├── extract.cpython-312.pyc
│ │ └── summarize.cpython-312.pyc
│ ├── model.py
│ ├── demo.py
│ ├── state.py
│ ├── agent.py
│ ├── search.py
│ ├── summarize.py
│ ├── extract.py
│ └── steps.py
├── .gitignore
├── langgraph.json
└── pyproject.toml
├── .eslintrc.json
├── public
├── cv.png
├── dp.jpg
├── robot.png
├── chat-app.png
├── reading.png
├── workflow.png
├── youtube.png
├── accounting.png
├── event-list.png
├── frequency.png
├── leadership.png
├── product-release.png
├── link.svg
├── twit.svg
└── git.svg
├── structure.txt
├── src
├── app
│ ├── favicon.ico
│ ├── loading.tsx
│ ├── page.tsx
│ ├── study-buddy
│ │ ├── page.tsx
│ │ └── instructions.ts
│ ├── api
│ │ └── copilotkit
│ │ │ └── route.ts
│ ├── chat
│ │ ├── instructions.ts
│ │ └── page.tsx
│ ├── expensetracker
│ │ └── instructions.ts
│ ├── todo
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── spreadsheet
│ │ ├── instructions.ts
│ │ └── page.tsx
│ └── globals.css
├── lib
│ ├── utils.ts
│ ├── tasks.types.ts
│ ├── default-tasks.ts
│ ├── research-provider.tsx
│ └── hooks
│ │ └── use-tasks.tsx
├── components
│ ├── Spreadsheet
│ │ ├── type.ts
│ │ ├── canonicalSpreadsheetData.ts
│ │ ├── Sidebar.tsx
│ │ ├── PreviewSpreadsheetChanges.tsx
│ │ └── SingleSpreadsheet.tsx
│ ├── StudyBuddy
│ │ ├── AnswerMarkdown.tsx
│ │ ├── SkeletonLoader.tsx
│ │ ├── ResearchWrapper.tsx
│ │ ├── Progress.tsx
│ │ ├── ResultsView.tsx
│ │ └── HomeView.tsx
│ ├── ui
│ │ ├── skeleton.tsx
│ │ ├── label.tsx
│ │ ├── textarea.tsx
│ │ ├── input.tsx
│ │ ├── meteors.tsx
│ │ ├── checkbox.tsx
│ │ ├── Spotlight.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── accordion.tsx
│ │ ├── background-gradient.tsx
│ │ ├── flip-words.tsx
│ │ ├── moving-border.tsx
│ │ ├── dialog.tsx
│ │ ├── multi-step-loader.tsx
│ │ ├── use-toast.ts
│ │ ├── typewriter-effect.tsx
│ │ ├── toast.tsx
│ │ ├── floating-dock.tsx
│ │ ├── background-beams-with-collision.tsx
│ │ ├── placeholders-and-vanish-input.tsx
│ │ └── background-beams.tsx
│ ├── Home
│ │ ├── FAQ.tsx
│ │ ├── Footer.tsx
│ │ ├── Header.tsx
│ │ ├── Features.tsx
│ │ ├── FloatingDock.tsx
│ │ ├── Hero.tsx
│ │ └── Feedback.tsx
│ └── Todo
│ │ ├── AddTodo.tsx
│ │ ├── TasksList.tsx
│ │ └── Task.tsx
└── data
│ └── index.ts
├── next.config.mjs
├── postcss.config.mjs
├── .vscode
└── cspell.json
├── .gitignore
├── tsconfig.json
├── LICENSE
├── package.json
├── tailwind.config.ts
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
└── README.md
/requirements.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/agent/ai_researcher/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/agent/.gitignore:
--------------------------------------------------------------------------------
1 | venv/
2 | __pycache__/
3 | *.pyc
4 | .env
5 | .vercel
6 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/core-web-vitals", "next/typescript"]
3 | }
4 |
--------------------------------------------------------------------------------
/public/cv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/cv.png
--------------------------------------------------------------------------------
/public/dp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/dp.jpg
--------------------------------------------------------------------------------
/structure.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/structure.txt
--------------------------------------------------------------------------------
/public/robot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/robot.png
--------------------------------------------------------------------------------
/public/chat-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/chat-app.png
--------------------------------------------------------------------------------
/public/reading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/reading.png
--------------------------------------------------------------------------------
/public/workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/workflow.png
--------------------------------------------------------------------------------
/public/youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/youtube.png
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/src/app/favicon.ico
--------------------------------------------------------------------------------
/public/accounting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/accounting.png
--------------------------------------------------------------------------------
/public/event-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/event-list.png
--------------------------------------------------------------------------------
/public/frequency.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/frequency.png
--------------------------------------------------------------------------------
/public/leadership.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/leadership.png
--------------------------------------------------------------------------------
/public/product-release.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/public/product-release.png
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/demo.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/demo.cpython-312.pyc
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/agent.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/agent.cpython-312.pyc
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/model.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/model.cpython-312.pyc
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/search.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/search.cpython-312.pyc
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/state.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/state.cpython-312.pyc
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/steps.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/steps.cpython-312.pyc
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/__init__.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/__init__.cpython-312.pyc
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/extract.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/extract.cpython-312.pyc
--------------------------------------------------------------------------------
/agent/ai_researcher/__pycache__/summarize.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkashJana18/copilotmate/HEAD/agent/ai_researcher/__pycache__/summarize.cpython-312.pyc
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/agent/langgraph.json:
--------------------------------------------------------------------------------
1 | {
2 | "python_version": "3.12",
3 | "dockerfile_lines": [],
4 | "dependencies": ["."],
5 | "graphs": {
6 | "ai_researcher": "./ai_researcher/agent.py:graph"
7 | },
8 | "env": ".env"
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Spreadsheet/type.ts:
--------------------------------------------------------------------------------
1 | export interface Cell {
2 | value: string;
3 | }
4 |
5 | export type SpreadsheetRow = Cell[];
6 |
7 | export interface SpreadsheetData {
8 | title: string;
9 | rows: SpreadsheetRow[];
10 | }
--------------------------------------------------------------------------------
/src/app/loading.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { MultiStepLoader as Loader } from "@/components/ui/multi-step-loader";
3 | import { loadingStates } from "@/data";
4 |
5 | export default function Loading() {
6 | return(
7 |
8 | )
9 | }
--------------------------------------------------------------------------------
/src/components/StudyBuddy/AnswerMarkdown.tsx:
--------------------------------------------------------------------------------
1 | import Markdown from "react-markdown";
2 |
3 | export function AnswerMarkdown({ markdown }: { markdown: string }) {
4 | return (
5 |
6 | {markdown}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/cspell.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2",
3 | "language": "en",
4 | "words": [
5 | "langgraph",
6 | "langchain",
7 | "perplexity",
8 | "ainvoke",
9 | "pydantic",
10 | "tavily",
11 | "copilotkit",
12 | "fastapi",
13 | "uvicorn",
14 | "checkpointer"
15 | ]
16 | }
--------------------------------------------------------------------------------
/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/src/lib/tasks.types.ts:
--------------------------------------------------------------------------------
1 | export type Task = {
2 | id: number;
3 | title: string;
4 | status: TaskStatus;
5 | priority: TaskPriority; // Add priority field
6 | };
7 |
8 | export enum TaskStatus {
9 | todo = "todo",
10 | completed = "completed",
11 | }
12 |
13 | export enum TaskPriority {
14 | low = "low",
15 | medium = "medium",
16 | high = "high",
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 |
2 | import FAQ from "@/components/Home/Faq";
3 | import Features from "@/components/Home/Features";
4 | import FeedbackSection from "@/components/Home/Feedback";
5 | import Hero from "@/components/Home/Hero";
6 |
7 |
8 | export default function Home() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/src/app/study-buddy/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ResearchWrapper } from "@/components/StudyBuddy/ResearchWrapper";
4 | import { ResearchProvider } from "@/lib/research-provider";
5 | import { CopilotKit } from "@copilotkit/react-core";
6 |
7 | export default function Home() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/agent/ai_researcher/model.py:
--------------------------------------------------------------------------------
1 | """
2 | This module provides a function to get a model based on the configuration.
3 | """
4 | import os
5 | import getpass
6 | from langchain_groq import ChatGroq
7 |
8 |
9 | def get_model():
10 | """
11 | Get a model based on the environment variable.
12 | """
13 |
14 | if "GROQ_API_KEY" not in os.environ:
15 | os.environ["GROQ_API_KEY"] = getpass.getpass("Enter your Groq API key: ")
16 | model = ChatGroq(
17 | model="llama3-groq-8b-8192-tool-use-preview",
18 | temperature=0,
19 | )
20 |
21 | if model:
22 | return model
23 | raise ValueError("Invalid model specified")
24 |
--------------------------------------------------------------------------------
/src/lib/default-tasks.ts:
--------------------------------------------------------------------------------
1 | import { Task, TaskStatus } from "./tasks.types";
2 |
3 | export const defaultTasks: Task[] = [
4 | {
5 | id: 1,
6 | title: "Vote this project on Quira.",
7 | status: TaskStatus.todo,
8 | },
9 | {
10 | id: 2,
11 | title: "Solve 3 DSA questions",
12 | status: TaskStatus.completed,
13 | },
14 | {
15 | id: 3,
16 | title: "Study for upcoming test.",
17 | status: TaskStatus.todo,
18 | },
19 | {
20 | id: 4,
21 | title: "Prepare youtube video for quira submission.",
22 | status: TaskStatus.todo,
23 | },
24 | {
25 | id: 5,
26 | title: "Give a star on this repo.",
27 | status: TaskStatus.todo,
28 | },
29 | ];
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Spreadsheet/canonicalSpreadsheetData.ts:
--------------------------------------------------------------------------------
1 | import { SpreadsheetRow } from "./type";
2 |
3 | export interface RowLike {
4 | cells: CellLike[] | undefined;
5 | }
6 |
7 | export interface CellLike {
8 | value: string;
9 | }
10 |
11 | export function canonicalSpreadsheetData(
12 | rows: RowLike[] | undefined
13 | ): SpreadsheetRow[] {
14 | const canonicalRows: SpreadsheetRow[] = [];
15 |
16 | for (const row of rows || []) {
17 | const canonicalRow: SpreadsheetRow = [];
18 | for (const cell of row.cells || []) {
19 | canonicalRow.push({value: cell.value});
20 | }
21 | canonicalRows.push(canonicalRow);
22 | }
23 |
24 | return canonicalRows;
25 | }
--------------------------------------------------------------------------------
/agent/ai_researcher/demo.py:
--------------------------------------------------------------------------------
1 | """Demo"""
2 |
3 | from dotenv import load_dotenv
4 | load_dotenv()
5 |
6 | from fastapi import FastAPI
7 | import uvicorn
8 | from copilotkit.integrations.fastapi import add_fastapi_endpoint
9 | from copilotkit import CopilotKitSDK, LangGraphAgent
10 | from ai_researcher.agent import graph
11 |
12 | app = FastAPI()
13 | sdk = CopilotKitSDK(
14 | agents=[
15 | LangGraphAgent(
16 | name="studybuddy_agent",
17 | description="Study Search agent.",
18 | agent=graph,
19 | )
20 | ],
21 | )
22 |
23 | add_fastapi_endpoint(app, sdk, "/copilotkit")
24 |
25 | def main():
26 | """Run the uvicorn server."""
27 | uvicorn.run("ai_researcher.demo:app", host="127.0.0.1", port=8000, reload=True)
28 |
--------------------------------------------------------------------------------
/agent/ai_researcher/state.py:
--------------------------------------------------------------------------------
1 | """
2 | This is the state definition for the AI.
3 | It defines the state of the agent and the state of the conversation.
4 | """
5 |
6 | from typing import List, TypedDict, Optional
7 | from langgraph.graph import MessagesState
8 |
9 | class Step(TypedDict):
10 | """
11 | Represents a step taken in the research process.
12 | """
13 | id: str
14 | description: str
15 | status: str
16 | type: str
17 | description: str
18 | search_result: Optional[str]
19 | result: Optional[str]
20 | updates: Optional[List[str]]
21 |
22 | class AgentState(MessagesState):
23 | """
24 | This is the state of the agent.
25 | It is a subclass of the MessagesState class from langgraph.
26 | """
27 | steps: List[Step]
28 | answer: Optional[str]
29 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/app/api/copilotkit/route.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CopilotRuntime,
3 | GroqAdapter,
4 | copilotRuntimeNextJSAppRouterEndpoint,
5 | } from "@copilotkit/runtime";
6 | import { NextRequest } from "next/server";
7 | import { Groq } from "groq-sdk";
8 |
9 | const BASE_URL = "http://127.0.0.1:8000";
10 | const groq = new Groq({ apiKey: process.env["GROQ_API_KEY"] });
11 | const serviceAdapter = new GroqAdapter({
12 | groq,
13 | model: "llama3-groq-8b-8192-tool-use-preview",
14 | });
15 |
16 | const runtime = new CopilotRuntime({
17 | remoteActions: [
18 | {
19 | url: `${BASE_URL}/copilotkit`,
20 | },
21 | ],
22 | });
23 |
24 | export const POST = async (req: NextRequest) => {
25 | const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
26 | runtime,
27 | serviceAdapter,
28 | endpoint: "/api/copilotkit",
29 | });
30 |
31 | return handleRequest(req);
32 | };
33 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/src/components/StudyBuddy/SkeletonLoader.tsx:
--------------------------------------------------------------------------------
1 | import { Skeleton } from "@/components/ui/skeleton";
2 |
3 | export function SkeletonLoader() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
--------------------------------------------------------------------------------
/src/app/chat/instructions.ts:
--------------------------------------------------------------------------------
1 | export const INSTRUCTIONS = `When a user asks a question, analyze it to understand the subject and provide a clear, accurate answer relevant to the study topic. Make sure to focus solely on educational content and avoid giving personal advice or unrelated information.
2 | What is copilotmate?
3 | CopilotMate is a personal AI assistant designed to help users streamline their daily tasks, organize information, and enhance productivity. Built using the CopilotKit platform, it offers features like to-do list management, expense tracking, spreadsheet handling, and reminders, all integrated into an intuitive chatbot interface. Users can interact with CopilotMate using natural language to create tasks, track expenses, and manage projects effortlessly. It also includes a StudyBuddy feature for creating notes and quizzes, making it useful for both personal and professional tasks. With its sleek design, dark theme, and glass-effect UI, CopilotMate not only offers functionality but also a visually appealing user experience.
4 | `
5 |
--------------------------------------------------------------------------------
/agent/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "ai_researcher"
3 | version = "0.1.0"
4 | description = "AI Researcher Demo"
5 | authors = ["Markus Ecker "]
6 | license = "MIT"
7 |
8 | [project]
9 | name = "ai_researcher"
10 | version = "0.0.1"
11 | dependencies = [
12 | "langgraph",
13 | "langchain_core",
14 | "langchain_openai",
15 | "langchain",
16 | "openai",
17 | "langchain-community",
18 | "tavily-python",
19 | "copilotkit",
20 | "python-dotenv",
21 | "uvicorn",
22 | ]
23 |
24 | [build-system]
25 | requires = ["setuptools >= 61.0"]
26 | build-backend = "setuptools.build_meta"
27 |
28 | [tool.poetry.dependencies]
29 | python = "^3.12"
30 | langgraph = "^0.2.34"
31 | langchain-core = "^0.3.8"
32 | langchain-openai = "^0.2.1"
33 | langchain = "^0.3.1"
34 | openai = "^1.51.0"
35 | langchain-community = "^0.3.1"
36 | tavily-python = "^0.5.0"
37 | copilotkit = "0.1.18"
38 | python-dotenv = "^1.0.1"
39 | uvicorn = "^0.31.0"
40 | langchain-anthropic = "^0.2.1"
41 |
42 |
43 | [tool.poetry.scripts]
44 | demo = "ai_researcher.demo:main"
45 |
--------------------------------------------------------------------------------
/src/app/expensetracker/instructions.ts:
--------------------------------------------------------------------------------
1 | export const INSTRUCTIONS = `You assist the user with managing their expenses. The user can add new expenses with a name, amount, and date. When entering numbers for amounts, do not use commas (e.g., enter 1000 instead of 1,000). Dates should be in a recognizable format (e.g., DD-MM-YYYY).
2 | keep a track of todays date, when date is not mentioned use today's date.
3 | When asked to add random expenses do not repeat expenses.
4 |
5 | When tracking expenses:
6 |
7 | Make sure each expense has a unique name, amount, and date.
8 | Add functionality to calculate total expenses, average spending, or find specific transactions when requested.
9 | Ensure expenses can be sorted by date, name, or amount, if needed.
10 | Here are some basic commands:
11 |
12 | To add an expense: Provide a name, amount, and date of the transaction.
13 | To remove an expense: Specify the name or date of the expense you want to remove.
14 | To view the total: Summarize all added expenses.
15 | To view spending trends: Show the highest and lowest expense amounts, or group expenses by date.`
16 |
--------------------------------------------------------------------------------
/public/link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/Spreadsheet/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SpreadsheetData } from "./type";
3 |
4 | interface SidebarProps {
5 | spreadsheets: SpreadsheetData[];
6 | selectedSpreadsheetIndex: number;
7 | setSelectedSpreadsheetIndex: (index: number) => void;
8 | }
9 |
10 | const Sidebar = ({
11 | spreadsheets,
12 | selectedSpreadsheetIndex,
13 | setSelectedSpreadsheetIndex,
14 | }: SidebarProps) => {
15 | return (
16 |
17 |
18 | {spreadsheets.map((spreadsheet, index) => (
19 | setSelectedSpreadsheetIndex(index)}
27 | >
28 | {spreadsheet.title}
29 |
30 | ))}
31 |
32 |
33 | );
34 | };
35 |
36 | export default Sidebar;
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Akash Jana
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/Home/FAQ.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { faq } from "@/data";
4 | import { BackgroundGradient } from "../ui/background-gradient";
5 | import { TypewriterEffectSmooth } from "../ui/typewriter-effect";
6 | import { faqTitle } from "@/data";
7 |
8 | const FAQ = () => {
9 |
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | {faq.map((item) => (
17 |
18 |
19 |
20 | {item.id}
21 | {". "}
22 | {item.question}
23 |
24 | {item.answer}
25 |
26 |
27 | ))}
28 |
29 |
30 | );
31 | };
32 |
33 | export default FAQ;
34 |
--------------------------------------------------------------------------------
/src/components/StudyBuddy/ResearchWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { HomeView } from "./HomeView";
2 | import { ResultsView } from "./ResultsView";
3 | import { AnimatePresence } from "framer-motion";
4 | import { useResearchContext } from "@/lib/research-provider";
5 |
6 | export function ResearchWrapper() {
7 | const { researchQuery, setResearchInput } = useResearchContext();
8 |
9 | return (
10 | <>
11 |
12 |
13 | {researchQuery ? (
14 |
{
17 | setResearchInput("");
18 | }}
19 | mode="wait"
20 | >
21 |
22 |
23 | ) : (
24 |
25 |
26 |
27 | )}
28 |
29 |
30 | >
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/ui/meteors.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import React from "react";
3 |
4 | export const Meteors = ({
5 | number,
6 | className,
7 | }: {
8 | number?: number;
9 | className?: string;
10 | }) => {
11 | const meteors = new Array(number || 20).fill(true);
12 | return (
13 | <>
14 | {meteors.map((el, idx) => (
15 |
29 | ))}
30 | >
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "copilotmate",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@copilotkit/react-core": "^1.3.1",
13 | "@copilotkit/react-textarea": "^1.3.2",
14 | "@copilotkit/react-ui": "^1.3.1",
15 | "@heroicons/react": "^2.1.5",
16 | "@radix-ui/react-slot": "^1.1.0",
17 | "@tabler/icons-react": "^3.17.0",
18 | "@types/react-scroll": "^1.8.10",
19 | "class-variance-authority": "^0.7.0",
20 | "clsx": "^2.1.1",
21 | "framer-motion": "^11.7.0",
22 | "groq-sdk": "^0.5.0",
23 | "lucide-react": "^0.446.0",
24 | "next": "14.2.13",
25 | "radix-ui": "^1.0.1",
26 | "react": "^18",
27 | "react-dom": "^18",
28 | "react-spreadsheet": "^0.9.5",
29 | "tailwind-merge": "^2.5.2"
30 | },
31 | "devDependencies": {
32 | "@types/node": "^20",
33 | "@types/react": "^18",
34 | "@types/react-dom": "^18",
35 | "eslint": "^8",
36 | "eslint-config-next": "14.2.13",
37 | "postcss": "^8",
38 | "tailwindcss": "^3.4.1",
39 | "typescript": "^5"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Home/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { socialMedia } from "@/data";
3 |
4 | const Footer = () => {
5 |
6 | return (
7 |
33 | );
34 | };
35 |
36 | export default Footer;
37 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const {
4 | default: flattenColorPalette,
5 | } = require("tailwindcss/lib/util/flattenColorPalette");
6 |
7 | const config: Config = {
8 | content: [
9 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
10 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
11 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
12 | ],
13 | theme: {
14 | extend: {
15 | colors: {
16 | background: "var(--background)",
17 | foreground: "var(--foreground)",
18 | },
19 | animation: {
20 | spotlight: "spotlight 2s ease .75s 1 forwards",
21 | },
22 | keyframes: {
23 | spotlight: {
24 | "0%": {
25 | opacity: "0",
26 | transform: "translate(-72%, -62%) scale(0.5)",
27 | },
28 | "100%": {
29 | opacity: "1",
30 | transform: "translate(-50%,-40%) scale(1)",
31 | },
32 | },
33 | },
34 | },
35 | },
36 | plugins: [addVariablesForColors],
37 | };
38 | function addVariablesForColors({ addBase, theme }: any) {
39 | let allColors = flattenColorPalette(theme("colors"));
40 | let newVars = Object.fromEntries(
41 | Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
42 | );
43 |
44 | addBase({
45 | ":root": newVars,
46 | });
47 | }
48 | export default config;
49 |
--------------------------------------------------------------------------------
/src/components/Todo/AddTodo.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/ui/input";
2 | import { Button } from "@/components/ui/button";
3 | import { useState } from "react";
4 | import { useTasks } from "@/lib/hooks/use-tasks";
5 |
6 | export function AddTodo() {
7 | const [title, setTitle] = useState("");
8 | const { addTask } = useTasks();
9 |
10 | const handleAddTask = () => {
11 | addTask(title);
12 | setTitle("");
13 | };
14 |
15 | return (
16 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/app/study-buddy/instructions.ts:
--------------------------------------------------------------------------------
1 | export const INSTRUCTIONS = `
2 | You assist the user with study tools in CopilotMate. Your main features include a Pomodoro Timer and Flashcards.
3 |
4 | When using the Pomodoro Timer:
5 | - The timer starts at 25 minutes (1500 seconds) by default.
6 | - You can pause, reset, and toggle the timer on and off.
7 | - Notify the user when the time is up by alerting them with a message.
8 | - Allow the user to track elapsed time in the format of MM:SS (e.g., 25:00 for 25 minutes).
9 |
10 | When using Flashcards:
11 | - Present a set of flashcards with questions and answers.
12 | - Allow the user to flip cards to reveal answers.
13 | - Implement functionality to navigate through flashcards (next card).
14 | - Provide an option to expand the card view for easier reading.
15 | - Support toggling between flipped and unflipped states.
16 |
17 | General Guidelines:
18 | - Use simple, clear language for prompts and alerts.
19 | - Maintain focus on enhancing the user's study experience through productivity tools.
20 | - Keep interactions smooth and responsive, ensuring that the user can easily switch between the timer and flashcards.
21 | - Be intuitive in recognizing when to prompt the user for the next action, such as starting the timer or flipping the flashcard.
22 |
23 | Available Functions:
24 | - For Pomodoro Timer: START, PAUSE, RESET.
25 | - For Flashcards: NEXT CARD, FLIP, EXPAND.
26 | `;
27 |
--------------------------------------------------------------------------------
/src/components/Todo/TasksList.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { AddTodo } from "@/components/Todo/AddTodo";
4 | import { Task } from "@/components/Todo/Task";
5 | import { useTasks } from "@/lib/hooks/use-tasks";
6 | import { TaskStatus } from "@/lib/tasks.types";
7 | import { AnimatePresence } from "framer-motion";
8 |
9 | export function TasksList() {
10 | const { tasks } = useTasks();
11 | return (
12 |
13 |
14 |
15 |
16 | To-Do List
17 |
18 |
19 |
20 |
21 | {tasks
22 | .sort((a, b) => {
23 | if (a.status === b.status) {
24 | return a.id - b.id;
25 | }
26 | return a.status === TaskStatus.todo ? -1 : 1;
27 | })
28 | .map((task, index) => (
29 |
30 |
31 |
32 | ))}
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/src/app/todo/page.tsx:
--------------------------------------------------------------------------------
1 | import { TasksList } from "@/components/Todo/TasksList";
2 | import { TasksProvider } from "@/lib/hooks/use-tasks";
3 | import { CopilotKitCSSProperties, CopilotPopup } from "@copilotkit/react-ui";
4 | import "@copilotkit/react-ui/styles.css";
5 | import { Suspense } from "react";
6 |
7 | const Todo = () => {
8 | return (
9 | <>
10 |
11 |
12 |
13 |
14 |
26 |
33 |
34 |
35 | >
36 | );
37 | };
38 |
39 | export default Todo;
40 |
--------------------------------------------------------------------------------
/public/twit.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Montserrat } from "next/font/google";
3 | import "./globals.css";
4 | import { FloatingDockDemo } from "@/components/Home/FloatingDock";
5 | import Footer from "@/components/Home/Footer";
6 | import Header from "@/components/Home/Header";
7 | import { CopilotKit } from "@copilotkit/react-core";
8 | import { Spotlight } from "@/components/ui/Spotlight";
9 |
10 | export const metadata: Metadata = {
11 | title: "CopilotMate",
12 | description:
13 | "Your AI-powered companion, seamlessly automating tasks and enhancing productivity with intuitive actions and smart assistance.",
14 | };
15 |
16 | const montserrat = Montserrat({
17 | subsets: ["latin"],
18 | display: "swap",
19 | });
20 |
21 | export default function RootLayout({
22 | children,
23 | }: Readonly<{
24 | children: React.ReactNode;
25 | }>) {
26 | return (
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 | {children}
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/Home/Header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { PlaceholdersAndVanishInput } from "@/components/ui/placeholders-and-vanish-input";
4 | import { placeholders } from "@/data";
5 | import Link from "next/link";
6 |
7 | const Header = () => {
8 | const handleChange = (e: React.ChangeEvent) => {};
9 | const onSubmit = (e: React.FormEvent) => {
10 | e.preventDefault();
11 | console.log("submitted");
12 | };
13 | return (
14 |
41 | );
42 | };
43 |
44 | export default Header;
45 |
--------------------------------------------------------------------------------
/src/components/ui/Spotlight.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { cn } from "@/lib/utils";
3 |
4 | type SpotlightProps = {
5 | className?: string;
6 | fill?: string;
7 | };
8 |
9 | export const Spotlight = ({ className, fill }: SpotlightProps) => {
10 | return (
11 |
20 |
21 |
30 |
31 |
32 |
41 |
42 |
48 |
52 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/components/StudyBuddy/Progress.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import { Check, CheckIcon, LoaderCircle, SearchCheck } from "lucide-react";
3 |
4 | export function Progress({
5 | steps,
6 | }: {
7 | steps: {
8 | description: string;
9 | status: "complete" | "done";
10 | updates: string[];
11 | }[];
12 | }) {
13 | if (steps.length === 0) {
14 | return null;
15 | }
16 |
17 | return (
18 |
19 |
20 | {steps.map((step, index) => (
21 |
22 |
23 |
24 | {step.status === "complete" ? (
25 |
26 | ) : (
27 |
28 | )}
29 |
30 | {index < steps.length - 1 && (
31 |
34 | )}
35 |
36 |
37 |
{step.description}
38 |
39 | {step.updates?.length > 0 && step.updates[step.updates.length - 1]}
40 |
41 |
42 |
43 | ))}
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/Home/Features.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { Button } from "@/components/ui/moving-border";
4 | import { copilotMateFeatures } from "@/data";
5 | import { TypewriterEffectSmooth } from "../ui/typewriter-effect";
6 | import { featureTitle } from "@/data";
7 |
8 | const Features = () => {
9 |
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | {copilotMateFeatures.map((feature, index) => (
18 |
24 |
25 |
30 |
31 |
32 | {feature.title}
33 |
34 |
35 | {feature.description}
36 |
37 |
38 |
39 |
40 | ))}
41 |
42 |
43 | );
44 | };
45 |
46 | export default Features;
47 |
--------------------------------------------------------------------------------
/agent/ai_researcher/agent.py:
--------------------------------------------------------------------------------
1 | """
2 | This is the main entry point for the AI.
3 | It defines the workflow graph and the entry point for the agent.
4 | """
5 | # pylint: disable=line-too-long, unused-import
6 | import json
7 |
8 | from langgraph.graph import StateGraph, END
9 | from langgraph.checkpoint.memory import MemorySaver
10 |
11 | from ai_researcher.state import AgentState
12 | from ai_researcher.steps import steps_node
13 | from ai_researcher.search import search_node
14 | from ai_researcher.summarize import summarize_node
15 | from ai_researcher.extract import extract_node
16 |
17 | def route(state):
18 | """Route to research nodes."""
19 | if not state.get("steps", None):
20 | return END
21 |
22 | current_step = next((step for step in state["steps"] if step["status"] == "pending"), None)
23 |
24 | if not current_step:
25 | return "summarize_node"
26 |
27 | if current_step["type"] == "search":
28 | return "search_node"
29 |
30 | raise ValueError(f"Unknown step type: {current_step['type']}")
31 |
32 | # Define a new graph
33 | workflow = StateGraph(AgentState)
34 | workflow.add_node("steps_node", steps_node)
35 | workflow.add_node("search_node", search_node)
36 | workflow.add_node("summarize_node", summarize_node)
37 | workflow.add_node("extract_node", extract_node)
38 | # Chatbot
39 | workflow.set_entry_point("steps_node")
40 |
41 | workflow.add_conditional_edges(
42 | "steps_node",
43 | route,
44 | ["summarize_node", "search_node", END]
45 | )
46 |
47 | workflow.add_edge("search_node", "extract_node")
48 |
49 | workflow.add_conditional_edges(
50 | "extract_node",
51 | route,
52 | ["summarize_node", "search_node"]
53 | )
54 |
55 | workflow.add_edge("summarize_node", END)
56 |
57 | memory = MemorySaver()
58 | graph = workflow.compile(checkpointer=memory)
59 |
--------------------------------------------------------------------------------
/src/lib/research-provider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useState, ReactNode, useEffect } from "react";
2 |
3 | type ResearchContextType = {
4 | researchQuery: string;
5 | setResearchQuery: (query: string) => void;
6 | researchInput: string;
7 | setResearchInput: (input: string) => void;
8 | isLoading: boolean;
9 | setIsLoading: (loading: boolean) => void;
10 | researchResult: ResearchResult | null;
11 | setResearchResult: (result: ResearchResult) => void;
12 | };
13 |
14 | type ResearchResult = {
15 | answer: string;
16 | sources: string[];
17 | }
18 |
19 | const ResearchContext = createContext(undefined);
20 |
21 | export const ResearchProvider = ({ children }: { children: ReactNode }) => {
22 | const [researchQuery, setResearchQuery] = useState("");
23 | const [researchInput, setResearchInput] = useState("");
24 | const [researchResult, setResearchResult] = useState(null);
25 | const [isLoading, setIsLoading] = useState(false);
26 |
27 | useEffect(() => {
28 | if (!researchQuery) {
29 | setResearchResult(null);
30 | setResearchInput("");
31 | }
32 | }, [researchQuery, researchResult]);
33 |
34 | return (
35 |
47 | {children}
48 |
49 | );
50 | };
51 |
52 | export const useResearchContext = () => {
53 | const context = useContext(ResearchContext);
54 | if (context === undefined) {
55 | throw new Error("useResearchContext must be used within a ResearchProvider");
56 | }
57 | return context;
58 | };
--------------------------------------------------------------------------------
/src/components/Spreadsheet/PreviewSpreadsheetChanges.tsx:
--------------------------------------------------------------------------------
1 | import { CheckCircleIcon } from '@heroicons/react/20/solid'
2 | import { SpreadsheetRow } from './type';
3 | import { useState } from 'react';
4 | import Spreadsheet from 'react-spreadsheet';
5 |
6 |
7 | export interface PreviewSpreadsheetChanges {
8 | preCommitTitle: string;
9 | postCommitTitle: string;
10 | newRows: SpreadsheetRow[];
11 | commit: (rows: SpreadsheetRow[]) => void;
12 | }
13 |
14 | export function PreviewSpreadsheetChanges(props: PreviewSpreadsheetChanges) {
15 | const [changesCommitted, setChangesCommitted] = useState(false);
16 |
17 | const commitChangesButton = () => {
18 | return (
19 | {
22 | props.commit(props.newRows);
23 | setChangesCommitted(true);
24 | }}
25 | >
26 | {props.preCommitTitle}
27 |
28 | );
29 | }
30 |
31 | const changesCommittedButtonPlaceholder = () => {
32 | return (
33 |
37 | {props.postCommitTitle}
38 |
39 |
40 | );
41 | }
42 |
43 | return (
44 |
45 |
48 |
49 |
50 | {changesCommitted ? changesCommittedButtonPlaceholder() : commitChangesButton() }
51 |
52 |
53 |
54 | );
55 | }
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/agent/ai_researcher/search.py:
--------------------------------------------------------------------------------
1 | """
2 | The search node is responsible for searching the internet for information.
3 | """
4 | import json
5 | from datetime import datetime
6 | from langchain_core.messages import HumanMessage
7 | from langchain_core.runnables import RunnableConfig
8 | from langchain_community.tools import TavilySearchResults
9 | from ai_researcher.state import AgentState
10 | from ai_researcher.model import get_model
11 | async def search_node(state: AgentState, config: RunnableConfig):
12 | """
13 | The search node is responsible for searching the internet for information.
14 | """
15 | tavily_tool = TavilySearchResults(
16 | max_results=10,
17 | search_depth="advanced",
18 | include_answer=True,
19 | include_raw_content=True,
20 | include_images=True,
21 | )
22 |
23 | current_step = next((step for step in state["steps"] if step["status"] == "pending"), None)
24 |
25 | if current_step is None:
26 | raise ValueError("No step to search for")
27 |
28 | if current_step["type"] != "search":
29 | raise ValueError("Current step is not a search step")
30 |
31 | instructions = f"""
32 | This is a step in a series of steps that are being executed to answer the user's query.
33 | These are all of the steps: {json.dumps(state["steps"])}
34 |
35 | You are responsible for carrying out the step: {json.dumps(current_step)}
36 |
37 | The current date is {datetime.now().strftime("%Y-%m-%d")}.
38 |
39 | This is what you need to search for, please come up with a good search query: {current_step["description"]}
40 | """
41 | model = get_model().bind_tools(
42 | [tavily_tool],
43 | tool_choice=tavily_tool.name
44 | )
45 |
46 | response = await model.ainvoke([
47 | HumanMessage(
48 | content=instructions
49 | )
50 | ], config)
51 |
52 | tool_msg = tavily_tool.invoke(response.tool_calls[0])
53 |
54 | current_step["search_result"] = json.loads(tool_msg.content)
55 | current_step["updates"] = [*current_step["updates"],"Extracting information..."]
56 |
57 | return state
58 |
--------------------------------------------------------------------------------
/src/app/chat/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import "@copilotkit/react-ui/styles.css";
3 | import {
4 | CopilotChat,
5 | CopilotKitCSSProperties,
6 | useCopilotChatSuggestions,
7 | } from "@copilotkit/react-ui";
8 | import { INSTRUCTIONS } from "./instructions";
9 |
10 | const Page = () => {
11 | useCopilotChatSuggestions({
12 | instructions: `Suggest questions what can you do?`,
13 | });
14 |
15 | return (
16 | <>
17 |
18 |
19 | Chatbox
20 |
21 |
38 |
46 |
47 |
48 | >
49 | );
50 | };
51 |
52 | export default Page;
53 |
--------------------------------------------------------------------------------
/agent/ai_researcher/summarize.py:
--------------------------------------------------------------------------------
1 | """
2 | The summarize node is responsible for summarizing the information.
3 | """
4 |
5 | import json
6 | from langchain_core.messages import HumanMessage
7 | from langchain_core.runnables import RunnableConfig
8 | from copilotkit.langchain import copilotkit_customize_config
9 | from pydantic import BaseModel, Field
10 | from ai_researcher.state import AgentState
11 | from ai_researcher.model import get_model
12 |
13 | class Reference(BaseModel):
14 | """Model for a reference"""
15 |
16 | title: str = Field(description="The title of the reference.")
17 | url: str = Field(description="The url of the reference.")
18 |
19 | class SummarizeTool(BaseModel):
20 | """
21 | Summarize the final result. Make sure that the summary is complete and
22 | includes all relevant information and reference links.
23 | """
24 |
25 | markdown: str = Field(
26 | description="""
27 | The markdown formatted summary of the final result.
28 | If you add any headings, make sure to start at the top level (#).
29 | """
30 | )
31 | references: list[Reference] = Field(description="A list of references.")
32 |
33 | async def summarize_node(state: AgentState, config: RunnableConfig):
34 | """
35 | The summarize node is responsible for summarizing the information.
36 | """
37 |
38 | config = copilotkit_customize_config(
39 | config,
40 | emit_intermediate_state=[
41 | {
42 | "state_key": "answer",
43 | "tool": "SummarizeTool",
44 | }
45 | ]
46 | )
47 |
48 | system_message = f"""
49 | The system has performed a series of steps to answer the user's query.
50 | These are all of the steps: {json.dumps(state["steps"])}
51 |
52 | Please summarize the final result and include all relevant information and reference links.
53 | """
54 |
55 | response = await get_model().bind_tools(
56 | [SummarizeTool],
57 | tool_choice="SummarizeTool"
58 | ).ainvoke([
59 | HumanMessage(
60 | content=system_message
61 | ),
62 | ], config)
63 |
64 | return {
65 | "answer": response.tool_calls[0]["args"],
66 | }
67 |
--------------------------------------------------------------------------------
/agent/ai_researcher/extract.py:
--------------------------------------------------------------------------------
1 | """
2 | The extract node is responsible for extracting information from a tavily search.
3 | """
4 | import json
5 |
6 | from langchain_core.messages import HumanMessage
7 | from langchain_core.runnables import RunnableConfig
8 | from ai_researcher.state import AgentState
9 | from ai_researcher.model import get_model
10 |
11 | async def extract_node(state: AgentState, config: RunnableConfig):
12 | """
13 | The extract node is responsible for extracting information from a tavily search.
14 | """
15 |
16 | current_step = next((step for step in state["steps"] if step["status"] == "pending"), None)
17 |
18 | if current_step is None:
19 | raise ValueError("No current step")
20 |
21 | if current_step["type"] != "search":
22 | raise ValueError("Current step is not of type search")
23 |
24 | system_message = f"""
25 | This step was just executed: {json.dumps(current_step)}
26 |
27 | This is the result of the search:
28 |
29 | Please summarize ONLY the result of the search and include all relevant information from the search and reference links.
30 | DO NOT INCLUDE ANY EXTRA INFORMATION. ALL OF THE INFORMATION YOU ARE LOOKING FOR IS IN THE SEARCH RESULTS.
31 |
32 | DO NOT answer the user's query yet. Just summarize the search results.
33 |
34 | Use markdown formatting and put the references inline and the links at the end.
35 | Like this:
36 | This is a sentence with a reference to a source [source 1][1] and another reference [source 2][2].
37 | [1]: http://example.com/source1 "Title of Source 1"
38 | [2]: http://example.com/source2 "Title of Source 2"
39 | """
40 |
41 | response = await get_model().ainvoke([
42 | state["messages"][0],
43 | HumanMessage(
44 | content=system_message
45 | )
46 | ], config)
47 |
48 | current_step["result"] = response.content
49 | current_step["search_result"] = None
50 | current_step["status"] = "complete"
51 | current_step["updates"] = [*current_step["updates"], "Done."]
52 |
53 | next_step = next((step for step in state["steps"] if step["status"] == "pending"), None)
54 | if next_step:
55 | next_step["updates"] = ["Searching the web..."]
56 |
57 | return state
58 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
5 | import { ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Accordion = AccordionPrimitive.Root
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ))
21 | AccordionItem.displayName = "AccordionItem"
22 |
23 | const AccordionTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef & {
26 | hideChevron?: boolean;
27 | }
28 | >(({ className, hideChevron = false, children, ...props }, ref) => (
29 |
30 | svg]:rotate-180",
34 | className
35 | )}
36 | {...props}
37 | >
38 | {children}
39 | {
40 | !hideChevron && (
41 |
42 | )
43 | }
44 |
45 |
46 | ))
47 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
48 |
49 | const AccordionContent = React.forwardRef<
50 | React.ElementRef,
51 | React.ComponentPropsWithoutRef
52 | >(({ className, children, ...props }, ref) => (
53 |
58 | {children}
59 |
60 | ))
61 |
62 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
63 |
64 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
65 |
--------------------------------------------------------------------------------
/src/components/Home/FloatingDock.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FloatingDock } from "@/components/ui/floating-dock";
3 | import {
4 | IconBook,
5 | IconBrandGithub,
6 | IconClipboardList,
7 | IconFileSpreadsheet,
8 | IconHome,
9 | IconReportMoney,
10 | IconTerminal2,
11 | IconTimeDurationOff,
12 | } from "@tabler/icons-react";
13 |
14 | export function FloatingDockDemo() {
15 | const links = [
16 | {
17 | title: "Home",
18 | icon: (
19 |
20 | ),
21 | href: "/",
22 | },
23 | {
24 | title: "Chatbot",
25 | icon: (
26 |
27 | ),
28 | href: "/chat",
29 | },
30 | {
31 | title: "Study Buddy",
32 | icon: (
33 |
34 | ),
35 | href: "/study-buddy",
36 | },
37 |
38 | {
39 | title: "Expense Tracker",
40 | icon: (
41 |
42 | ),
43 | href: "/expensetracker",
44 | },
45 | {
46 | title: "Spreadsheet",
47 | icon: (
48 |
49 | ),
50 | href: "/spreadsheet",
51 | },
52 | {
53 | title: "Todo list",
54 | icon: (
55 |
56 | ),
57 | href: "/todo",
58 | },
59 |
60 |
61 |
62 | {
63 | title: "Vote",
64 | icon: (
65 |
66 | ),
67 | href: "https://quira.sh/repo/AkashJana18-copilotmate-863038853",
68 | },
69 | {
70 | title: "GitHub",
71 | icon: (
72 |
73 | ),
74 | href: "https://github.com/AkashJana18/copilotmate",
75 | },
76 |
77 | ];
78 | return (
79 |
80 |
84 |
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/Todo/Task.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox } from "@/components/ui/checkbox";
2 | import { Label } from "@/components/ui/label";
3 | import { Button } from "@/components/ui/button";
4 | import { TrashIcon } from "lucide-react";
5 | import { cn } from "@/lib/utils";
6 | import { useTasks } from "@/lib/hooks/use-tasks";
7 | import { motion } from "framer-motion";
8 | import { TaskStatus, TaskPriority, type Task } from "@/lib/tasks.types";
9 |
10 | export function Task({
11 | task: { id, title, status, priority },
12 | }: {
13 | task: Task;
14 | }) {
15 | const { setTaskStatus, deleteTask, setTaskPriority } = useTasks(); // Assuming setTaskPriority is available
16 |
17 | // Priority options
18 | const priorityOptions = [
19 | { label: "Low", value: TaskPriority.low },
20 | { label: "Medium", value: TaskPriority.medium },
21 | { label: "High", value: TaskPriority.high },
22 | ];
23 |
24 | return (
25 |
32 |
35 | setTaskStatus(
36 | id,
37 | status === TaskStatus.completed ? TaskStatus.todo : TaskStatus.completed
38 | )
39 | }
40 | checked={status === TaskStatus.completed}
41 | className="ml-4"
42 | />
43 | TASK-{id}
44 |
51 | {title}
52 |
53 |
54 | {/* Priority Dropdown */}
55 | setTaskPriority(id, e.target.value as TaskPriority)}
58 | className={cn(
59 | "text-sm p-1 rounded-md bg-black",
60 | priority === TaskPriority.low && "text-green-500",
61 | priority === TaskPriority.medium && "text-yellow-500",
62 | priority === TaskPriority.high && "text-red-500"
63 | )}
64 | >
65 | {priorityOptions.map((option) => (
66 |
67 | {option.label}
68 |
69 | ))}
70 |
71 |
72 | deleteTask(id)}>
73 |
74 |
75 |
76 | Delete
77 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/app/spreadsheet/instructions.ts:
--------------------------------------------------------------------------------
1 | export const INSTRUCTIONS = `
2 | You assist the user with a spreadsheet. Basic formulas are permitted, for example \`=SUM(B1:B9)\`.
3 | When setting numbers, don't use commas. For example, use 1000 instead of 1,000.
4 | Don't use words like 'million' etc, just the numbers.
5 |
6 | When the user prompts you to create a new spreadsheet, focus on providing interesting numerical data and
7 | insert formulas when appropriate.
8 |
9 | If you have values that are a range, create a column for min and one for max.
10 |
11 | Available functions in formulas:
12 | ABS, ACOS, ACOSH, ACOT, ACOTH, ADDRESS, AND, ARABIC, AREAS, ASC, ASIN, ASINH, ATAN, ATAN2, ATANH, AVEDEV, AVERAGE, AVERAGEA, AVERAGEIF, BAHTTEXT, BASE, BESSELI, BESSELJ, BESSELK, BESSELY, BETA.DIST, BETA.INV, BIN2DEC, BIN2HEX, BIN2OCT, BINOM.DIST, BINOM.DIST.RANGE, BINOM.INV, BITAND, BITLSHIFT, BITOR,
13 | BITRSHIFT, BITXOR, CEILING, CEILING.MATH, CEILING.PRECISE, CHAR, CHISQ.DIST, CHISQ.DIST.RT, CHISQ.INV, CHISQ.INV.RT, CHISQ.TEST, CLEAN, CODE, COLUMN, COLUMNS, COMBIN, COMBINA, COMPLEX, CONCAT, CONCATENATE, CONFIDENCE.NORM, CONFIDENCE.T, CORREL, COS, COSH, COT, COTH, COUNT, COUNTIF, COVARIANCE.P,
14 | COVARIANCE.S, CSC, CSCH, DATE, DATEDIF, DATEVALUE, DAY, DAYS, DAYS360, DBCS, DEC2BIN, DEC2HEX, DEC2OCT, DECIMAL, DEGREES, DELTA, DEVSQ, DOLLAR, EDATE, ENCODEURL, EOMONTH, ERF, ERFC, ERROR.TYPE, EVEN, EXACT, EXP, EXPON.DIST, F.DIST, F.DIST.RT, F.INV, F.INV.RT, F.TEST, FACT, FACTDOUBLE, FALSE, FIND, FINDB,
15 | FISHER, FISHERINV, FIXED, FLOOR, FLOOR.MATH, FLOOR.PRECISE, FORECAST, FORECAST.LINEAR, FREQUENCY, GAMMA, GAMMA.DIST, GAMMA.INV, GAMMALN, GAMMALN.PRECISE, GAUSS, GCD, GEOMEAN, GESTEP, GROWTH, HARMEAN, HEX2BIN, HEX2DEC, HEX2OCT, HLOOKUP, HOUR, HYPGEOM.DIST, IF, IFERROR, IFNA, IFS, IMABS, IMAGINARY, IMARGUMENT,
16 | IMCONJUGATE, IMCOS, IMCOSH, IMCOT, IMCSC, IMCSCH, IMDIV, IMEXP, IMLN, IMLOG10, IMLOG2, IMPOWER, IMPRODUCT, IMREAL, IMSEC, IMSECH, IMSIN, IMSINH, IMSQRT, IMSUB, IMSUM, IMTAN, INDEX, INT, INTERCEPT, ISBLANK, ISERR, ISERROR, ISEVEN, ISLOGICAL, ISNA, ISNONTEXT, ISNUMBER, ISO.CEILING, ISOWEEKNUM, ISREF, ISTEXT,
17 | KURT, LCM, LEFT, LEFTB, LN, LOG, LOG10, LOGNORM.DIST, LOGNORM.INV, LOWER, MDETERM, MID, MIDB, MINUTE, MMULT, MOD, MONTH, MROUND, MULTINOMIAL, MUNIT, N, NA, NEGBINOM.DIST, NETWORKDAYS, NETWORKDAYS.INTL, NORM.DIST, NORM.INV, NORM.S.DIST, NORM.S.INV, NOT, NOW, NUMBERVALUE, OCT2BIN, OCT2DEC, OCT2HEX, ODD, OR,
18 | PHI, PI, POISSON.DIST, POWER, PRODUCT, PROPER, QUOTIENT, RADIANS, RAND, RANDBETWEEN, REPLACE, REPLACEB, REPT, RIGHT, RIGHTB, ROMAN, ROUND, ROUNDDOWN, ROUNDUP, ROW, ROWS, SEARCH, SEARCHB, SEC, SECH, SECOND, SERIESSUM, SIGN, SIN, SINH, SQRT, SQRTPI, STANDARDIZE, SUM, SUMIF, SUMPRODUCT, SUMSQ, SUMX2MY2,
19 | SUMX2PY2, SUMXMY2, T, T.DIST, T.DIST.2T, T.DIST.RT, T.INV, T.INV.2T, TAN, TANH, TEXT, TIME, TIMEVALUE, TODAY, TRANSPOSE, TRIM, TRUE, TRUNC, TYPE, UNICHAR, UNICODE, UPPER, VLOOKUP, WEBSERVICE, WEEKDAY, WEEKNUM, WEIBULL.DIST, WORKDAY, WORKDAY.INTL, XOR, YEAR, YEARFRAC
20 | `;
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to CopilotMate
2 |
3 | Thank you for your interest in contributing to CopilotMate! We welcome contributions from the community and appreciate your help in making this project better. Please follow the guidelines below to ensure a smooth collaboration.
4 |
5 | ## How to Contribute
6 |
7 | ### 1. Fork the Repository
8 |
9 | Start by forking the [CopilotMate repository](https://github.com/yourusername/copilotmate) on GitHub. This creates your own copy of the project that you can modify freely.
10 |
11 | ### 2. Clone Your Fork
12 |
13 | Clone your forked repository to your local machine:
14 |
15 | ```bash
16 | git clone https://github.com/yourusername/copilotmate.git
17 | ```
18 |
19 | Navigate into the project directory:
20 |
21 | ```bash
22 | cd copilotmate
23 | ```
24 |
25 | ### 3. Create a Branch
26 |
27 | Create a new branch for your feature or bug fix. It’s a good practice to name your branch descriptively:
28 |
29 | ```bash
30 | git checkout -b feature/your-feature-name
31 | ```
32 |
33 | ### 4. Make Your Changes
34 |
35 | Make the necessary changes to the codebase. Ensure that your code follows the project's coding conventions and style.
36 |
37 | ### 5. Test Your Changes
38 |
39 | Before committing your changes, make sure to run any existing tests and, if applicable, add new tests to cover your modifications. Ensure everything works as expected.
40 |
41 | ### 6. Commit Your Changes
42 |
43 | Commit your changes with a clear and descriptive commit message:
44 |
45 | ```bash
46 | git commit -m "Add feature: your-feature-name"
47 | ```
48 |
49 | ### 7. Push to Your Fork
50 |
51 | Push your changes to your forked repository:
52 |
53 | ```bash
54 | git push origin feature/your-feature-name
55 | ```
56 |
57 | ### 8. Create a Pull Request
58 |
59 | Navigate to the original repository and create a pull request (PR) from your branch. Provide a clear description of your changes, including any relevant context or links to related issues.
60 |
61 | ### 9. Address Feedback
62 |
63 | Be open to feedback and discussions regarding your pull request. You may need to make additional changes based on the project maintainers’ suggestions.
64 |
65 | ## Code of Conduct
66 |
67 | Please adhere to the [Code of Conduct](CODE_OF_CONDUCT.md) while contributing to this project. We aim to create a welcoming environment for all contributors.
68 |
69 | ## Issues and Feature Requests
70 |
71 | If you encounter any issues or have suggestions for new features, please open an issue in the repository. Be sure to provide detailed information about the problem or feature you want to discuss.
72 |
73 | ## Additional Resources
74 |
75 | - [GitHub Flow](https://guides.github.com/introduction/flow/)
76 | - [Understanding the GitHub Pull Request Process](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests)
77 |
78 | Thank you for contributing to CopilotMate! Your contributions help improve the project and support the community.
79 |
--------------------------------------------------------------------------------
/src/components/ui/background-gradient.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import React from "react";
3 | import { motion } from "framer-motion";
4 |
5 | export const BackgroundGradient = ({
6 | children,
7 | className,
8 | containerClassName,
9 | animate = true,
10 | }: {
11 | children?: React.ReactNode;
12 | className?: string;
13 | containerClassName?: string;
14 | animate?: boolean;
15 | }) => {
16 | const variants = {
17 | initial: {
18 | backgroundPosition: "0 50%",
19 | },
20 | animate: {
21 | backgroundPosition: ["0, 50%", "100% 50%", "0 50%"],
22 | },
23 | };
24 |
25 | // Function to generate a random duration between 3 and 10 seconds
26 | const getRandomDuration = () => Math.random() * (10 - 3) + 3;
27 |
28 | return (
29 |
30 |
51 |
72 |
73 |
{children}
74 |
75 | );
76 | };
77 |
--------------------------------------------------------------------------------
/agent/ai_researcher/steps.py:
--------------------------------------------------------------------------------
1 | """
2 | This node is responsible for creating the steps for the research process.
3 | """
4 |
5 | # pylint: disable=line-too-long
6 |
7 | from datetime import datetime
8 | from langchain_core.messages import HumanMessage
9 | from langchain_core.runnables import RunnableConfig
10 | from copilotkit.langchain import copilotkit_customize_config
11 | from pydantic import BaseModel, Field
12 | from ai_researcher.state import AgentState
13 | from ai_researcher.model import get_model
14 | class WeatherResponse(BaseModel):
15 | """Respond to the user with this"""
16 |
17 | temperature: float = Field(description="The temperature in fahrenheit")
18 | wind_direction: str = Field(
19 | description="The direction of the wind in abbreviated form"
20 | )
21 | wind_speed: float = Field(description="The speed of the wind in km/h")
22 |
23 |
24 | class SearchStep(BaseModel):
25 | """Model for a search step"""
26 |
27 | id: str = Field(description="The id of the step. This is used to identify the step in the state. Just make sure it is unique.")
28 | description: str = Field(description='The description of the step, i.e. "search for information about the latest AI news"')
29 | status: str = Field(description='The status of the step. Always "pending".', enum=['pending'])
30 | type: str = Field(description='The type of step.', enum=['search'])
31 |
32 | class SearchTool(BaseModel):
33 | """
34 | Break the user's query into smaller steps.
35 | Use step type "search" to search the web for information.
36 | Make sure to add all the steps needed to answer the user's query.
37 | """
38 |
39 | steps: list[SearchStep] = Field(description="The steps to be executed.")
40 |
41 | async def steps_node(state: AgentState, config: RunnableConfig):
42 | """
43 | The steps node is responsible for building the steps in the research process.
44 | """
45 |
46 | config = copilotkit_customize_config(
47 | config,
48 | emit_intermediate_state=[
49 | {
50 | "state_key": "steps",
51 | "tool": "SearchTool",
52 | "tool_argument": "steps"
53 | },
54 | ]
55 | )
56 |
57 | instructions = f"""
58 | You are a search assistant. Your task is to help the user with complex search queries by breaking the down into smaller steps.
59 |
60 | These steps are then executed serially. In the end, a final answer is produced in markdown format.
61 |
62 | The current date is {datetime.now().strftime("%Y-%m-%d")}.
63 | """
64 |
65 | response = await get_model().bind_tools(
66 | [SearchTool],
67 | tool_choice="SearchTool"
68 | ).ainvoke([
69 | state["messages"][0],
70 | HumanMessage(
71 | content=instructions
72 | ),
73 | ], config)
74 |
75 | steps = response.tool_calls[0]["args"]["steps"]
76 |
77 | if len(steps):
78 | steps[0]["updates"] = ["Searching the web..."]
79 |
80 | return {
81 | "steps": steps,
82 | }
83 |
--------------------------------------------------------------------------------
/src/components/Home/Hero.tsx:
--------------------------------------------------------------------------------
1 | import { BackgroundBeamsWithCollision } from "@/components/ui/background-beams-with-collision";
2 | import { FlipWords } from "@/components/ui/flip-words";
3 | import { firstWords, thirdWords } from "@/data";
4 | import Link from "next/link";
5 |
6 | const Hero = () => {
7 | return (
8 |
9 |
10 |
11 |
12 | Your
13 | with CopilotMate
14 |
15 | Your AI Assitant
16 |
17 |
18 |
19 |
20 |
21 |
22 | Vote on Quira
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ⭐ on Github
31 |
32 |
33 |
34 |
35 |
36 |
37 | Watch Demo
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default Hero;
51 |
--------------------------------------------------------------------------------
/src/components/ui/flip-words.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useCallback, useEffect, useRef, useState } from "react";
3 | import { AnimatePresence, motion, LayoutGroup } from "framer-motion";
4 | import { cn } from "@/lib/utils";
5 |
6 | export const FlipWords = ({
7 | words,
8 | duration = 3000,
9 | className,
10 | }: {
11 | words: string[];
12 | duration?: number;
13 | className?: string;
14 | }) => {
15 | const [currentWord, setCurrentWord] = useState(words[0]);
16 | const [isAnimating, setIsAnimating] = useState(false);
17 |
18 | // thanks for the fix Julian - https://github.com/Julian-AT
19 | const startAnimation = useCallback(() => {
20 | const word = words[words.indexOf(currentWord) + 1] || words[0];
21 | setCurrentWord(word);
22 | setIsAnimating(true);
23 | }, [currentWord, words]);
24 |
25 | useEffect(() => {
26 | if (!isAnimating)
27 | setTimeout(() => {
28 | startAnimation();
29 | }, duration);
30 | }, [isAnimating, duration, startAnimation]);
31 |
32 | return (
33 | {
35 | setIsAnimating(false);
36 | }}
37 | >
38 |
66 | {/* edit suggested by Sajal: https://x.com/DewanganSajal */}
67 | {currentWord.split(" ").map((word, wordIndex) => (
68 |
78 | {word.split("").map((letter, letterIndex) => (
79 |
89 | {letter}
90 |
91 | ))}
92 |
93 |
94 | ))}
95 |
96 |
97 | );
98 | };
99 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --background: #ffffff;
7 | --foreground: #171717;
8 | --gradient-purple: #a855f7; /* from-purple-500 */
9 | --gradient-violet: #8b5cf6; /* via-violet-500 */
10 | --gradient-pink: #ec4899; /* to-pink-500 */
11 | }
12 |
13 | @media (prefers-color-scheme: dark) {
14 | :root {
15 | --background: #0a0a0a;
16 | --foreground: #ededed;
17 | }
18 | }
19 |
20 | body {
21 | color: var(--foreground);
22 | background: var(--background);
23 | -webkit-user-select: none; /* Safari */
24 | -ms-user-select: none; /* IE 10 and IE 11 */
25 | user-select: none; /* Standard syntax */
26 | }
27 | img {
28 | -webkit-user-drag: none;
29 | -khtml-user-drag: none;
30 | -moz-user-drag: none;
31 | -o-user-drag: none;
32 | }
33 | /* Custom Scrollbar Styles */
34 | ::-webkit-scrollbar {
35 | width: 0.4rem; /* Width of the scrollbar */
36 | }
37 |
38 | ::-webkit-scrollbar-track {
39 | background: transparent; /* Track background */
40 | }
41 |
42 | ::-webkit-scrollbar-thumb {
43 | background: linear-gradient(
44 | to bottom,
45 | var(--gradient-purple),
46 | var(--gradient-violet),
47 | var(--gradient-pink)
48 | ); /* Custom gradient */
49 | border-radius: 0.4rem; /* Optional: Rounded corners */
50 | }
51 |
52 | ::-webkit-scrollbar-thumb:hover {
53 | background: linear-gradient(
54 | to bottom,
55 | var(--gradient-purple),
56 | var(--gradient-pink)
57 | ); /* Change on hover */
58 | }
59 |
60 | ::-moz-scrollbar {
61 | width: 0.4rem; /* Width for Firefox */
62 | }
63 |
64 | ::-moz-scrollbar-thumb {
65 | background: linear-gradient(
66 | to bottom,
67 | var(--gradient-purple),
68 | var(--gradient-violet),
69 | var(--gradient-pink)
70 | );
71 | border-radius: 0.4rem;
72 | }
73 |
74 | @layer utilities {
75 | .text-balance {
76 | text-wrap: balance;
77 | }
78 | .purple-pink-gradient {
79 | position: relative; /* 'relative' */
80 | background-clip: text; /* 'bg-clip-text' */
81 | color: transparent; /* 'text-transparent' */
82 | background-repeat: no-repeat; /* 'bg-no-repeat' */
83 | background-image: linear-gradient(to right, #a855f7, #8b5cf6, #ec4899);
84 | }
85 | /* Glassmorphism Card */
86 | .glass-card {
87 | background: rgba(255, 255, 255, 0.1);
88 | backdrop-filter: blur(10px);
89 | -webkit-backdrop-filter: blur(10px);
90 | border: 1px solid rgba(255, 255, 255, 0.2);
91 | border-radius: 15px;
92 | padding: 1rem;
93 | box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
94 | }
95 |
96 | /* Glow Button */
97 | .glow-btn {
98 | background-color: #1a202c;
99 | border: 1px solid rgba(255, 255, 255, 0.2);
100 | padding: 0.5rem 1rem;
101 | border-radius: 10px;
102 | color: white;
103 | text-shadow: 0 0 10px #a020f0; /* Bright purple text glow */
104 | box-shadow: 0 0 20px rgba(160, 32, 240, 0.8), 0 0 30px rgba(160, 32, 240, 0.5); /* Purple glow */
105 | transition: box-shadow 0.3s ease-in-out;
106 | }
107 |
108 | .glow-btn:hover {
109 | box-shadow: 0 0 30px rgb(195, 98, 255), 0 0 40px rgba(160, 32, 240, 1); /* Stronger purple glow on hover */
110 | }
111 |
112 |
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/components/Home/Feedback.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import { TypewriterEffectSmooth } from "../ui/typewriter-effect";
4 | import { initialFeedbacks } from "@/data";
5 |
6 | const FeedbackSection = () => {
7 | const [feedbacks, setFeedbacks] = useState(initialFeedbacks);
8 | const [author, setAuthor] = useState("");
9 | const [text, setText] = useState("");
10 |
11 | const handleSubmit = (e: React.FormEvent) => {
12 | e.preventDefault();
13 | if (text && author) {
14 | const newFeedback = {
15 | id: feedbacks.length + 1, // Simple id assignment
16 | text,
17 | author,
18 | };
19 | setFeedbacks([...feedbacks, newFeedback]);
20 | setAuthor("");
21 | setText("");
22 | }
23 | };
24 | const words = [
25 | {
26 | text: "User",
27 | },
28 | {
29 | text: "Feedback",
30 | },
31 | ];
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 | {feedbacks.map((feedback) => (
40 |
44 |
"{feedback.text}"
45 |
46 | - {feedback.author}
47 |
48 |
49 | ))}
50 |
51 |
52 |
92 |
93 |
94 | );
95 | };
96 |
97 | export default FeedbackSection;
98 |
--------------------------------------------------------------------------------
/public/git.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/ui/moving-border.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import {
4 | motion,
5 | useAnimationFrame,
6 | useMotionTemplate,
7 | useMotionValue,
8 | useTransform,
9 | } from "framer-motion";
10 | import { useRef } from "react";
11 | import { cn } from "@/lib/utils";
12 |
13 | export function Button({
14 | borderRadius = "1.75rem",
15 | children,
16 | as: Component = "button",
17 | containerClassName,
18 | borderClassName,
19 | duration,
20 | className,
21 | ...otherProps
22 | }: {
23 | borderRadius?: string;
24 | children: React.ReactNode;
25 | as?: any;
26 | containerClassName?: string;
27 | borderClassName?: string;
28 | duration?: number;
29 | className?: string;
30 | [key: string]: any;
31 | }) {
32 | return (
33 |
43 |
56 | {/* style={{
57 | background: "rgb(4,7,29)",
58 | backgroundColor:
59 | "linear-gradient(90deg, rgba(4,7,29,1) 0%, rgba(12,14,35,1) 100%)",
60 | }} */}
61 |
70 | {children}
71 |
72 |
73 | );
74 | }
75 |
76 | export const MovingBorder = ({
77 | children,
78 | duration = 2000,
79 | rx,
80 | ry,
81 | ...otherProps
82 | }: {
83 | children: React.ReactNode;
84 | duration?: number;
85 | rx?: string;
86 | ry?: string;
87 | [key: string]: any;
88 | }) => {
89 | const pathRef = useRef();
90 | const progress = useMotionValue(0);
91 |
92 | useAnimationFrame((time) => {
93 | const length = pathRef.current?.getTotalLength();
94 | if (length) {
95 | const pxPerMillisecond = length / duration;
96 | progress.set((time * pxPerMillisecond) % length);
97 | }
98 | });
99 |
100 | const x = useTransform(
101 | progress,
102 | (val) => pathRef.current?.getPointAtLength(val).x
103 | );
104 | const y = useTransform(
105 | progress,
106 | (val) => pathRef.current?.getPointAtLength(val).y
107 | );
108 |
109 | const transform = useMotionTemplate`translateX(${x}px) translateY(${y}px) translateX(-50%) translateY(-50%)`;
110 |
111 | return (
112 | <>
113 |
121 |
129 |
130 |
139 | {children}
140 |
141 | >
142 | );
143 | };
144 |
--------------------------------------------------------------------------------
/src/components/StudyBuddy/ResultsView.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useResearchContext } from "@/lib/research-provider";
4 | import { motion } from "framer-motion";
5 | import { BookOpenIcon, LoaderCircleIcon, SparkleIcon } from "lucide-react";
6 | import { SkeletonLoader } from "./SkeletonLoader";
7 | import { useCoAgent } from "@copilotkit/react-core";
8 | import { Progress } from "./Progress";
9 | import { AnswerMarkdown } from "./AnswerMarkdown";
10 |
11 | export function ResultsView() {
12 | const { researchQuery } = useResearchContext();
13 | const { state: agentState } = useCoAgent({
14 | name: "studybuddy_agent",
15 | });
16 |
17 | console.log("AGENT_STATE", agentState);
18 |
19 | const steps =
20 | agentState?.steps?.map((step: any) => {
21 | return {
22 | description: step.description || "",
23 | status: step.status || "pending",
24 | updates: step.updates || [],
25 | };
26 | }) || [];
27 |
28 | const isLoading = !agentState?.answer?.markdown;
29 |
30 | return (
31 |
37 |
38 |
39 |
40 | {researchQuery}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | {isLoading ? (
50 |
51 | ) : (
52 |
53 | )}
54 | Search Result
55 |
56 |
57 | {isLoading ? (
58 |
59 | ) : (
60 |
61 | )}
62 |
63 |
64 |
65 | {agentState?.answer?.references?.length && (
66 |
67 |
68 |
69 | References
70 |
71 |
86 |
87 | )}
88 |
89 |
90 | window.location.reload()} >
91 |
92 |
93 | Return
94 |
95 |
96 |
97 |
98 |
99 | );
100 | }
101 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 | {/*
48 |
49 | Close
50 | */}
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogClose,
116 | DialogTrigger,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/src/components/StudyBuddy/HomeView.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { Textarea } from "@/components/ui/textarea";
5 | import { cn } from "@/lib/utils";
6 | import { Button } from "@/components/ui/button";
7 | import { CornerDownLeftIcon } from "lucide-react";
8 | import { useResearchContext } from "@/lib/research-provider";
9 | import { motion } from "framer-motion";
10 | import { useCopilotChat } from "@copilotkit/react-core";
11 | import { Role, TextMessage } from "@copilotkit/runtime-client-gql";
12 | import { useCoAgent } from "@copilotkit/react-core";
13 |
14 | const MAX_INPUT_LENGTH = 250;
15 |
16 | export function HomeView() {
17 | const { setResearchQuery, researchInput, setResearchInput } =
18 | useResearchContext();
19 | const [isInputFocused, setIsInputFocused] = useState(false);
20 | const { run: runResearchAgent } = useCoAgent({
21 | name: "studybuddy_agent",
22 | });
23 |
24 | const handleResearch = (query: string) => {
25 | setResearchQuery(query);
26 | runResearchAgent(query);
27 | };
28 |
29 | const suggestions = [
30 | { label: "Effective study techniques for group learning", icon: "📚" },
31 | { label: "Best apps for collaborative note-taking", icon: "📝" },
32 | { label: "Top resources for mastering difficult subjects", icon: "🎓" },
33 | { label: "Creating a study schedule that works for everyone", icon: "📅" },
34 | ];
35 |
36 | return (
37 |
44 |
45 | Your AI study Companion - StudyBuddy
46 |
47 |
48 |
56 |
setIsInputFocused(true)}
60 | onBlur={() => setIsInputFocused(false)}
61 | value={researchInput}
62 | onChange={(e) => setResearchInput(e.target.value)}
63 | onKeyDown={(e) => {
64 | if (e.key === "Enter" && !e.shiftKey) {
65 | e.preventDefault();
66 | handleResearch(researchInput);
67 | }
68 | }}
69 | maxLength={MAX_INPUT_LENGTH}
70 | />
71 |
72 |
78 | {researchInput.length} / {MAX_INPUT_LENGTH}
79 |
80 |
handleResearch(researchInput)}
87 | >
88 | Search
89 |
90 |
91 |
92 |
93 |
94 | {suggestions.map((suggestion) => (
95 |
handleResearch(suggestion.label)}
98 | className="p-2 text-xl bg-transparent rounded-md border-2 border-indigo-300 col-span-2 lg:col-span-1 flex cursor-pointer items-center space-x-2 hover:border-purple-500 hover:border-2 hover:shadow-lg hover:shadow-purple-500 transition-all hover:scale-105 duration-300"
99 |
100 | >
101 | {suggestion.icon}
102 | {suggestion.label}
103 |
104 | ))}
105 |
106 |
107 | );
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/ui/multi-step-loader.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { cn } from "@/lib/utils";
3 | import { AnimatePresence, motion } from "framer-motion";
4 | import { useState, useEffect } from "react";
5 |
6 | const CheckIcon = ({ className }: { className?: string }) => {
7 | return (
8 |
16 |
17 |
18 | );
19 | };
20 |
21 | const CheckFilled = ({ className }: { className?: string }) => {
22 | return (
23 |
29 |
34 |
35 | );
36 | };
37 |
38 | type LoadingState = {
39 | text: string;
40 | };
41 |
42 | const LoaderCore = ({
43 | loadingStates,
44 | value = 0,
45 | }: {
46 | loadingStates: LoadingState[];
47 | value?: number;
48 | }) => {
49 | return (
50 |
51 | {loadingStates.map((loadingState, index) => {
52 | const distance = Math.abs(index - value);
53 | const opacity = Math.max(1 - distance * 0.2, 0); // Minimum opacity is 0, keep it 0.2 if you're sane.
54 |
55 | return (
56 |
63 |
64 | {index > value && (
65 |
66 | )}
67 | {index <= value && (
68 |
75 | )}
76 |
77 |
83 | {loadingState.text}
84 |
85 |
86 | );
87 | })}
88 |
89 | );
90 | };
91 |
92 | export const MultiStepLoader = ({
93 | loadingStates,
94 | loading,
95 | duration = 2000,
96 | loop = true,
97 | }: {
98 | loadingStates: LoadingState[];
99 | loading?: boolean;
100 | duration?: number;
101 | loop?: boolean;
102 | }) => {
103 | const [currentState, setCurrentState] = useState(0);
104 |
105 | useEffect(() => {
106 | if (!loading) {
107 | setCurrentState(0);
108 | return;
109 | }
110 | const timeout = setTimeout(() => {
111 | setCurrentState((prevState) =>
112 | loop
113 | ? prevState === loadingStates.length - 1
114 | ? 0
115 | : prevState + 1
116 | : Math.min(prevState + 1, loadingStates.length - 1)
117 | );
118 | }, duration);
119 |
120 | return () => clearTimeout(timeout);
121 | }, [currentState, loading, loop, loadingStates.length, duration]);
122 | return (
123 |
124 | {loading && (
125 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | )}
144 |
145 | );
146 | };
147 |
--------------------------------------------------------------------------------
/src/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | // Inspired by react-hot-toast library
4 | import * as React from "react"
5 |
6 | import type {
7 | ToastActionElement,
8 | ToastProps,
9 | } from "@/components/ui/toast"
10 |
11 | const TOAST_LIMIT = 1
12 | const TOAST_REMOVE_DELAY = 1000000
13 |
14 | type ToasterToast = ToastProps & {
15 | id: string
16 | title?: React.ReactNode
17 | description?: React.ReactNode
18 | action?: ToastActionElement
19 | }
20 |
21 | const actionTypes = {
22 | ADD_TOAST: "ADD_TOAST",
23 | UPDATE_TOAST: "UPDATE_TOAST",
24 | DISMISS_TOAST: "DISMISS_TOAST",
25 | REMOVE_TOAST: "REMOVE_TOAST",
26 | } as const
27 |
28 | let count = 0
29 |
30 | function genId() {
31 | count = (count + 1) % Number.MAX_SAFE_INTEGER
32 | return count.toString()
33 | }
34 |
35 | type ActionType = typeof actionTypes
36 |
37 | type Action =
38 | | {
39 | type: ActionType["ADD_TOAST"]
40 | toast: ToasterToast
41 | }
42 | | {
43 | type: ActionType["UPDATE_TOAST"]
44 | toast: Partial
45 | }
46 | | {
47 | type: ActionType["DISMISS_TOAST"]
48 | toastId?: ToasterToast["id"]
49 | }
50 | | {
51 | type: ActionType["REMOVE_TOAST"]
52 | toastId?: ToasterToast["id"]
53 | }
54 |
55 | interface State {
56 | toasts: ToasterToast[]
57 | }
58 |
59 | const toastTimeouts = new Map>()
60 |
61 | const addToRemoveQueue = (toastId: string) => {
62 | if (toastTimeouts.has(toastId)) {
63 | return
64 | }
65 |
66 | const timeout = setTimeout(() => {
67 | toastTimeouts.delete(toastId)
68 | dispatch({
69 | type: "REMOVE_TOAST",
70 | toastId: toastId,
71 | })
72 | }, TOAST_REMOVE_DELAY)
73 |
74 | toastTimeouts.set(toastId, timeout)
75 | }
76 |
77 | export const reducer = (state: State, action: Action): State => {
78 | switch (action.type) {
79 | case "ADD_TOAST":
80 | return {
81 | ...state,
82 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
83 | }
84 |
85 | case "UPDATE_TOAST":
86 | return {
87 | ...state,
88 | toasts: state.toasts.map((t) =>
89 | t.id === action.toast.id ? { ...t, ...action.toast } : t
90 | ),
91 | }
92 |
93 | case "DISMISS_TOAST": {
94 | const { toastId } = action
95 |
96 | // ! Side effects ! - This could be extracted into a dismissToast() action,
97 | // but I'll keep it here for simplicity
98 | if (toastId) {
99 | addToRemoveQueue(toastId)
100 | } else {
101 | state.toasts.forEach((toast) => {
102 | addToRemoveQueue(toast.id)
103 | })
104 | }
105 |
106 | return {
107 | ...state,
108 | toasts: state.toasts.map((t) =>
109 | t.id === toastId || toastId === undefined
110 | ? {
111 | ...t,
112 | open: false,
113 | }
114 | : t
115 | ),
116 | }
117 | }
118 | case "REMOVE_TOAST":
119 | if (action.toastId === undefined) {
120 | return {
121 | ...state,
122 | toasts: [],
123 | }
124 | }
125 | return {
126 | ...state,
127 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
128 | }
129 | }
130 | }
131 |
132 | const listeners: Array<(state: State) => void> = []
133 |
134 | let memoryState: State = { toasts: [] }
135 |
136 | function dispatch(action: Action) {
137 | memoryState = reducer(memoryState, action)
138 | listeners.forEach((listener) => {
139 | listener(memoryState)
140 | })
141 | }
142 |
143 | type Toast = Omit
144 |
145 | function toast({ ...props }: Toast) {
146 | const id = genId()
147 |
148 | const update = (props: ToasterToast) =>
149 | dispatch({
150 | type: "UPDATE_TOAST",
151 | toast: { ...props, id },
152 | })
153 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
154 |
155 | dispatch({
156 | type: "ADD_TOAST",
157 | toast: {
158 | ...props,
159 | id,
160 | open: true,
161 | onOpenChange: (open) => {
162 | if (!open) dismiss()
163 | },
164 | },
165 | })
166 |
167 | return {
168 | id: id,
169 | dismiss,
170 | update,
171 | }
172 | }
173 |
174 | function useToast() {
175 | const [state, setState] = React.useState(memoryState)
176 |
177 | React.useEffect(() => {
178 | listeners.push(setState)
179 | return () => {
180 | const index = listeners.indexOf(setState)
181 | if (index > -1) {
182 | listeners.splice(index, 1)
183 | }
184 | }
185 | }, [state])
186 |
187 | return {
188 | ...state,
189 | toast,
190 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
191 | }
192 | }
193 |
194 | export { useToast, toast }
195 |
--------------------------------------------------------------------------------
/src/app/spreadsheet/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import "@copilotkit/react-ui/styles.css";
3 |
4 | import React, { useState } from "react";
5 | import Sidebar from "@/components/Spreadsheet/Sidebar";
6 | import SingleSpreadsheet from "@/components/Spreadsheet/SingleSpreadsheet";
7 | import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core";
8 | import {
9 | CopilotKitCSSProperties,
10 | CopilotSidebar,
11 | useCopilotChatSuggestions,
12 | } from "@copilotkit/react-ui";
13 | import { INSTRUCTIONS } from "./instructions";
14 | import { canonicalSpreadsheetData } from "@/components/Spreadsheet/canonicalSpreadsheetData";
15 | import { SpreadsheetData } from "@/components/Spreadsheet/type";
16 | import { PreviewSpreadsheetChanges } from "@/components/Spreadsheet/PreviewSpreadsheetChanges";
17 |
18 | const HomePage = () => {
19 | useCopilotChatSuggestions(
20 | {
21 | instructions: "Suggest the most relevant actions related to spreadsheet.",
22 | },
23 | );
24 | return (
25 |
37 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | const Main = () => {
54 | const [spreadsheets, setSpreadsheets] = React.useState([
55 | {
56 | title: "Spreadsheet 1",
57 | rows: [
58 | [{ value: "" }, { value: "" }, { value: "" }],
59 | [{ value: "" }, { value: "" }, { value: "" }],
60 | [{ value: "" }, { value: "" }, { value: "" }],
61 | ],
62 | },
63 | ]);
64 |
65 | const [selectedSpreadsheetIndex, setSelectedSpreadsheetIndex] = useState(0);
66 |
67 | useCopilotAction({
68 | name: "createSpreadsheet",
69 | description: "Create a new spreadsheet",
70 | parameters: [
71 | {
72 | name: "rows",
73 | type: "object[]",
74 | description: "The rows of the spreadsheet",
75 | attributes: [
76 | {
77 | name: "cells",
78 | type: "object[]",
79 | description: "The cells of the row",
80 | attributes: [
81 | {
82 | name: "value",
83 | type: "string",
84 | description: "The value of the cell",
85 | },
86 | ],
87 | },
88 | ],
89 | },
90 | {
91 | name: "title",
92 | type: "string",
93 | description: "The title of the spreadsheet",
94 | },
95 | ],
96 | render: (props) => {
97 | const { rows, title } = props.args;
98 | const newRows = canonicalSpreadsheetData(rows);
99 |
100 | return (
101 | {
106 | const newSpreadsheet: SpreadsheetData = {
107 | title: title || "Untitled Spreadsheet",
108 | rows: rows,
109 | };
110 | setSpreadsheets((prev) => [...prev, newSpreadsheet]);
111 | setSelectedSpreadsheetIndex(spreadsheets.length);
112 | }}
113 | />
114 | );
115 | },
116 | handler: ({ rows, title }) => {
117 | // Do nothing.
118 | // The preview component will optionally handle committing the changes.
119 | },
120 | });
121 |
122 | useCopilotReadable({
123 | description: "Today's date",
124 | value: new Date().toLocaleDateString(),
125 | });
126 |
127 | return (
128 |
129 |
134 | {
137 | setSpreadsheets((prev) => {
138 | console.log("setSpreadsheet", spreadsheet);
139 | const newSpreadsheets = [...prev];
140 | newSpreadsheets[selectedSpreadsheetIndex] = spreadsheet;
141 | return newSpreadsheets;
142 | });
143 | }}
144 | />
145 |
146 | );
147 | };
148 |
149 | export default HomePage;
150 |
--------------------------------------------------------------------------------
/src/components/ui/typewriter-effect.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { cn } from "@/lib/utils";
4 | import { motion, stagger, useAnimate, useInView } from "framer-motion";
5 | import { useEffect } from "react";
6 |
7 | export const TypewriterEffect = ({
8 | words,
9 | className,
10 | cursorClassName,
11 | }: {
12 | words: {
13 | text: string;
14 | className?: string;
15 | }[];
16 | className?: string;
17 | cursorClassName?: string;
18 | }) => {
19 | // split text inside of words into array of characters
20 | const wordsArray = words.map((word) => {
21 | return {
22 | ...word,
23 | text: word.text.split(""),
24 | };
25 | });
26 |
27 | const [scope, animate] = useAnimate();
28 | const isInView = useInView(scope);
29 | useEffect(() => {
30 | if (isInView) {
31 | animate(
32 | "span",
33 | {
34 | display: "inline-block",
35 | opacity: 1,
36 | width: "fit-content",
37 | },
38 | {
39 | duration: 0.3,
40 | delay: stagger(0.1),
41 | ease: "easeInOut",
42 | }
43 | );
44 | }
45 | }, [isInView]);
46 |
47 | const renderWords = () => {
48 | return (
49 |
50 | {wordsArray.map((word, idx) => {
51 | return (
52 |
53 | {word.text.map((char, index) => (
54 |
62 | {char}
63 |
64 | ))}
65 |
66 |
67 | );
68 | })}
69 |
70 | );
71 | };
72 | return (
73 |
79 | {renderWords()}
80 |
97 |
98 | );
99 | };
100 |
101 | export const TypewriterEffectSmooth = ({
102 | words,
103 | className,
104 | cursorClassName,
105 | }: {
106 | words: {
107 | text: string;
108 | className?: string;
109 | }[];
110 | className?: string;
111 | cursorClassName?: string;
112 | }) => {
113 | // split text inside of words into array of characters
114 | const wordsArray = words.map((word) => {
115 | return {
116 | ...word,
117 | text: word.text.split(""),
118 | };
119 | });
120 | const renderWords = () => {
121 | return (
122 |
123 | {wordsArray.map((word, idx) => {
124 | return (
125 |
126 | {word.text.map((char, index) => (
127 |
131 | {char}
132 |
133 | ))}
134 |
135 |
136 | );
137 | })}
138 |
139 | );
140 | };
141 |
142 | return (
143 |
144 |
158 |
164 | {renderWords()}{" "}
165 |
{" "}
166 |
167 |
185 |
186 | );
187 | };
188 |
--------------------------------------------------------------------------------
/src/lib/hooks/use-tasks.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core";
3 | import { createContext, useContext, useState, ReactNode } from "react";
4 | import { defaultTasks } from "../default-tasks";
5 | import { Task, TaskStatus, TaskPriority } from "../tasks.types"; // Import TaskPriority
6 |
7 | let nextId = defaultTasks.length + 1;
8 |
9 | type TasksContextType = {
10 | tasks: Task[];
11 | addTask: (title: string, priority?: TaskPriority) => void; // Add priority parameter
12 | setTaskStatus: (id: number, status: TaskStatus) => void;
13 | setTaskPriority: (id: number, priority: TaskPriority) => void; // New function for setting priority
14 | deleteTask: (id: number) => void;
15 | };
16 |
17 | const TasksContext = createContext(undefined);
18 |
19 | export const TasksProvider = ({ children }: { children: ReactNode }) => {
20 | const [tasks, setTasks] = useState(defaultTasks);
21 |
22 | useCopilotReadable({
23 | description: "The state of the todo list",
24 | value: tasks,
25 | });
26 |
27 | useCopilotAction({
28 | name: "addTask",
29 | description: "Adds a task to the todo list",
30 | parameters: [
31 | {
32 | name: "title",
33 | type: "string",
34 | description: "The title of the task",
35 | required: true,
36 | },
37 | {
38 | name: "priority", // Add priority parameter
39 | type: "string",
40 | description: "The priority of the task",
41 | enum: Object.values(TaskPriority), // Ensure it uses enum values
42 | required: false, // Optional parameter
43 | },
44 | ],
45 | handler: async ({ title, priority }) => {
46 | addTask(title, priority || TaskPriority.medium); // Default priority to medium if not provided
47 | return "Task added successfully";
48 | },
49 | render: "Processing..."
50 | });
51 |
52 | useCopilotAction({
53 | name: "deleteTask",
54 | description: "Deletes a task from the todo list",
55 | parameters: [
56 | {
57 | name: "id",
58 | type: "number",
59 | description: "The id of the task to delete",
60 | required: true,
61 | },
62 | ],
63 | handler: async ({ id }) => {
64 | deleteTask(id);
65 | return "Task deleted successfully";
66 | },
67 | render: "Processing..."
68 | });
69 |
70 | useCopilotAction({
71 | name: "setTaskStatus",
72 | description: "Sets the status of a task",
73 | parameters: [
74 | {
75 | name: "id",
76 | type: "number",
77 | description: "The id of the task",
78 | required: true,
79 | },
80 | {
81 | name: "status",
82 | type: "string",
83 | description: "The status of the task",
84 | enum: Object.values(TaskStatus),
85 | required: true,
86 | },
87 | ],
88 | handler: async ({ id, status }) => {
89 | setTaskStatus(id, status);
90 | return "Set status successful";
91 | },
92 | render: "Processing..."
93 | });
94 |
95 | // New Action to Update Task Priority
96 | useCopilotAction({
97 | name: "setTaskPriority",
98 | description: "Sets the priority of a task",
99 | parameters: [
100 | {
101 | name: "id",
102 | type: "number",
103 | description: "The id of the task",
104 | required: true,
105 | },
106 | {
107 | name: "priority",
108 | type: "string",
109 | description: "The priority of the task",
110 | enum: Object.values(TaskPriority), // Use the TaskPriority enum
111 | required: true,
112 | },
113 | ],
114 | handler: async ({ id, priority }) => {
115 | setTaskPriority(id, priority);
116 | return "Task priority updated successfully";
117 | },
118 | render: "Processing..."
119 | });
120 |
121 | const addTask = (title: string, priority: TaskPriority = TaskPriority.medium) => {
122 | setTasks([...tasks, { id: nextId++, title, status: TaskStatus.todo, priority }]);
123 | };
124 |
125 | const setTaskStatus = (id: number, status: TaskStatus) => {
126 | setTasks(
127 | tasks.map((task) =>
128 | task.id === id ? { ...task, status } : task
129 | )
130 | );
131 | };
132 |
133 | const setTaskPriority = (id: number, priority: TaskPriority) => {
134 | setTasks(
135 | tasks.map((task) =>
136 | task.id === id ? { ...task, priority } : task
137 | )
138 | );
139 | };
140 |
141 | const deleteTask = (id: number) => {
142 | setTasks(tasks.filter((task) => task.id !== id));
143 | };
144 |
145 | return (
146 |
147 | {children}
148 |
149 | );
150 | };
151 |
152 | export const useTasks = () => {
153 | const context = useContext(TasksContext);
154 | if (context === undefined) {
155 | throw new Error("useTasks must be used within a TasksProvider");
156 | }
157 | return context;
158 | };
159 |
--------------------------------------------------------------------------------
/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ToastPrimitives from "@radix-ui/react-toast"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { X } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const ToastProvider = ToastPrimitives.Provider
11 |
12 | const ToastViewport = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, ...props }, ref) => (
16 |
24 | ))
25 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
26 |
27 | const toastVariants = cva(
28 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
29 | {
30 | variants: {
31 | variant: {
32 | default: "border bg-background text-foreground",
33 | destructive:
34 | "destructive group border-destructive bg-destructive text-destructive-foreground",
35 | },
36 | },
37 | defaultVariants: {
38 | variant: "default",
39 | },
40 | }
41 | )
42 |
43 | const Toast = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef &
46 | VariantProps
47 | >(({ className, variant, ...props }, ref) => {
48 | return (
49 |
54 | )
55 | })
56 | Toast.displayName = ToastPrimitives.Root.displayName
57 |
58 | const ToastAction = React.forwardRef<
59 | React.ElementRef,
60 | React.ComponentPropsWithoutRef
61 | >(({ className, ...props }, ref) => (
62 |
70 | ))
71 | ToastAction.displayName = ToastPrimitives.Action.displayName
72 |
73 | const ToastClose = React.forwardRef<
74 | React.ElementRef,
75 | React.ComponentPropsWithoutRef
76 | >(({ className, ...props }, ref) => (
77 |
86 |
87 |
88 | ))
89 | ToastClose.displayName = ToastPrimitives.Close.displayName
90 |
91 | const ToastTitle = React.forwardRef<
92 | React.ElementRef,
93 | React.ComponentPropsWithoutRef
94 | >(({ className, ...props }, ref) => (
95 |
100 | ))
101 | ToastTitle.displayName = ToastPrimitives.Title.displayName
102 |
103 | const ToastDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | ToastDescription.displayName = ToastPrimitives.Description.displayName
114 |
115 | type ToastProps = React.ComponentPropsWithoutRef
116 |
117 | type ToastActionElement = React.ReactElement
118 |
119 | export {
120 | type ToastProps,
121 | type ToastActionElement,
122 | ToastProvider,
123 | ToastViewport,
124 | Toast,
125 | ToastTitle,
126 | ToastDescription,
127 | ToastClose,
128 | ToastAction,
129 | }
130 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | akashjana663@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/src/components/Spreadsheet/SingleSpreadsheet.tsx:
--------------------------------------------------------------------------------
1 | import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core";
2 | import React, { useState } from "react";
3 | import Spreadsheet from "react-spreadsheet";
4 | import { canonicalSpreadsheetData } from "./canonicalSpreadsheetData";
5 | import { SpreadsheetData, SpreadsheetRow } from "./type";
6 | import { PreviewSpreadsheetChanges } from "./PreviewSpreadsheetChanges";
7 | //import { PreviewSpreadsheetChanges } from "./PreviewSpreadsheetChanges";
8 |
9 | interface MainAreaProps {
10 | spreadsheet: SpreadsheetData;
11 | setSpreadsheet: (spreadsheet: SpreadsheetData) => void;
12 | }
13 |
14 | const SingleSpreadsheet = ({ spreadsheet, setSpreadsheet }: MainAreaProps) => {
15 | useCopilotReadable({
16 | description: "The current spreadsheet",
17 | value: spreadsheet,
18 | });
19 |
20 | useCopilotAction({
21 | name: "suggestSpreadsheetOverride",
22 | description: "Suggest an override of the current spreadsheet",
23 | parameters: [
24 | {
25 | name: "rows",
26 | type: "object[]",
27 | description: "The rows of the spreadsheet",
28 | attributes: [
29 | {
30 | name: "cells",
31 | type: "object[]",
32 | description: "The cells of the row",
33 | attributes: [
34 | {
35 | name: "value",
36 | type: "string",
37 | description: "The value of the cell",
38 | },
39 | ],
40 | },
41 | ],
42 | },
43 | {
44 | name: "title",
45 | type: "string",
46 | description: "The title of the spreadsheet",
47 | required: false,
48 | },
49 | ],
50 | render: (props) => {
51 | const { rows } = props.args;
52 | const newRows = canonicalSpreadsheetData(rows);
53 |
54 | return (
55 | {
60 | const updatedSpreadsheet: SpreadsheetData = {
61 | title: spreadsheet.title,
62 | rows: rows,
63 | };
64 | setSpreadsheet(updatedSpreadsheet);
65 | }}
66 | />
67 | );
68 | },
69 | handler: ({ rows, title }) => {
70 | // Do nothing.
71 | // The preview component will optionally handle committing the changes.
72 | },
73 | });
74 |
75 | useCopilotAction({
76 | name: "appendToSpreadsheet",
77 | description: "Append rows to the current spreadsheet",
78 | parameters: [
79 | {
80 | name: "rows",
81 | type: "object[]",
82 | description: "The new rows of the spreadsheet",
83 | attributes: [
84 | {
85 | name: "cells",
86 | type: "object[]",
87 | description: "The cells of the row",
88 | attributes: [
89 | {
90 | name: "value",
91 | type: "string",
92 | description: "The value of the cell",
93 | },
94 | ],
95 | },
96 | ],
97 | },
98 | ],
99 | render: (props) => {
100 | const status = props.status;
101 | const { rows } = props.args;
102 | const newRows = canonicalSpreadsheetData(rows);
103 | return (
104 |
105 |
Status: {status}
106 |
107 |
108 | );
109 | },
110 | handler: ({ rows }) => {
111 | const canonicalRows = canonicalSpreadsheetData(rows);
112 | const updatedSpreadsheet: SpreadsheetData = {
113 | title: spreadsheet.title,
114 | rows: [...spreadsheet.rows, ...canonicalRows],
115 | };
116 | setSpreadsheet(updatedSpreadsheet);
117 | },
118 | });
119 |
120 | return (
121 |
122 |
127 | setSpreadsheet({ ...spreadsheet, title: e.target.value })
128 | }
129 | />
130 |
131 | {
134 | console.log("data", data);
135 | setSpreadsheet({ ...spreadsheet, rows: data as any });
136 | }}
137 | />
138 | {
141 | // add an empty cell to each row
142 | const spreadsheetRows = [...spreadsheet.rows];
143 | for (let i = 0; i < spreadsheet.rows.length; i++) {
144 | spreadsheet.rows[i].push({ value: "" });
145 | }
146 | setSpreadsheet({
147 | ...spreadsheet,
148 | rows: spreadsheetRows,
149 | });
150 | }}
151 | >
152 | +
153 |
154 |
155 |
{
158 | const numberOfColumns = spreadsheet.rows[0].length;
159 | const newRow: SpreadsheetRow = [];
160 | for (let i = 0; i < numberOfColumns; i++) {
161 | newRow.push({ value: "" });
162 | }
163 | setSpreadsheet({
164 | ...spreadsheet,
165 | rows: [...spreadsheet.rows, newRow],
166 | });
167 | }}
168 | >
169 | +
170 |
171 |
172 | );
173 | };
174 |
175 | export default SingleSpreadsheet;
176 |
--------------------------------------------------------------------------------
/src/components/ui/floating-dock.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Note: Use position fixed according to your needs
3 | * Desktop navbar is better positioned at the bottom
4 | * Mobile navbar is better positioned at bottom right.
5 | **/
6 | "use client"
7 | import { cn } from "@/lib/utils";
8 | import { IconLayoutNavbarCollapse } from "@tabler/icons-react";
9 | import {
10 | AnimatePresence,
11 | MotionValue,
12 | motion,
13 | useMotionValue,
14 | useSpring,
15 | useTransform,
16 | } from "framer-motion";
17 | import Link from "next/link";
18 | import { useRef, useState } from "react";
19 |
20 | export const FloatingDock = ({
21 | items,
22 | desktopClassName,
23 | mobileClassName,
24 | }: {
25 | items: { title: string; icon: React.ReactNode; href: string }[];
26 | desktopClassName?: string;
27 | mobileClassName?: string;
28 | }) => {
29 | return (
30 | <>
31 |
32 |
33 | >
34 | );
35 | };
36 |
37 | const FloatingDockMobile = ({
38 | items,
39 | className,
40 | }: {
41 | items: { title: string; icon: React.ReactNode; href: string }[];
42 | className?: string;
43 | }) => {
44 | const [open, setOpen] = useState(false);
45 | return (
46 |
47 |
48 | {open && (
49 |
53 | {items.map((item, idx) => (
54 |
70 |
75 | {item.icon}
76 |
77 |
78 | ))}
79 |
80 | )}
81 |
82 |
setOpen(!open)}
84 | className="h-10 w-10 rounded-full bg-gray-50 dark:bg-neutral-800 flex items-center justify-center"
85 | >
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | const FloatingDockDesktop = ({
93 | items,
94 | className,
95 | }: {
96 | items: { title: string; icon: React.ReactNode; href: string }[];
97 | className?: string;
98 | }) => {
99 | let mouseX = useMotionValue(Infinity);
100 | return (
101 | mouseX.set(e.pageX)}
103 | onMouseLeave={() => mouseX.set(Infinity)}
104 | className={cn(
105 | "mx-auto hidden md:flex h-16 gap-4 items-end border border-white/[.2] rounded-2xl dark:bg-white-500 backdrop-blur-lg px-4 pb-3",
106 | className
107 | )}
108 | >
109 | {items.map((item) => (
110 |
111 | ))}
112 |
113 | );
114 | };
115 |
116 | function IconContainer({
117 | mouseX,
118 | title,
119 | icon,
120 | href,
121 | }: {
122 | mouseX: MotionValue;
123 | title: string;
124 | icon: React.ReactNode;
125 | href: string;
126 | }) {
127 | let ref = useRef(null);
128 |
129 | let distance = useTransform(mouseX, (val) => {
130 | let bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };
131 |
132 | return val - bounds.x - bounds.width / 2;
133 | });
134 |
135 | let widthTransform = useTransform(distance, [-150, 0, 150], [40, 80, 40]);
136 | let heightTransform = useTransform(distance, [-150, 0, 150], [40, 80, 40]);
137 |
138 | let widthTransformIcon = useTransform(distance, [-150, 0, 150], [20, 40, 20]);
139 | let heightTransformIcon = useTransform(
140 | distance,
141 | [-150, 0, 150],
142 | [20, 40, 20]
143 | );
144 |
145 | let width = useSpring(widthTransform, {
146 | mass: 0.1,
147 | stiffness: 150,
148 | damping: 12,
149 | });
150 | let height = useSpring(heightTransform, {
151 | mass: 0.1,
152 | stiffness: 150,
153 | damping: 12,
154 | });
155 |
156 | let widthIcon = useSpring(widthTransformIcon, {
157 | mass: 0.1,
158 | stiffness: 150,
159 | damping: 12,
160 | });
161 | let heightIcon = useSpring(heightTransformIcon, {
162 | mass: 0.1,
163 | stiffness: 150,
164 | damping: 12,
165 | });
166 |
167 | const [hovered, setHovered] = useState(false);
168 |
169 | return (
170 |
171 | setHovered(true)}
175 | onMouseLeave={() => setHovered(false)}
176 | className="aspect-square rounded-full bg-gray-200 dark:bg-transparent flex items-center justify-center relative"
177 | >
178 |
179 | {hovered && (
180 |
186 | {title}
187 |
188 | )}
189 |
190 |
194 | {icon}
195 |
196 |
197 |
198 | );
199 | }
200 |
--------------------------------------------------------------------------------
/src/data/index.ts:
--------------------------------------------------------------------------------
1 | export const initialFeedbacks = [
2 | {
3 | id: 1,
4 | text: "CopilotMate has significantly improved my productivity!",
5 | author: "John Doe",
6 | },
7 | {
8 | id: 2,
9 | text: "The features are intuitive and easy to use. Highly recommended!",
10 | author: "Jane Smith",
11 | },
12 | {
13 | id: 3,
14 | text: "A great tool for managing tasks efficiently.",
15 | author: "Alice Johnson",
16 | },
17 | {
18 | id: 4,
19 | text: "Love the AI capabilities in CopilotMate. It helps me stay organized!",
20 | author: "Bob Brown",
21 | },
22 | ];
23 |
24 |
25 | export const loadingStates = [
26 | {
27 | text: "Starting your assistant",
28 | },
29 | {
30 | text: "Syncing your tasks",
31 | },
32 | {
33 | text: "Organizing your calendar",
34 | },
35 | {
36 | text: "Preparing study tools",
37 | },
38 | {
39 | text: "Loading your chatbot",
40 | },
41 | {
42 | text: "All set! Welcome to CopilotMate",
43 | },
44 | ];
45 |
46 | export const faqTitle = [
47 | {
48 | text: "Frequently",
49 | },
50 | {
51 | text: "Asked",
52 | },
53 | {
54 | text: "Questions",
55 | className: "text-indigo-500 dark:text-indigo-500",
56 | },
57 | ];
58 |
59 | export const featureTitle = [
60 | { text: "Unlock" },
61 | { text: "Productivity" },
62 | { text: "with" },
63 | { text: "CopilotMate", className: "text-purple-500 dark:text-purple-500" },
64 |
65 | ];
66 |
67 | export const faq = [
68 | {
69 | id: "1",
70 | question: "What features are available right now?",
71 | answer:
72 | "Currently, the to-do list, spreadsheet, and chatbot features are ready for use. Other features, like expense tracking and calendar tools, are still in development.",
73 | },
74 |
75 | {
76 | id: "2",
77 | question: "Can I contribute to CopilotMate?",
78 | answer:
79 | "Absolutely! CopilotMate is open-source, and we welcome contributions. You can help by submitting pull requests, reporting bugs, or suggesting improvements.",
80 | },
81 | {
82 | id: "3",
83 | question: "How can I get started with CopilotMate?",
84 | answer:
85 | "You can get started by watching the demo video or by visiting our GitHub page to access the codebase. Follow the instructions to set up CopilotMate and start using the available features.",
86 | },
87 | {
88 | id: "4",
89 | question: "Can I customize CopilotMate?",
90 | answer:
91 | "Yes! Since CopilotMate is open-source, you can modify the code to fit your personal or project needs.",
92 | },
93 | {
94 | id: "5",
95 | question: "What are the upcoming features?",
96 | answer:
97 | "Some upcoming features include expense tracking, calendar integration, study tools, and quiz creation. Stay tuned for regular updates as we continue to develop.",
98 | },
99 | {
100 | id: "6",
101 | question: "Is CopilotMate suitable for teams?",
102 | answer:
103 | "Currently, CopilotMate is focused on individual use, but we may add team collaboration features in future updates.",
104 | },
105 | {
106 | id: "7",
107 | question: "Will CopilotMate always be free?",
108 | answer:
109 | "Yes, CopilotMate is open-source and free to use. You can access the codebase, contribute to development, or customize it as per your needs.",
110 | },
111 | {
112 | id: "8",
113 | question: "How can I report an issue or bug?",
114 | answer:
115 | "You can report issues directly through our GitHub repository by opening a new issue. We appreciate all feedback and contributions!",
116 | },
117 | {
118 | id: "9",
119 | question: "Do you have corporate or enterprise plans?",
120 | answer:
121 | "At the moment, CopilotMate is focused on individual users, but we're open to expanding based on community feedback.",
122 | },
123 | {
124 | id: "10",
125 | question: "Is CopilotMate open-source?",
126 | answer:
127 | "Yes, CopilotMate is fully open-source. You can contribute to its development, report bugs, or suggest features via our GitHub repository.",
128 | },
129 | ];
130 |
131 |
132 | export const firstWords = [
133 | "Streamline",
134 | "Optimize",
135 | "Elevate",
136 | "Automate",
137 | "Facilitate",
138 | "Boost",
139 | "Simplify",
140 | "Revolutionize",
141 | "Maximize",
142 | "Personalize",
143 | ];
144 | export const thirdWords = [
145 | "Processes",
146 | "Efficiency",
147 | "Projects",
148 | "Routine",
149 | "Collaboration",
150 | "Performance",
151 | "Work",
152 | "Approach",
153 | "Potential",
154 | "Experience",
155 | ];
156 |
157 | export const placeholders = [
158 | "What can CopilotMate help you with today?",
159 | "Ask me anything!",
160 | "Looking for something specific?",
161 | "Type your task or question...",
162 | "Need help organizing your day?",
163 | "Start typing to plan smarter...",
164 | "What would you like to do?",
165 | "How can I assist you?",
166 | "Let me find that for you...",
167 | "What’s on your mind?",
168 | ];
169 |
170 | export const socialMedia = [
171 | {
172 | id: 1,
173 | img: "/git.svg",
174 | href: "https://github.com/AkashJana18",
175 | },
176 | {
177 | id: 2,
178 | img: "/twit.svg",
179 | href: "https://x.com/Akashj_01",
180 | },
181 | {
182 | id: 3,
183 | img: "/link.svg",
184 | href: "https://www.linkedin.com/in/akashjana",
185 | },
186 | ];
187 |
188 | export const copilotMateFeatures = [
189 | {
190 | title: "Expenses Tracker",
191 | description: "Easily monitor and manage your expenses with real-time tracking and detailed reports.",
192 | thumbnail: "/accounting.png"
193 |
194 | },
195 | {
196 | title: "Daily Task Summary",
197 | description: "At the end of the day, provide a brief summary of tasks completed, reminders for the next day, or undone to-dos.",
198 | thumbnail: "/product-release.png"
199 | },
200 | {
201 | title: "To-Do List",
202 | description: "Organize tasks, set priorities, and get reminders with CopilotMate's to-do list feature.",
203 | thumbnail: "/event-list.png"
204 | },
205 | {
206 | title: "Study Assistant",
207 | description: "Use tools like flashcards, notes, and study reminders to stay on track academically.",
208 | thumbnail: "/reading.png"
209 | },
210 | {
211 | title: "AI spreadsheet",
212 | description: "Tailor your resume with AI suggestions, making it job-ready and customized for each role.",
213 | thumbnail: "/frequency.png"
214 | },
215 | {
216 | title: "AI ChatBot",
217 | description: "Get real-time assistance through a smart AI chatbot that helps with tasks, queries, and more.",
218 | thumbnail: "/chat-app.png"
219 | },
220 | ];
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # CopilotMate
3 | 
4 | 
5 | 
6 | 
7 |
8 |
9 |
10 | 
11 | CopilotMate is an open-source personal assistant designed to streamline productivity with an intuitive interface and a suite of essential tools. Whether you're organizing your tasks, tracking expenses, or studying efficiently, CopilotMate has your back! Built using **CopilotKit**, it integrates seamlessly to help you stay organized and focused.
12 |
13 | > **Note:**
14 | > Due to major updates in CoAgents and CopilotKit AI, the agent may not function as expected. Please review the latest changes before use.
15 |
16 |
17 | ### 🛠️ Technologies Being Used
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | - **CopilotKit** (for AI functionalities)
26 | - **Next.js** (for the frontend)
27 | - **Tailwind CSS** (for styling)
28 | - **Groq SDK** (for language models)
29 | - **Llama model** (for AI coagents)
30 |
31 | ### [Watch video on YouTube](https://youtu.be/qPVRPUH8ewU)
32 |
33 | ### Screenshot
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ## Blog
48 | Read more about CopilotMate in our detailed blog posts:
49 | 1. [CopilotMate Blog](https://dev.to/akashjana/future-of-productivity-meet-copilotmate-3k7i)
50 | 2. [CopilotMate Development Journey](https://dev.to/akashjana/how-i-integrated-copilotkit-ai-into-copilotmate-23gm)
51 | 3. Also, don't forget to support on [Twitter](https://x.com/Akashj_01/status/1843662122917736475) and [Linkedin](https://www.linkedin.com/feed/update/urn:li:activity:7254023926891065346/).
52 |
53 |
54 | ## Table of Contents
55 |
56 | - [Features](#features)
57 | - [Installation](#installation)
58 | - [Usage](#usage)
59 | - [Demo](https://youtu.be/qPVRPUH8ewU?si=R9TqpgmkYOL8n5lf)
60 | - [Contributing](#contributing)
61 | - [License](#license)
62 |
63 | ## Features
64 |
65 | CopilotMate currently supports the following features:
66 |
67 | - **To-Do Assistant**: Create, update, and manage your tasks effortlessly.
68 | - **Spreadsheet**: Organize your data in a spreadsheet format.
69 | - **Chatbot**: Engage with a chatbot to answer your queries and assist in tasks.
70 | - **Expense Tracker**: Track and categorize your daily expenses with an improved dark-themed UI and glass effect for a sleek look.
71 | - **StudyBuddy Coagent**: A coagent that helps with study planning, note-taking, and quiz creation for focused learning sessions.
72 |
73 | **Upcoming Updates**:
74 | - **Calendar**: Keep up with important dates and events (coming soon!).
75 |
76 | ## What's New in v2.0?
77 |
78 | - **StudyBuddy Coagent**: Added a StudyBuddy tool for note-taking and quiz creation to boost your learning productivity.
79 | - **Expense Tracker**: Now available with a polished dark UI and glass effect to make tracking your expenses more enjoyable.
80 | - **Improved UI/UX**: Revamped interface for a smoother user experience and a more modern look.
81 |
82 | ## Installation
83 |
84 | ### Prerequisites
85 |
86 | - [Node.js](https://nodejs.org/) (v14 or later)
87 | - [Next.js](https://nextjs.org/)
88 | - [CopilotKit](https://docs.copilotkit.ai/what-is-copilotkit)
89 |
90 | ### Steps
91 |
92 | 1. Clone this repository:
93 |
94 | ```bash
95 | git clone https://github.com/yourusername/copilotmate.git
96 | ```
97 |
98 | ## Running the Agent
99 |
100 | First, install the dependencies:
101 |
102 | ```bash
103 | cd agent
104 | poetry install
105 | ```
106 |
107 | Then, create a `.env` file inside `./agent` with the following:
108 |
109 | ```bash
110 | GROQ_API_KEY=...
111 | TAVILY_API_KEY=...
112 | ```
113 |
114 | Then, run the demo:
115 |
116 | ```bash
117 | poetry run demo
118 | ```
119 |
120 | 2. Navigate to the project directory:
121 |
122 | ```bash
123 | cd copilotmate
124 | ```
125 |
126 | 3. Install the required dependencies:
127 |
128 | ```bash
129 | npm install
130 | ```
131 |
132 | 4. Start the development server:
133 |
134 | ```bash
135 | npm run dev
136 | ```
137 |
138 | 5. Open the app in your browser:
139 |
140 | ```bash
141 | http://localhost:3000
142 | ```
143 |
144 | ## Usage
145 |
146 | Once installed, you can access the following features:
147 |
148 | - **To-Do Assistant**: Navigate to `/todo` to manage your tasks. You can add, edit, mark complete, and delete tasks.
149 |
150 | - **Spreadsheet**: Access the spreadsheet at `/spreadsheet` to manage your data. Organize your records using rows and columns.
151 |
152 | - **Chatbot**: Go to `/chatbot` to interact with the AI-powered assistant for general queries and task automation.
153 |
154 | - **Expense Tracker**: Visit `/expense-tracker` to start tracking your expenses. The improved dark UI will keep you focused on your financials with style.
155 |
156 | - **StudyBuddy Coagent**: Head over to `/studybuddy` for study tools that help you plan, create quizzes, and organize notes effectively.
157 |
158 | More routes and features are currently being developed.
159 |
160 | ## Contributing
161 |
162 | We welcome contributions from the community! To get started:
163 |
164 | 1. Fork the repository.
165 | 2. Create a new branch for your feature or bugfix.
166 | 3. Make your changes and test them thoroughly.
167 | 4. Submit a pull request with a clear description of your changes.
168 |
169 | Please refer to our [Contributing Guide](CONTRIBUTING.md) for more details.
170 |
171 | ## License
172 |
173 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
174 |
175 |
176 |
--------------------------------------------------------------------------------
/src/components/ui/background-beams-with-collision.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { cn } from "@/lib/utils";
3 | import { motion, AnimatePresence } from "framer-motion";
4 | import React, { useRef, useState, useEffect } from "react";
5 |
6 | export const BackgroundBeamsWithCollision = ({
7 | children,
8 | className,
9 | }: {
10 | children: React.ReactNode;
11 | className?: string;
12 | }) => {
13 | const containerRef = useRef(null);
14 | const parentRef = useRef(null);
15 |
16 | const beams = [
17 | {
18 | initialX: 10,
19 | translateX: 10,
20 | duration: 7,
21 | repeatDelay: 3,
22 | delay: 2,
23 | },
24 | {
25 | initialX: 600,
26 | translateX: 600,
27 | duration: 3,
28 | repeatDelay: 3,
29 | delay: 4,
30 | },
31 | {
32 | initialX: 100,
33 | translateX: 100,
34 | duration: 7,
35 | repeatDelay: 7,
36 | className: "h-6",
37 | },
38 | {
39 | initialX: 400,
40 | translateX: 400,
41 | duration: 5,
42 | repeatDelay: 14,
43 | delay: 4,
44 | },
45 | {
46 | initialX: 800,
47 | translateX: 800,
48 | duration: 11,
49 | repeatDelay: 2,
50 | className: "h-20",
51 | },
52 | {
53 | initialX: 1000,
54 | translateX: 1000,
55 | duration: 4,
56 | repeatDelay: 2,
57 | className: "h-12",
58 | },
59 | {
60 | initialX: 1200,
61 | translateX: 1200,
62 | duration: 6,
63 | repeatDelay: 4,
64 | delay: 2,
65 | className: "h-6",
66 | },
67 | ];
68 |
69 | return (
70 |
78 | {beams.map((beam) => (
79 |
85 | ))}
86 |
87 | {children}
88 |
96 |
97 | );
98 | };
99 |
100 | const CollisionMechanism = React.forwardRef<
101 | HTMLDivElement,
102 | {
103 | containerRef: React.RefObject;
104 | parentRef: React.RefObject;
105 | beamOptions?: {
106 | initialX?: number;
107 | translateX?: number;
108 | initialY?: number;
109 | translateY?: number;
110 | rotate?: number;
111 | className?: string;
112 | duration?: number;
113 | delay?: number;
114 | repeatDelay?: number;
115 | };
116 | }
117 | >(({ parentRef, containerRef, beamOptions = {} }, ref) => {
118 | const beamRef = useRef(null);
119 | const [collision, setCollision] = useState<{
120 | detected: boolean;
121 | coordinates: { x: number; y: number } | null;
122 | }>({
123 | detected: false,
124 | coordinates: null,
125 | });
126 | const [beamKey, setBeamKey] = useState(0);
127 | const [cycleCollisionDetected, setCycleCollisionDetected] = useState(false);
128 |
129 | useEffect(() => {
130 | const checkCollision = () => {
131 | if (
132 | beamRef.current &&
133 | containerRef.current &&
134 | parentRef.current &&
135 | !cycleCollisionDetected
136 | ) {
137 | const beamRect = beamRef.current.getBoundingClientRect();
138 | const containerRect = containerRef.current.getBoundingClientRect();
139 | const parentRect = parentRef.current.getBoundingClientRect();
140 |
141 | if (beamRect.bottom >= containerRect.top) {
142 | const relativeX =
143 | beamRect.left - parentRect.left + beamRect.width / 2;
144 | const relativeY = beamRect.bottom - parentRect.top;
145 |
146 | setCollision({
147 | detected: true,
148 | coordinates: {
149 | x: relativeX,
150 | y: relativeY,
151 | },
152 | });
153 | setCycleCollisionDetected(true);
154 | }
155 | }
156 | };
157 |
158 | const animationInterval = setInterval(checkCollision, 50);
159 |
160 | return () => clearInterval(animationInterval);
161 | }, [cycleCollisionDetected, containerRef]);
162 |
163 | useEffect(() => {
164 | if (collision.detected && collision.coordinates) {
165 | setTimeout(() => {
166 | setCollision({ detected: false, coordinates: null });
167 | setCycleCollisionDetected(false);
168 | }, 2000);
169 |
170 | setTimeout(() => {
171 | setBeamKey((prevKey) => prevKey + 1);
172 | }, 2000);
173 | }
174 | }, [collision]);
175 |
176 | return (
177 | <>
178 |
207 |
208 | {collision.detected && collision.coordinates && (
209 |
218 | )}
219 |
220 | >
221 | );
222 | });
223 |
224 | CollisionMechanism.displayName = "CollisionMechanism";
225 |
226 | const Explosion = ({ ...props }: React.HTMLProps) => {
227 | const spans = Array.from({ length: 20 }, (_, index) => ({
228 | id: index,
229 | initialX: 0,
230 | initialY: 0,
231 | directionX: Math.floor(Math.random() * 80 - 40),
232 | directionY: Math.floor(Math.random() * -50 - 10),
233 | }));
234 |
235 | return (
236 |
237 |
244 | {spans.map((span) => (
245 |
256 | ))}
257 |
258 | );
259 | };
260 |
--------------------------------------------------------------------------------
/src/components/ui/placeholders-and-vanish-input.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { AnimatePresence, motion } from "framer-motion";
4 | import { useCallback, useEffect, useRef, useState } from "react";
5 | import { cn } from "@/lib/utils";
6 |
7 | export function PlaceholdersAndVanishInput({
8 | placeholders,
9 | onChange,
10 | onSubmit,
11 | }: {
12 | placeholders: string[];
13 | onChange: (e: React.ChangeEvent) => void;
14 | onSubmit: (e: React.FormEvent) => void;
15 | }) {
16 | const [currentPlaceholder, setCurrentPlaceholder] = useState(0);
17 |
18 | const intervalRef = useRef(null);
19 | const startAnimation = () => {
20 | intervalRef.current = setInterval(() => {
21 | setCurrentPlaceholder((prev) => (prev + 1) % placeholders.length);
22 | }, 5000);
23 | };
24 | const handleVisibilityChange = () => {
25 | if (document.visibilityState !== "visible" && intervalRef.current) {
26 | clearInterval(intervalRef.current); // Clear the interval when the tab is not visible
27 | intervalRef.current = null;
28 | } else if (document.visibilityState === "visible") {
29 | startAnimation(); // Restart the interval when the tab becomes visible
30 | }
31 | };
32 |
33 | useEffect(() => {
34 | startAnimation();
35 | document.addEventListener("visibilitychange", handleVisibilityChange);
36 |
37 | return () => {
38 | if (intervalRef.current) {
39 | clearInterval(intervalRef.current);
40 | }
41 | document.removeEventListener("visibilitychange", handleVisibilityChange);
42 | };
43 | }, [placeholders]);
44 |
45 | const canvasRef = useRef(null);
46 | const newDataRef = useRef([]);
47 | const inputRef = useRef(null);
48 | const [value, setValue] = useState("");
49 | const [animating, setAnimating] = useState(false);
50 |
51 | const draw = useCallback(() => {
52 | if (!inputRef.current) return;
53 | const canvas = canvasRef.current;
54 | if (!canvas) return;
55 | const ctx = canvas.getContext("2d");
56 | if (!ctx) return;
57 |
58 | canvas.width = 800;
59 | canvas.height = 800;
60 | ctx.clearRect(0, 0, 800, 800);
61 | const computedStyles = getComputedStyle(inputRef.current);
62 |
63 | const fontSize = parseFloat(computedStyles.getPropertyValue("font-size"));
64 | ctx.font = `${fontSize * 2}px ${computedStyles.fontFamily}`;
65 | ctx.fillStyle = "#FFF";
66 | ctx.fillText(value, 16, 40);
67 |
68 | const imageData = ctx.getImageData(0, 0, 800, 800);
69 | const pixelData = imageData.data;
70 | const newData: any[] = [];
71 |
72 | for (let t = 0; t < 800; t++) {
73 | let i = 4 * t * 800;
74 | for (let n = 0; n < 800; n++) {
75 | let e = i + 4 * n;
76 | if (
77 | pixelData[e] !== 0 &&
78 | pixelData[e + 1] !== 0 &&
79 | pixelData[e + 2] !== 0
80 | ) {
81 | newData.push({
82 | x: n,
83 | y: t,
84 | color: [
85 | pixelData[e],
86 | pixelData[e + 1],
87 | pixelData[e + 2],
88 | pixelData[e + 3],
89 | ],
90 | });
91 | }
92 | }
93 | }
94 |
95 | newDataRef.current = newData.map(({ x, y, color }) => ({
96 | x,
97 | y,
98 | r: 1,
99 | color: `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`,
100 | }));
101 | }, [value]);
102 |
103 | useEffect(() => {
104 | draw();
105 | }, [value, draw]);
106 |
107 | const animate = (start: number) => {
108 | const animateFrame = (pos: number = 0) => {
109 | requestAnimationFrame(() => {
110 | const newArr = [];
111 | for (let i = 0; i < newDataRef.current.length; i++) {
112 | const current = newDataRef.current[i];
113 | if (current.x < pos) {
114 | newArr.push(current);
115 | } else {
116 | if (current.r <= 0) {
117 | current.r = 0;
118 | continue;
119 | }
120 | current.x += Math.random() > 0.5 ? 1 : -1;
121 | current.y += Math.random() > 0.5 ? 1 : -1;
122 | current.r -= 0.05 * Math.random();
123 | newArr.push(current);
124 | }
125 | }
126 | newDataRef.current = newArr;
127 | const ctx = canvasRef.current?.getContext("2d");
128 | if (ctx) {
129 | ctx.clearRect(pos, 0, 800, 800);
130 | newDataRef.current.forEach((t) => {
131 | const { x: n, y: i, r: s, color: color } = t;
132 | if (n > pos) {
133 | ctx.beginPath();
134 | ctx.rect(n, i, s, s);
135 | ctx.fillStyle = color;
136 | ctx.strokeStyle = color;
137 | ctx.stroke();
138 | }
139 | });
140 | }
141 | if (newDataRef.current.length > 0) {
142 | animateFrame(pos - 8);
143 | } else {
144 | setValue("");
145 | setAnimating(false);
146 | }
147 | });
148 | };
149 | animateFrame(start);
150 | };
151 |
152 | const handleKeyDown = (e: React.KeyboardEvent) => {
153 | if (e.key === "Enter" && !animating) {
154 | vanishAndSubmit();
155 | }
156 | };
157 |
158 | const vanishAndSubmit = () => {
159 | setAnimating(true);
160 | draw();
161 |
162 | const value = inputRef.current?.value || "";
163 | if (value && inputRef.current) {
164 | const maxX = newDataRef.current.reduce(
165 | (prev, current) => (current.x > prev ? current.x : prev),
166 | 0
167 | );
168 | animate(maxX);
169 | }
170 | };
171 |
172 | const handleSubmit = (e: React.FormEvent) => {
173 | e.preventDefault();
174 | vanishAndSubmit();
175 | onSubmit && onSubmit(e);
176 | };
177 | return (
178 |
185 |
192 | {
194 | if (!animating) {
195 | setValue(e.target.value);
196 | onChange && onChange(e);
197 | }
198 | }}
199 | onKeyDown={handleKeyDown}
200 | ref={inputRef}
201 | value={value}
202 | type="text"
203 | className={cn(
204 | "w-full relative text-sm sm:text-base z-50 border-none dark:text-white bg-transparent text-black h-full rounded-full focus:outline-none focus:ring-0 pl-4 sm:pl-10 pr-20",
205 | animating && "text-transparent dark:text-transparent"
206 | )}
207 | />
208 |
209 |
214 |
226 |
227 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 | {!value && (
249 |
269 | {placeholders[currentPlaceholder]}
270 |
271 | )}
272 |
273 |
274 |
275 | );
276 | }
277 |
--------------------------------------------------------------------------------
/src/components/ui/background-beams.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { motion } from "framer-motion";
4 | import { cn } from "@/lib/utils";
5 |
6 | export const BackgroundBeams = React.memo(
7 | ({ className }: { className?: string }) => {
8 | const paths = [
9 | "M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875",
10 | "M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867",
11 | "M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859",
12 | "M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851",
13 | "M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843",
14 | "M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835",
15 | "M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827",
16 | "M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819",
17 | "M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811",
18 | "M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803",
19 | "M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795",
20 | "M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787",
21 | "M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779",
22 | "M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771",
23 | "M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763",
24 | "M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755",
25 | "M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747",
26 | "M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739",
27 | "M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731",
28 | "M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723",
29 | "M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715",
30 | "M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707",
31 | "M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699",
32 | "M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691",
33 | "M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683",
34 | "M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675",
35 | "M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667",
36 | "M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659",
37 | "M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651",
38 | "M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643",
39 | "M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635",
40 | "M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627",
41 | "M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619",
42 | "M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611",
43 | "M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603",
44 | "M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595",
45 | "M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587",
46 | "M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579",
47 | "M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571",
48 | "M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563",
49 | "M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555",
50 | "M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547",
51 | "M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539",
52 | "M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531",
53 | "M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523",
54 | "M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515",
55 | "M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507",
56 | "M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499",
57 | "M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491",
58 | "M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483",
59 | ];
60 | return (
61 |
67 |
75 |
81 |
82 | {paths.map((path, index) => (
83 |
90 | ))}
91 |
92 | {paths.map((path, index) => (
93 |
115 |
116 |
117 |
118 |
119 |
120 | ))}
121 |
122 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | );
138 | }
139 | );
140 |
141 | BackgroundBeams.displayName = "BackgroundBeams";
142 |
--------------------------------------------------------------------------------