chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:18 +08:00
commit 6d5d58c1a9
18293 changed files with 3502153 additions and 0 deletions
@@ -0,0 +1,3 @@
OPENAI_API_KEY=xxxxxxx
OPENAI_MODEL=gpt-4-1106-preview
TAVILY_API_KEY=xxxxxxx
+39
View File
@@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
.env
# typescript
*.tsbuildinfo
next-env.d.ts
+62
View File
@@ -0,0 +1,62 @@
## 🧮 CopilotKit Spreadsheet Demo
This is a demo that showcases using CopilotKit to build an Excel like web app.
## 🧑‍💻 Run the live demo
Want to see CopilotKit in action? Click the button below to try the live demo.
<a href="https://x.com/copilotkit](https://spreadsheet-demo-tau.vercel.app/" target="_blank">
Run Demo →
</a>
<br></br>
![banner](https://github.com/user-attachments/assets/992b06ae-be6c-4bd2-ae57-20a793688e78)
## 🛠️ How to Build: A spreadsheet app with an AI-copilot
Learn how to create a powerful spreadsheet app using CopilotKit. This tutorial will guide you through the process step-by-step.
Tutorial: [How to Build: A spreadsheet app with an AI-copilot](https://dev.to/copilotkit/build-an-ai-powered-spreadsheet-app-nextjs-langchain-copilotkit-109d)
## 🚀 Getting Started
### 1. install the needed package:
```bash
npm i
```
### 2. Set the required environment variables:
copy `.env.local.example` to `.env.local` and populate the required environment variables.
### 3. Run the app
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
### 4. Use the Copilot
Ask it to build a budget spreadsheet.
## 🔍 Zoom in on the CopilotKit code
1. Look for `/api/copilotkit/route.ts` and `/api/copilotkit/tavily.ts` - for the research agent integrated into the spreadsheet
2. Look for `useCopilotReadable` to see where frontend application context is being made accessible to the Copilot engine
3. Search for `updateSpreadsheet`, `appendToSpreadsheet`, and `createSpreadsheet` to see application interaction hooks made available to agents.
## 📚 Learn More
To learn more about CopilotKit, take a look at the following resources:
- [CopilotKit Documentation](https://docs.copilotkit.ai/getting-started/quickstart-chatbot) - learn about CopilotKit features and API.
- [GitHub](https://github.com/CopilotKit/CopilotKit) - Check out the CopilotKit GitHub repository.
- [Discord](https://discord.gg/6dffbvGU3D) - Join the CopilotKit Discord community.
@@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
};
export default nextConfig;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
{
"name": "spreadsheet-demo",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@copilotkit/react-core": "^1.8.12-next.3",
"@copilotkit/react-textarea": "^1.8.12-next.3",
"@copilotkit/react-ui": "^1.8.12-next.3",
"@copilotkit/runtime": "^1.8.12-next.3",
"@copilotkit/shared": "^1.8.12-next.3",
"@heroicons/react": "^2.2.0",
"@langchain/community": "^0.3.17",
"@langchain/core": "^0.3.20",
"@langchain/langgraph": "^0.2.24",
"@langchain/openai": "^0.3.14",
"@syncfusion/ej2-react-spreadsheet": "^29.1.39",
"langchain": "^0.3.6",
"next": "15.5.15",
"openai": "^4.96.2",
"react": "^18",
"react-dom": "^18",
"react-spreadsheet": "^0.9.5",
"scheduler": "^0.23.2"
},
"devDependencies": {
"@types/node": "^22",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.4.20",
"postcss": "^8",
"tailwindcss": "^3.4.16",
"typescript": "^5"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

@@ -0,0 +1,146 @@
import { TavilySearchAPIRetriever } from "@langchain/community/retrievers/tavily_search_api";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { RunnableLambda } from "@langchain/core/runnables";
import { Annotation, END, MemorySaver, StateGraph } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
interface AgentState {
topic: string;
searchResults?: string;
article?: string;
critique?: string;
}
const StateAnnotation = Annotation.Root({
agentState: Annotation<AgentState>({
value: (x: AgentState, y: AgentState) => y,
default: () => ({
topic: "",
}),
}),
});
function model() {
return new ChatOpenAI({
temperature: 0,
modelName: "gpt-3.5-turbo-0125",
});
}
async function search(state: typeof StateAnnotation.State) {
const retriever = new TavilySearchAPIRetriever({ k: 10 });
let topic = state.agentState.topic;
if (topic.length < 5) topic = "topic: " + topic;
const docs = await retriever.invoke(topic);
return {
agentState: { ...state.agentState, searchResults: JSON.stringify(docs) },
};
}
async function curate(state: typeof StateAnnotation.State) {
const response = await model().invoke(
[
new SystemMessage(
'Return 5 most relevant article URLs as JSON: {urls: ["url1",...]}',
),
new HumanMessage(
`Topic: ${state.agentState.topic}\nArticles: ${state.agentState.searchResults}`,
),
],
{ response_format: { type: "json_object" } },
);
const urls = JSON.parse(response.content as string).urls;
const searchResults = JSON.parse(state.agentState.searchResults!);
const filtered = searchResults.filter((r: any) =>
urls.includes(r.metadata.source),
);
return {
agentState: {
...state.agentState,
searchResults: JSON.stringify(filtered),
},
};
}
async function write(state: typeof StateAnnotation.State) {
const response = await model().invoke([
new SystemMessage("Write a 5-paragraph article in markdown."),
new HumanMessage(
`Topic: ${state.agentState.topic}\nSources: ${state.agentState.searchResults}`,
),
]);
return {
agentState: { ...state.agentState, article: response.content as string },
};
}
async function critique(state: typeof StateAnnotation.State) {
const feedback = state.agentState.critique
? `Previous critique: ${state.agentState.critique}`
: "";
const response = await model().invoke([
new SystemMessage(
"Review article. Return [DONE] if good, or provide brief feedback.",
),
new HumanMessage(`${feedback}\nArticle: ${state.agentState.article}`),
]);
const content = response.content as string;
return {
agentState: {
...state.agentState,
critique: content.includes("[DONE]") ? undefined : content,
},
};
}
async function revise(state: typeof StateAnnotation.State) {
const response = await model().invoke([
new SystemMessage("Edit article based on critique."),
new HumanMessage(
`Article: ${state.agentState.article}\nCritique: ${state.agentState.critique}`,
),
]);
return {
agentState: { ...state.agentState, article: response.content as string },
};
}
function shouldContinue(state: typeof StateAnnotation.State) {
return state.agentState.critique === undefined ? "end" : "continue";
}
export async function createNewspaperWorkflow() {
const workflow = new StateGraph(StateAnnotation)
.addNode("search", new RunnableLambda({ func: search }))
.addNode("curate", new RunnableLambda({ func: curate }))
.addNode("write", new RunnableLambda({ func: write }))
.addNode("critique", new RunnableLambda({ func: critique }))
.addNode("revise", new RunnableLambda({ func: revise }))
.addEdge("search", "curate")
.addEdge("curate", "write")
.addEdge("write", "critique")
.addConditionalEdges("critique", shouldContinue, {
continue: "revise",
end: END,
})
.addEdge("revise", "critique")
.addEdge("__start__", "search");
const checkpointer = new MemorySaver();
return workflow.compile({ checkpointer });
}
export async function researchWithLangGraph(topic: string) {
const app = await createNewspaperWorkflow();
const result = await app.invoke(
{ agentState: { topic } },
{
configurable: { thread_id: "research-" + Date.now(), checkpoint_id: "1" },
},
);
return result?.agentState?.article?.replace(
/<FEEDBACK>[\s\S]*?<\/FEEDBACK>/g,
"",
);
}
@@ -0,0 +1,69 @@
import { Action } from "@copilotkit/shared";
import { researchWithLangGraph } from "./research";
import { NextRequest } from "next/server";
import {
CopilotRuntime,
LangChainAdapter,
copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
import { ChatOpenAI } from "@langchain/openai";
const researchAction: Action<any> = {
name: "research",
description:
"Call this function to conduct research on a certain topic. Respect other notes about when to call this function",
parameters: [
{
name: "topic",
type: "string",
description: "The topic to research. 5 characters or longer.",
},
],
handler: async ({ topic }) => {
console.log("Researching topic: ", topic);
return await researchWithLangGraph(topic);
},
};
const actions: Action<any>[] = [];
if (process.env["TAVILY_API_KEY"] && process.env["TAVILY_API_KEY"] !== "NONE") {
actions.push(researchAction);
}
const model = new ChatOpenAI({
modelName: "gpt-4o-mini",
temperature: 0,
apiKey: process.env["OPENAI_API_KEY"],
});
export const POST = async (req: NextRequest) => {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime: new CopilotRuntime({
actions: actions,
}),
serviceAdapter: new LangChainAdapter({
chainFn: async ({ messages, tools, threadId }) => {
console.log("POST messages: ", messages);
console.log("POST tools: ", Object.keys(tools));
console.log(
"POST tools: ",
tools.map((tool) => {
return {
name: tool.lc_kwargs.name,
func: JSON.stringify(tool.lc_kwargs.func),
};
}),
);
console.log("POST threadId: ", threadId);
const modelWithTools = model.bindTools(tools, { strict: true });
return modelWithTools.stream(messages, {
tools,
metadata: { conversation_id: threadId },
});
},
}),
endpoint: req.nextUrl.pathname,
});
return handleRequest(req);
};
@@ -0,0 +1,36 @@
import OpenAI from "openai";
export async function research(query: string) {
const response = await fetch("https://api.tavily.com/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
api_key: process.env.TAVILY_API_KEY,
query,
search_depth: "basic",
include_answer: true,
include_images: false,
include_raw_content: false,
max_results: 20,
}),
});
const responseJson = await response.json();
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
messages: [
{
role: "system",
content: `Summarize the following JSON to answer the research query \`"${query}"\`: ${JSON.stringify(
responseJson,
)} in plain English.`,
},
],
model: process.env.OPENAI_MODEL || "gpt-4",
});
return completion.choices[0].message.content;
}
@@ -0,0 +1,31 @@
import { OpenAI } from "openai";
// export const runtime = "edge";
const openai = new OpenAI();
export async function POST(req: Request): Promise<Response> {
try {
const formData = await req.formData();
const file = formData.get("file") as File;
if (!file) {
return new Response("File not provided", { status: 400 });
}
const transcription = await openai.audio.transcriptions.create({
file,
model: "whisper-1",
});
return new Response(JSON.stringify(transcription), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error: any) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
@@ -0,0 +1,22 @@
import { OpenAI } from "openai";
export const runtime = "edge";
export async function GET(req: Request): Promise<Response> {
const openai = new OpenAI();
const url = new URL(req.url);
const text = url.searchParams.get("text"); // 'text' is the query parameter name
if (!text) {
return new Response("Text parameter is missing", { status: 400 });
}
const response = await openai.audio.speech.create({
voice: "alloy",
input: text,
model: "tts-1",
});
return response;
}
@@ -0,0 +1,53 @@
import { CheckCircleIcon } from "@heroicons/react/20/solid";
import { SpreadsheetRow } from "../types";
import { useState } from "react";
import Spreadsheet from "react-spreadsheet";
export interface PreviewSpreadsheetChanges {
preCommitTitle: string;
postCommitTitle: string;
newRows: SpreadsheetRow[];
commit: (rows: SpreadsheetRow[]) => void;
}
export function PreviewSpreadsheetChanges(props: PreviewSpreadsheetChanges) {
const [changesCommitted, setChangesCommitted] = useState(false);
const commitChangesButton = () => {
return (
<button
className="inline-flex items-center gap-x-2 rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
onClick={() => {
props.commit(props.newRows);
setChangesCommitted(true);
}}
>
{props.preCommitTitle}
</button>
);
};
const changesCommittedButtonPlaceholder = () => {
return (
<button
className=" inline-flex items-center gap-x-2 rounded-md bg-gray-100 px-3.5 py-2.5 text-sm font-semibold text-green-600 shadow-sm cursor-not-allowed"
disabled
>
{props.postCommitTitle}
<CheckCircleIcon className="-mr-0.5 h-5 w-5" aria-hidden="true" />
</button>
);
};
return (
<div className="flex flex-col">
<Spreadsheet data={props.newRows} />
<div className="mt-5">
{changesCommitted
? changesCommittedButtonPlaceholder()
: commitChangesButton()}
</div>
</div>
);
}
@@ -0,0 +1,71 @@
import React, { useContext } from "react";
import { SpreadsheetData } from "../types";
import { ThemeContext } from "./ThemeProvider";
interface SidebarProps {
spreadsheets: SpreadsheetData[];
selectedSpreadsheetIndex: number;
setSelectedSpreadsheetIndex: (index: number) => void;
}
const Sidebar = ({
spreadsheets,
selectedSpreadsheetIndex,
setSelectedSpreadsheetIndex,
}: SidebarProps) => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div className="w-64 h-screen bg-gray-800 text-white overflow-auto p-5">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-bold">Spreadsheets</h2>
<button
onClick={toggleTheme}
className="p-2 rounded-full hover:bg-gray-700"
aria-label="Toggle theme"
>
{theme === "light" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
/>
</svg>
)}
</button>
</div>
<ul>
{spreadsheets.map((spreadsheet, index) => (
<li
key={index}
className={`mb-4 cursor-pointer ${
index === selectedSpreadsheetIndex
? "ring-2 ring-blue-500 ring-inset p-3 rounded-lg"
: "p-3"
}`}
onClick={() => setSelectedSpreadsheetIndex(index)}
>
{spreadsheet.title}
</li>
))}
</ul>
</div>
);
};
export default Sidebar;
@@ -0,0 +1,237 @@
import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core";
import React, { useContext, useState } from "react";
import Spreadsheet from "react-spreadsheet";
import { canonicalSpreadsheetData } from "../utils/canonicalSpreadsheetData";
import { SpreadsheetData, SpreadsheetRow } from "../types";
import { PreviewSpreadsheetChanges } from "./PreviewSpreadsheetChanges";
import { ThemeContext } from "./ThemeProvider";
interface MainAreaProps {
spreadsheet: SpreadsheetData;
setSpreadsheet: (spreadsheet: SpreadsheetData) => void;
spreadSheets: SpreadsheetData[];
selectedSpreadsheetIndex: number;
setSelectedSpreadsheetIndex: (index: number) => void;
}
const SingleSpreadsheet = ({
spreadsheet,
setSpreadsheet,
spreadSheets,
selectedSpreadsheetIndex,
setSelectedSpreadsheetIndex,
}: MainAreaProps) => {
const { theme, toggleTheme } = useContext(ThemeContext);
useCopilotReadable({
description: "The current spreadsheet",
value: spreadsheet,
});
useCopilotAction({
name: "suggestSpreadsheetOverride",
description: "Suggest an override of the current spreadsheet",
parameters: [
{
name: "rows",
type: "object[]",
description: "The rows of the spreadsheet",
attributes: [
{
name: "cells",
type: "object[]",
description: "The cells of the row",
attributes: [
{
name: "value",
type: "string",
description: "The value of the cell",
},
],
},
],
},
{
name: "title",
type: "string",
description: "The title of the spreadsheet",
required: false,
nullable: true,
},
],
render: (props) => {
const { rows } = props.args;
const newRows = canonicalSpreadsheetData(rows);
return (
<PreviewSpreadsheetChanges
preCommitTitle="Replace contents"
postCommitTitle="Changes committed"
newRows={newRows}
commit={(rows) => {
const updatedSpreadsheet: SpreadsheetData = {
title: spreadsheet.title,
rows: rows,
};
setSpreadsheet(updatedSpreadsheet);
}}
/>
);
},
handler: ({ rows, title }) => {
// Do nothing.
// The preview component will optionally handle committing the changes.
},
});
useCopilotAction({
name: "appendToSpreadsheet",
description: "Append rows to the current spreadsheet",
parameters: [
{
name: "rows",
type: "object[]",
description: "The new rows of the spreadsheet",
attributes: [
{
name: "cells",
type: "object[]",
description: "The cells of the row",
attributes: [
{
name: "value",
type: "string",
description: "The value of the cell",
},
],
},
],
},
],
render: (props) => {
const status = props.status;
const { rows } = props.args;
const newRows = canonicalSpreadsheetData(rows);
return (
<div>
<p>Status: {status}</p>
<Spreadsheet data={newRows} />
</div>
);
},
handler: ({ rows }) => {
const canonicalRows = canonicalSpreadsheetData(rows);
const updatedSpreadsheet: SpreadsheetData = {
title: spreadsheet.title,
rows: [...spreadsheet.rows, ...canonicalRows],
};
setSpreadsheet(updatedSpreadsheet);
},
});
return (
<>
<div className="flex-1 overflow-auto p-5">
<input
type="text"
value={spreadsheet.title}
className="w-full p-2 mb-5 text-center text-2xl font-bold outline-none bg-transparent"
onChange={(e) =>
setSpreadsheet({ ...spreadsheet, title: e.target.value })
}
/>
<div className="flex items-start">
<Spreadsheet
data={spreadsheet.rows}
onChange={(data) => {
console.log("data", data);
setSpreadsheet({ ...spreadsheet, rows: data as any });
}}
/>
<button
className="bg-blue-500 text-white rounded-lg w-8 h-8 ml-5 "
onClick={() => {
// add an empty cell to each row
const spreadsheetRows = [...spreadsheet.rows];
for (let i = 0; i < spreadsheet.rows.length; i++) {
spreadsheet.rows[i].push({ value: "" });
}
setSpreadsheet({
...spreadsheet,
rows: spreadsheetRows,
});
}}
>
+
</button>
</div>
<button
style={{ marginBottom: 200 }}
className="bg-blue-500 text-white rounded-lg w-8 h-8 mt-5 "
onClick={() => {
const numberOfColumns = spreadsheet.rows[0].length;
const newRow: SpreadsheetRow = [];
for (let i = 0; i < numberOfColumns; i++) {
newRow.push({ value: "" });
}
setSpreadsheet({
...spreadsheet,
rows: [...spreadsheet.rows, newRow],
});
}}
>
+
</button>
</div>
<div>
<div className="fixed bottom-0 left-0 right-0 bg-gray-100 text-white flex items-center justify-between p-2 shadow-lg">
<div className="flex space-x-2">
{/* Replace with dynamic sheet buttons */}
{spreadSheets.map((sheet, index) => (
<button
key={index}
className={`${selectedSpreadsheetIndex == index ? "bg-blue-100 text-blue-600 font-bold" : "bg-gray-100 text-black"} px-4 py-2 rounded hover:bg-gray-300 transition`}
onClick={() => {
setSelectedSpreadsheetIndex(index);
}}
>
{sheet.title}
</button>
))}
<button
onClick={toggleTheme}
className="p-2 rounded-full bg-gray-500 hover:bg-gray-700"
aria-label="Toggle theme"
>
{theme === "light" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
/>
</svg>
)}
</button>
</div>
</div>
</div>
</>
);
};
export default SingleSpreadsheet;
@@ -0,0 +1,32 @@
"use client";
import { createContext, useState, useEffect, ReactNode } from "react";
// Create a theme context
export const ThemeContext = createContext({
theme: "light",
toggleTheme: () => {},
});
interface ThemeProviderProps {
children: ReactNode;
}
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
};
useEffect(() => {
// Apply theme class to the document element
document.documentElement.classList.remove("light", "dark");
document.documentElement.classList.add(theme);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,41 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
html.dark {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
/* Dark mode overrides */
html.dark .bg-gray-800 {
background-color: #111827;
/* Darker shade */
}
html.dark .ring-blue-500 {
--tw-ring-color: #3b82f6;
}
@@ -0,0 +1,20 @@
export const INSTRUCTIONS = `
You assist the user with a spreadsheet. Basic formulas are permitted, for example \`=SUM(B1:B9)\`.
When setting numbers, don't use commas. For example, use 1000 instead of 1,000.
Don't use words like 'million' etc, just the numbers.
When the user prompts you to create a new spreadsheet, focus on providing interesting numerical data and
insert formulas when appropriate.
If you have values that are a range, create a column for min and one for max.
Available functions in formulas:
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,
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,
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,
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,
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,
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,
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,
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
`;
@@ -0,0 +1,27 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "./components/ThemeProvider";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<ThemeProvider>
<body style={{ backgroundColor: "white" }} className={inter.className}>
{children}
</body>
</ThemeProvider>
</html>
);
}
@@ -0,0 +1,167 @@
"use client";
import "@copilotkit/react-ui/styles.css";
import React, { useState } from "react";
import SingleSpreadsheet from "./components/SingleSpreadsheet";
import {
CopilotKit,
useCopilotAction,
useCopilotReadable,
} from "@copilotkit/react-core";
import {
CopilotSidebar,
useCopilotChatSuggestions,
} from "@copilotkit/react-ui";
import { INSTRUCTIONS } from "./instructions";
import { canonicalSpreadsheetData } from "./utils/canonicalSpreadsheetData";
import { SpreadsheetData } from "./types";
import { PreviewSpreadsheetChanges } from "./components/PreviewSpreadsheetChanges";
import { sampleData, sampleData2 } from "./utils/sampleData";
// import { Bottombar } from "./components/Bottombar";
const HomePage = () => {
return (
<CopilotKit
runtimeUrl="api/copilotkit"
transcribeAudioUrl="/api/transcribe"
textToSpeechUrl="/api/tts"
>
<CopilotSidebar
instructions={INSTRUCTIONS}
labels={{
initial:
"Welcome to the spreadsheet app! What would you like help with?",
}}
defaultOpen={true}
clickOutsideToClose={false}
>
<Main />
</CopilotSidebar>
</CopilotKit>
);
};
// function rowsGenerator(n : number) {
// const rows = [];
// for (let i = 0; i < 25; i++) {
// const row = [];
// for (let j = 0; j < 25; j++) {
// if (i < n && j < n) {
// row.push({ value: "Sample data" });
// }
// else {
// row.push({ value: "" });
// }
// }
// rows.push(row);
// }
// return rows;
// }
const Main = () => {
const [spreadsheets, setSpreadsheets] = React.useState<SpreadsheetData[]>([
{
title: "Revenue by department",
rows: sampleData,
},
{
title: "Projects Tracker",
rows: sampleData2,
},
]);
const [selectedSpreadsheetIndex, setSelectedSpreadsheetIndex] = useState(0);
useCopilotAction({
name: "createSpreadsheet",
description: "Create a new spreadsheet",
parameters: [
{
name: "rows",
type: "object[]",
description: "The rows of the spreadsheet",
attributes: [
{
name: "cells",
type: "object[]",
description: "The cells of the row",
attributes: [
{
name: "value",
type: "string",
description: "The value of the cell",
},
],
},
],
},
{
name: "title",
type: "string",
description: "The title of the spreadsheet",
},
],
render: (props) => {
console.log("createSpreadsheet => props: => ", props);
const { rows, title } = props.args;
const newRows = canonicalSpreadsheetData(rows);
return (
<PreviewSpreadsheetChanges
preCommitTitle="Create spreadsheet"
postCommitTitle="Spreadsheet created"
newRows={newRows}
commit={(rows) => {
const newSpreadsheet: SpreadsheetData = {
title: title || "Untitled Spreadsheet",
rows: rows,
};
setSpreadsheets((prev) => [...prev, newSpreadsheet]);
setSelectedSpreadsheetIndex(spreadsheets.length);
}}
/>
);
},
handler: ({ rows, title }) => {
// Do nothing.
// The preview component will optionally handle committing the changes.
},
});
useCopilotChatSuggestions({
instructions:
"Provide suggestions for the user like creating a new sheet with sample data, appending rows, telling them about this view. Strictly show only these options at the start of the chat.",
maxSuggestions: 3,
minSuggestions: 1,
});
useCopilotReadable({
description: "Today's date",
value: new Date().toLocaleDateString(),
});
return (
<div className="flex">
<SingleSpreadsheet
spreadSheets={spreadsheets}
selectedSpreadsheetIndex={selectedSpreadsheetIndex}
setSelectedSpreadsheetIndex={setSelectedSpreadsheetIndex}
spreadsheet={spreadsheets[selectedSpreadsheetIndex]}
setSpreadsheet={(spreadsheet) => {
setSpreadsheets((prev) => {
console.log("setSpreadsheet", spreadsheet);
const newSpreadsheets = [...prev];
newSpreadsheets[selectedSpreadsheetIndex] = spreadsheet;
return newSpreadsheets;
});
}}
/>
</div>
);
};
export default HomePage;
@@ -0,0 +1,10 @@
export interface Cell {
value: string;
}
export type SpreadsheetRow = Cell[];
export interface SpreadsheetData {
title: string;
rows: SpreadsheetRow[];
}
@@ -0,0 +1,25 @@
import { SpreadsheetRow } from "../types";
export interface RowLike {
cells: CellLike[] | undefined;
}
export interface CellLike {
value: string;
}
export function canonicalSpreadsheetData(
rows: RowLike[] | undefined,
): SpreadsheetRow[] {
const canonicalRows: SpreadsheetRow[] = [];
for (const row of rows || []) {
const canonicalRow: SpreadsheetRow = [];
for (const cell of row.cells || []) {
canonicalRow.push({ value: cell.value });
}
canonicalRows.push(canonicalRow);
}
return canonicalRows;
}
@@ -0,0 +1,575 @@
export const sampleData = [
[
{ value: "Department" },
{ value: "Region" },
{ value: "Q1 Revenue" },
{ value: "Q2 Revenue" },
{ value: "Growth %" },
{ value: "Team Size" },
{ value: "Status" },
],
[
{ value: "Sales" },
{ value: "North America" },
{ value: "$1,250,000" },
{ value: "$1,450,000" },
{ value: "16%" },
{ value: "12" },
{ value: "Active" },
],
[
{ value: "Sales" },
{ value: "Europe" },
{ value: "$980,000" },
{ value: "$1,150,000" },
{ value: "17.3%" },
{ value: "8" },
{ value: "Active" },
],
[
{ value: "Sales" },
{ value: "Asia Pacific" },
{ value: "$850,000" },
{ value: "$1,020,000" },
{ value: "20%" },
{ value: "10" },
{ value: "Active" },
],
[
{ value: "Marketing" },
{ value: "Global" },
{ value: "$850,000" },
{ value: "$920,000" },
{ value: "8.2%" },
{ value: "8" },
{ value: "Active" },
],
[
{ value: "R&D" },
{ value: "North America" },
{ value: "$2,100,000" },
{ value: "$2,300,000" },
{ value: "9.5%" },
{ value: "15" },
{ value: "Active" },
],
[
{ value: "R&D" },
{ value: "Europe" },
{ value: "$1,800,000" },
{ value: "$1,950,000" },
{ value: "8.3%" },
{ value: "12" },
{ value: "Active" },
],
[
{ value: "Operations" },
{ value: "North America" },
{ value: "$750,000" },
{ value: "$780,000" },
{ value: "4%" },
{ value: "10" },
{ value: "Active" },
],
[
{ value: "Operations" },
{ value: "Europe" },
{ value: "$620,000" },
{ value: "$650,000" },
{ value: "4.8%" },
{ value: "8" },
{ value: "Active" },
],
[
{ value: "Finance" },
{ value: "Global" },
{ value: "$500,000" },
{ value: "$520,000" },
{ value: "4%" },
{ value: "6" },
{ value: "Active" },
],
[
{ value: "HR" },
{ value: "Global" },
{ value: "$300,000" },
{ value: "$310,000" },
{ value: "3.3%" },
{ value: "5" },
{ value: "Active" },
],
[
{ value: "IT" },
{ value: "Global" },
{ value: "$1,200,000" },
{ value: "$1,300,000" },
{ value: "8.3%" },
{ value: "14" },
{ value: "Active" },
],
[
{ value: "Customer Support" },
{ value: "North America" },
{ value: "$450,000" },
{ value: "$480,000" },
{ value: "6.7%" },
{ value: "9" },
{ value: "Active" },
],
[
{ value: "Customer Support" },
{ value: "Europe" },
{ value: "$380,000" },
{ value: "$410,000" },
{ value: "7.9%" },
{ value: "7" },
{ value: "Active" },
],
[
{ value: "Legal" },
{ value: "Global" },
{ value: "$400,000" },
{ value: "$420,000" },
{ value: "5%" },
{ value: "4" },
{ value: "Active" },
],
[
{ value: "Product Development" },
{ value: "North America" },
{ value: "$1,500,000" },
{ value: "$1,650,000" },
{ value: "10%" },
{ value: "11" },
{ value: "Active" },
],
[
{ value: "Product Development" },
{ value: "Europe" },
{ value: "$1,200,000" },
{ value: "$1,320,000" },
{ value: "10%" },
{ value: "9" },
{ value: "Active" },
],
[
{ value: "Quality Assurance" },
{ value: "Global" },
{ value: "$600,000" },
{ value: "$630,000" },
{ value: "5%" },
{ value: "7" },
{ value: "Active" },
],
[
{ value: "Business Development" },
{ value: "North America" },
{ value: "$800,000" },
{ value: "$950,000" },
{ value: "18.8%" },
{ value: "6" },
{ value: "Active" },
],
[
{ value: "Business Development" },
{ value: "Europe" },
{ value: "$700,000" },
{ value: "$820,000" },
{ value: "17.1%" },
{ value: "5" },
{ value: "Active" },
],
[
{ value: "Business Development" },
{ value: "Asia Pacific" },
{ value: "$600,000" },
{ value: "$750,000" },
{ value: "25%" },
{ value: "4" },
{ value: "Active" },
],
[
{ value: "Data Analytics" },
{ value: "Global" },
{ value: "$900,000" },
{ value: "$1,000,000" },
{ value: "11.1%" },
{ value: "8" },
{ value: "Active" },
],
[
{ value: "Security" },
{ value: "Global" },
{ value: "$550,000" },
{ value: "$580,000" },
{ value: "5.5%" },
{ value: "6" },
{ value: "Active" },
],
[
{ value: "Facilities" },
{ value: "North America" },
{ value: "$300,000" },
{ value: "$310,000" },
{ value: "3.3%" },
{ value: "4" },
{ value: "Active" },
],
[
{ value: "Facilities" },
{ value: "Europe" },
{ value: "$250,000" },
{ value: "$260,000" },
{ value: "4%" },
{ value: "3" },
{ value: "Active" },
],
[
{ value: "Communications" },
{ value: "Global" },
{ value: "$400,000" },
{ value: "$430,000" },
{ value: "7.5%" },
{ value: "5" },
{ value: "Active" },
],
[
{ value: "Compliance" },
{ value: "Global" },
{ value: "$350,000" },
{ value: "$370,000" },
{ value: "5.7%" },
{ value: "4" },
{ value: "Active" },
],
[
{ value: "Training" },
{ value: "Global" },
{ value: "$280,000" },
{ value: "$300,000" },
{ value: "7.1%" },
{ value: "3" },
{ value: "Active" },
],
[
{ value: "Procurement" },
{ value: "Global" },
{ value: "$450,000" },
{ value: "$470,000" },
{ value: "4.4%" },
{ value: "5" },
{ value: "Active" },
],
[
{ value: "Strategy" },
{ value: "Global" },
{ value: "$600,000" },
{ value: "$650,000" },
{ value: "8.3%" },
{ value: "4" },
{ value: "Active" },
],
];
export const sampleData2 = [
[
{ value: "Project Name" },
{ value: "Client" },
{ value: "Start Date" },
{ value: "End Date" },
{ value: "Budget" },
{ value: "Spent" },
{ value: "Status" },
{ value: "Team Size" },
],
[
{ value: "Cloud Migration" },
{ value: "TechCorp Inc" },
{ value: "2024-01-15" },
{ value: "2024-06-30" },
{ value: "$500,000" },
{ value: "$150,000" },
{ value: "In Progress" },
{ value: "8" },
],
[
{ value: "Mobile App Development" },
{ value: "StartUpX" },
{ value: "2024-02-01" },
{ value: "2024-08-31" },
{ value: "$300,000" },
{ value: "$75,000" },
{ value: "In Progress" },
{ value: "6" },
],
[
{ value: "Data Center Upgrade" },
{ value: "GlobalBank" },
{ value: "2023-11-01" },
{ value: "2024-04-30" },
{ value: "$1,200,000" },
{ value: "$900,000" },
{ value: "In Progress" },
{ value: "12" },
],
[
{ value: "Security Audit" },
{ value: "HealthCare Plus" },
{ value: "2024-01-01" },
{ value: "2024-03-31" },
{ value: "$200,000" },
{ value: "$180,000" },
{ value: "Completed" },
{ value: "4" },
],
[
{ value: "Website Redesign" },
{ value: "RetailCo" },
{ value: "2023-12-01" },
{ value: "2024-02-28" },
{ value: "$150,000" },
{ value: "$150,000" },
{ value: "Completed" },
{ value: "5" },
],
[
{ value: "AI Integration" },
{ value: "ManufacturingPro" },
{ value: "2024-03-01" },
{ value: "2024-09-30" },
{ value: "$800,000" },
{ value: "$100,000" },
{ value: "In Progress" },
{ value: "10" },
],
[
{ value: "ERP Implementation" },
{ value: "LogisticsCo" },
{ value: "2023-10-01" },
{ value: "2024-12-31" },
{ value: "$2,000,000" },
{ value: "$800,000" },
{ value: "In Progress" },
{ value: "15" },
],
[
{ value: "Network Upgrade" },
{ value: "EducationFirst" },
{ value: "2024-02-15" },
{ value: "2024-05-31" },
{ value: "$400,000" },
{ value: "$120,000" },
{ value: "In Progress" },
{ value: "7" },
],
[
{ value: "Digital Transformation" },
{ value: "InsuranceCo" },
{ value: "2023-09-01" },
{ value: "2024-08-31" },
{ value: "$1,500,000" },
{ value: "$900,000" },
{ value: "In Progress" },
{ value: "14" },
],
[
{ value: "IoT Implementation" },
{ value: "SmartHome Inc" },
{ value: "2024-01-01" },
{ value: "2024-07-31" },
{ value: "$600,000" },
{ value: "$200,000" },
{ value: "In Progress" },
{ value: "9" },
],
[
{ value: "Blockchain Integration" },
{ value: "FinTech Solutions" },
{ value: "2024-02-01" },
{ value: "2024-10-31" },
{ value: "$900,000" },
{ value: "$150,000" },
{ value: "In Progress" },
{ value: "11" },
],
[
{ value: "CRM Upgrade" },
{ value: "SalesForce Co" },
{ value: "2023-11-15" },
{ value: "2024-03-31" },
{ value: "$250,000" },
{ value: "$220,000" },
{ value: "In Progress" },
{ value: "6" },
],
[
{ value: "Mobile Payment System" },
{ value: "PayTech" },
{ value: "2024-01-01" },
{ value: "2024-06-30" },
{ value: "$700,000" },
{ value: "$300,000" },
{ value: "In Progress" },
{ value: "10" },
],
[
{ value: "Cloud Security" },
{ value: "SecureCloud" },
{ value: "2023-12-01" },
{ value: "2024-05-31" },
{ value: "$450,000" },
{ value: "$200,000" },
{ value: "In Progress" },
{ value: "8" },
],
[
{ value: "E-commerce Platform" },
{ value: "ShopOnline" },
{ value: "2024-02-01" },
{ value: "2024-08-31" },
{ value: "$550,000" },
{ value: "$100,000" },
{ value: "In Progress" },
{ value: "9" },
],
[
{ value: "Data Analytics" },
{ value: "MarketInsights" },
{ value: "2023-10-01" },
{ value: "2024-04-30" },
{ value: "$350,000" },
{ value: "$280,000" },
{ value: "In Progress" },
{ value: "7" },
],
[
{ value: "API Development" },
{ value: "TechStartup" },
{ value: "2024-01-15" },
{ value: "2024-05-31" },
{ value: "$200,000" },
{ value: "$80,000" },
{ value: "In Progress" },
{ value: "5" },
],
[
{ value: "DevOps Implementation" },
{ value: "SoftwareCo" },
{ value: "2023-11-01" },
{ value: "2024-03-31" },
{ value: "$300,000" },
{ value: "$250,000" },
{ value: "In Progress" },
{ value: "6" },
],
[
{ value: "Machine Learning Model" },
{ value: "AI Solutions" },
{ value: "2024-02-01" },
{ value: "2024-09-30" },
{ value: "$750,000" },
{ value: "$150,000" },
{ value: "In Progress" },
{ value: "10" },
],
[
{ value: "Mobile Banking App" },
{ value: "DigitalBank" },
{ value: "2023-12-01" },
{ value: "2024-07-31" },
{ value: "$1,000,000" },
{ value: "$400,000" },
{ value: "In Progress" },
{ value: "12" },
],
[
{ value: "Supply Chain System" },
{ value: "LogisticsPro" },
{ value: "2024-01-01" },
{ value: "2024-12-31" },
{ value: "$1,800,000" },
{ value: "$500,000" },
{ value: "In Progress" },
{ value: "15" },
],
[
{ value: "Video Platform" },
{ value: "StreamTech" },
{ value: "2024-02-15" },
{ value: "2024-08-31" },
{ value: "$650,000" },
{ value: "$100,000" },
{ value: "In Progress" },
{ value: "9" },
],
[
{ value: "Cybersecurity Audit" },
{ value: "SecureNet" },
{ value: "2023-11-01" },
{ value: "2024-02-28" },
{ value: "$280,000" },
{ value: "$250,000" },
{ value: "Completed" },
{ value: "5" },
],
[
{ value: "Social Media Platform" },
{ value: "ConnectSocial" },
{ value: "2024-01-01" },
{ value: "2024-10-31" },
{ value: "$1,200,000" },
{ value: "$300,000" },
{ value: "In Progress" },
{ value: "13" },
],
[
{ value: "IoT Security" },
{ value: "SmartSecure" },
{ value: "2024-02-01" },
{ value: "2024-07-31" },
{ value: "$400,000" },
{ value: "$80,000" },
{ value: "In Progress" },
{ value: "7" },
],
[
{ value: "AR/VR Development" },
{ value: "VirtualTech" },
{ value: "2023-12-01" },
{ value: "2024-09-30" },
{ value: "$950,000" },
{ value: "$350,000" },
{ value: "In Progress" },
{ value: "11" },
],
[
{ value: "Payment Gateway" },
{ value: "PaySecure" },
{ value: "2024-01-15" },
{ value: "2024-06-30" },
{ value: "$500,000" },
{ value: "$150,000" },
{ value: "In Progress" },
{ value: "8" },
],
[
{ value: "Cloud Storage Solution" },
{ value: "StorageTech" },
{ value: "2023-10-01" },
{ value: "2024-05-31" },
{ value: "$800,000" },
{ value: "$600,000" },
{ value: "In Progress" },
{ value: "10" },
],
[
{ value: "Mobile Game Development" },
{ value: "GameStudio" },
{ value: "2024-02-01" },
{ value: "2024-11-30" },
{ value: "$1,500,000" },
{ value: "$200,000" },
{ value: "In Progress" },
{ value: "14" },
],
];
@@ -0,0 +1,21 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
darkMode: "class",
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
};
export default config;
@@ -0,0 +1,38 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"],
"@copilotkit/react-core": [
"../CopilotKit/CopilotKit/packages/react-core/dist"
],
"@copilotkit/shared": ["../CopilotKit/CopilotKit/packages/shared/dist"],
"@copilotkit/react-ui": [
"../CopilotKit/CopilotKit/packages/react-ui/dist"
],
"@copilotkit/react-ui/*": [
"../CopilotKit/CopilotKit/packages/react-ui/dist/*"
],
"@copilotkit/runtime": ["../CopilotKit/CopilotKit/packages/runtime/dist"]
},
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}