chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
OPENAI_API_KEY=xxxxxxx
|
||||
OPENAI_MODEL=gpt-4-1106-preview
|
||||
TAVILY_API_KEY=xxxxxxx
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||

|
||||
|
||||
## 🛠️ 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;
|
||||
+14625
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"
|
||||
}
|
||||
+13022
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"]
|
||||
}
|
||||
Reference in New Issue
Block a user