mirror of
https://github.com/PragmaticMachineLearning/probly.git
synced 2026-01-09 13:27:55 -05:00
feat: Add Docker support and improve browser compatibility
This commit adds Docker support for easy deployment and improves browser compatibility: - Add Dockerfile and docker-compose.yml for containerized deployment - Fix Pyodide integration to work properly in browser-only mode - Add mock implementation for server-side rendering - Clean up markdown formatting in analysis output - Update README with Docker deployment instructions - Add platform-specific keyboard shortcuts (Mac/Windows) - Ensure public directory exists in Docker build - Fix TypeScript type definitions for Pyodide The application now properly handles the browser/server environment difference, with Python analysis running exclusively in the browser while the server provides API proxying and static file serving.
This commit is contained in:
61
Dockerfile
Normal file
61
Dockerfile
Normal file
@@ -0,0 +1,61 @@
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
# Install missing dependency
|
||||
RUN npm install class-variance-authority
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Ensure public directory exists
|
||||
RUN mkdir -p ./public
|
||||
|
||||
# Set environment variables with build-time defaults
|
||||
ARG OPENAI_API_KEY
|
||||
ENV OPENAI_API_KEY=$OPENAI_API_KEY
|
||||
|
||||
# Build the application
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
# Create a non-root user
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Create public directory if it doesn't exist
|
||||
RUN mkdir -p ./public
|
||||
# Set proper permissions
|
||||
RUN chown -R nextjs:nodejs ./public
|
||||
|
||||
# Switch to non-root user
|
||||
USER nextjs
|
||||
|
||||
# Copy necessary files from builder
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Set environment variables with runtime defaults
|
||||
ENV PORT 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
# Expose the port the app will run on
|
||||
EXPOSE 3000
|
||||
|
||||
# Start the application
|
||||
CMD ["node", "server.js"]
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Probly
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
97
README.md
97
README.md
@@ -7,17 +7,55 @@ An AI-powered spreadsheet application that combines spreadsheet functionality wi
|
||||
## Features
|
||||
|
||||
- **Interactive Spreadsheet**: Full-featured spreadsheet with formula support
|
||||
- **Python Analysis**: Run Python code directly on your spreadsheet data
|
||||
- **Python Analysis**: Run Python code directly in your browser using WebAssembly
|
||||
- **Data Visualization**: Create charts and visualizations from your data
|
||||
- **AI-Powered**: Get intelligent suggestions and automated analysis
|
||||
|
||||
## Architecture
|
||||
|
||||
Probly uses a modern architecture:
|
||||
|
||||
- **Frontend**: Next.js application that runs in the browser
|
||||
- **Python Execution**: Pyodide (Python compiled to WebAssembly) runs entirely in the browser
|
||||
- **LLM Integration**: OpenAI API calls are proxied through the server
|
||||
|
||||
This design means that data analysis happens locally in your browser, providing better performance and privacy.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 18 or higher
|
||||
- npm or yarn
|
||||
- A modern web browser (Chrome, Firefox, Edge, or Safari)
|
||||
- OpenAI API key
|
||||
|
||||
## Installation and Setup
|
||||
## Quick Start with Docker
|
||||
|
||||
The easiest way to get started with Probly is using Docker:
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/PragmaticMachineLearning/probly.git
|
||||
cd probly
|
||||
```
|
||||
|
||||
2. Create a simple `.env` file with your OpenAI API key:
|
||||
```
|
||||
OPENAI_API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
3. Build and start the application:
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
4. Access Probly at http://localhost:3000
|
||||
|
||||
The Docker container serves the Next.js application and handles API requests to OpenAI, while all Python code execution happens in your browser using WebAssembly.
|
||||
|
||||
## Manual Installation
|
||||
|
||||
If you prefer to run Probly without Docker:
|
||||
|
||||
1. Clone the repository
|
||||
```bash
|
||||
@@ -30,32 +68,57 @@ An AI-powered spreadsheet application that combines spreadsheet functionality wi
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Create a `.env` file in the root directory with your OpenAI API key:
|
||||
3. Create a `.env` file with your OpenAI API key:
|
||||
```
|
||||
OPENAI_API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
## Running the Application
|
||||
4. Start the development server:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Development mode:
|
||||
```bash
|
||||
# Start Next.js development server
|
||||
npm run dev
|
||||
```
|
||||
5. For production, build and start:
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
Production build:
|
||||
```bash
|
||||
# Build Next.js
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
## Using Probly
|
||||
|
||||
1. Start the application and open it in your browser
|
||||
2. Import data using the import button or start with a blank spreadsheet
|
||||
3. Open the AI chat with `Ctrl+Shift+/` to start interacting with Probly
|
||||
3. Open the AI chat with the keyboard shortcut:
|
||||
- **Windows/Linux**: `Ctrl+Shift+?`
|
||||
- **Mac**: `⌘+Shift+?` (Command+Shift+?)
|
||||
4. Ask questions about your data or request analysis
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Action | Windows/Linux | Mac |
|
||||
|--------|--------------|-----|
|
||||
| Toggle AI Chat | `Ctrl+Shift+?` | `⌘+Shift+?` (Command+Shift+?) |
|
||||
|
||||
## Advanced Docker Options
|
||||
|
||||
### Alternative Docker Setup
|
||||
|
||||
You can also pass the API key directly to Docker Compose:
|
||||
|
||||
```bash
|
||||
OPENAI_API_KEY=your_api_key_here docker compose build
|
||||
OPENAI_API_KEY=your_api_key_here docker compose up -d
|
||||
```
|
||||
|
||||
### Building Custom Docker Images
|
||||
|
||||
To build a custom Docker image with your API key baked in:
|
||||
|
||||
```bash
|
||||
docker build -t probly:custom --build-arg OPENAI_API_KEY=your_api_key_here .
|
||||
docker run -p 3000:3000 probly:custom
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Frontend**: Next.js 14, TypeScript, React
|
||||
|
||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
probly:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
restart: unless-stopped
|
||||
@@ -1,7 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: false,
|
||||
// other configurations...
|
||||
reactStrictMode: true,
|
||||
output: "standalone",
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ["sharp", "canvas"],
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@types/react": "18.2.55",
|
||||
"@types/react-dom": "18.2.19",
|
||||
"autoprefixer": "10.4.17",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"handsontable": "^13.1.0",
|
||||
@@ -23,9 +24,11 @@
|
||||
"openai": "^4.26.0",
|
||||
"postcss": "8.4.35",
|
||||
"pyodide": "^0.27.2",
|
||||
"radix-ui": "^1.1.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "3.4.1",
|
||||
"typescript": "5.3.3",
|
||||
"uuid": "^11.0.5",
|
||||
|
||||
0
public/.gitkeep
Normal file
0
public/.gitkeep
Normal file
@@ -124,6 +124,7 @@ const SpreadsheetApp = () => {
|
||||
let accumulatedResponse = "";
|
||||
let updates: CellUpdate[] | undefined;
|
||||
let chartData: any | undefined;
|
||||
let lastParsedData: any | undefined;
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
@@ -137,7 +138,7 @@ const SpreadsheetApp = () => {
|
||||
const jsonData = event.substring(6);
|
||||
try {
|
||||
const parsedData = JSON.parse(jsonData);
|
||||
|
||||
lastParsedData = parsedData;
|
||||
if (parsedData.response) {
|
||||
if (parsedData.streaming) {
|
||||
// For streaming content, append to the existing response
|
||||
@@ -188,7 +189,7 @@ const SpreadsheetApp = () => {
|
||||
response: accumulatedResponse,
|
||||
updates: updates,
|
||||
chartData: chartData,
|
||||
analysis: parsedData?.analysis,
|
||||
analysis: lastParsedData?.analysis,
|
||||
streaming: false,
|
||||
status: updates || chartData ? "pending" : null,
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ interface ChartInfo {
|
||||
const Spreadsheet = forwardRef<SpreadsheetRef, SpreadsheetProps>(
|
||||
({ onDataChange, initialData }, ref) => {
|
||||
const spreadsheetRef = useRef<HTMLDivElement>(null);
|
||||
const hotInstanceRef = useRef<Handsontable | null>(null);
|
||||
const hotInstanceRef = useRef<any>(null);
|
||||
const { formulaQueue, clearFormula, setFormulas } = useSpreadsheet();
|
||||
const [currentData, setCurrentData] = useState(
|
||||
initialData || [
|
||||
|
||||
@@ -50,7 +50,9 @@ const ToolResponse: React.FC<ToolResponseProps> = ({
|
||||
const col = match[1];
|
||||
const row = parseInt(match[2]);
|
||||
return { col, row, formula: update.formula, target: update.target };
|
||||
}).filter(Boolean);
|
||||
}).filter(Boolean) as { col: string; row: number; formula: string; target: string }[];
|
||||
|
||||
if (cellInfo.length === 0) return null;
|
||||
|
||||
// Find the range of rows and columns
|
||||
const minRow = Math.min(...cellInfo.map(cell => cell.row));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HyperFormula } from "hyperformula";
|
||||
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
import { HyperFormula } from "hyperformula";
|
||||
|
||||
// Helper function to convert HyperFormula cell address to string format
|
||||
const cellAddressToString = (address: any) => {
|
||||
if (typeof address !== "object" || address === null) {
|
||||
@@ -69,18 +69,31 @@ const calculateCellValue = (
|
||||
const existingValue = cellValues.get(cellRef);
|
||||
if (existingValue) {
|
||||
// set up data in hyperformula if the value is already provided, rather than attempting to perform an evaluation
|
||||
hyperformulaInstance.setCellContents(cellAddress, existingValue);
|
||||
hyperformulaInstance.setCellContents({
|
||||
col: cellAddress.c,
|
||||
row: cellAddress.r,
|
||||
sheet: 0
|
||||
}, existingValue);
|
||||
}
|
||||
|
||||
// Parse hyperformula formula
|
||||
const ast = hyperformulaInstance.parse(formula);
|
||||
if (ast.result === "ERROR") {
|
||||
console.error("HyperFormula parse error:", ast.error);
|
||||
return "#ERROR";
|
||||
}
|
||||
// // Parse hyperformula formula
|
||||
// const ast = hyperformulaInstance.parseFormula(formula, {
|
||||
// col: cellAddress.c,
|
||||
// row: cellAddress.r,
|
||||
// sheet: 0
|
||||
// });
|
||||
|
||||
// if (ast.error) {
|
||||
// console.error("HyperFormula parse error:", ast.error);
|
||||
// return "#ERROR";
|
||||
// }
|
||||
|
||||
// Calculate using HyperFormula
|
||||
const calculatedValue = hyperformulaInstance.getCellValue(cellAddress);
|
||||
const calculatedValue = hyperformulaInstance.getCellValue({
|
||||
col: cellAddress.c,
|
||||
row: cellAddress.r,
|
||||
sheet: 0
|
||||
});
|
||||
return calculatedValue;
|
||||
// return hyperformulaInstance.getCellValue(cellAddress);
|
||||
}
|
||||
|
||||
20
src/types/handsontable.d.ts
vendored
Normal file
20
src/types/handsontable.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
declare module 'handsontable' {
|
||||
// Define the Handsontable class/interface
|
||||
interface HandsontableInstance {
|
||||
// Add any methods or properties you need to use
|
||||
updateSettings(settings: any): void;
|
||||
getData(): any[][];
|
||||
render(): void;
|
||||
destroy(): void;
|
||||
// Add other methods as needed
|
||||
}
|
||||
|
||||
// Define the constructor function
|
||||
interface HandsontableStatic {
|
||||
new (element: HTMLElement, options: any): HandsontableInstance;
|
||||
}
|
||||
|
||||
// Export the namespace and default
|
||||
const Handsontable: HandsontableStatic;
|
||||
export = Handsontable;
|
||||
}
|
||||
@@ -1,22 +1,18 @@
|
||||
export interface PyodideInterface {
|
||||
loadPackagesFromImports(code: string): Promise<void>;
|
||||
runPython(code: string): any;
|
||||
runPythonAsync(code: string): Promise<any>;
|
||||
loadPackage(packages: string | string[]): Promise<any>;
|
||||
globals: {
|
||||
get(key: string): any;
|
||||
set(key: string, value: any): void;
|
||||
};
|
||||
setStdout(options: { batched: (msg: string) => void }): void;
|
||||
setStderr(options: { batched: (msg: string) => void }): void;
|
||||
setStdout(options: { batched: (s: string) => void }): void;
|
||||
setStderr(options: { batched: (s: string) => void }): void;
|
||||
globals: any;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
loadPyodide(options?: { indexURL?: string }): Promise<PyodideInterface>;
|
||||
}
|
||||
|
||||
// Make loadPyodide available globally in browser environments
|
||||
const loadPyodide: (options?: { indexURL?: string }) => Promise<PyodideInterface>;
|
||||
}
|
||||
|
||||
// For Node environment
|
||||
declare module 'pyodide' {
|
||||
export function loadPyodide(options?: { indexURL?: string }): Promise<PyodideInterface>;
|
||||
}
|
||||
@@ -41,17 +41,26 @@ export function formatSpreadsheetData(data: any[][]): string {
|
||||
* @returns Promise<string> - Structured CSV-like output with headers
|
||||
*/
|
||||
export async function structureAnalysisOutput(rawOutput: string, analysisGoal: string): Promise<string> {
|
||||
// First, clean up the raw output in case it already contains backticks
|
||||
const cleanedRawOutput = rawOutput
|
||||
.replace(/```[\s\S]*?```/g, '') // Remove code blocks with content
|
||||
.replace(/```/g, '') // Remove any remaining backticks
|
||||
.trim(); // Remove extra whitespace
|
||||
|
||||
const messages: ChatCompletionMessageParam[] = [
|
||||
{
|
||||
role: "system",
|
||||
content: `Convert the following analysis output into a clean tabular format.
|
||||
Each row should be comma-separated values, with the first row being headers.
|
||||
Ensure numbers are properly formatted and aligned.
|
||||
The output should be ready to insert into a spreadsheet.`
|
||||
The output should be ready to insert into a spreadsheet.
|
||||
|
||||
IMPORTANT: Do not use any markdown formatting or code blocks in your response.
|
||||
Just return plain text with comma-separated values.`
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: `Analysis Goal: ${analysisGoal}\n\nRaw Output:\n${rawOutput}\n\nConvert this into comma-separated rows with headers.`
|
||||
content: `Analysis Goal: ${analysisGoal}\n\nRaw Output:\n${cleanedRawOutput}\n\nConvert this into comma-separated rows with headers.`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -61,7 +70,15 @@ export async function structureAnalysisOutput(rawOutput: string, analysisGoal: s
|
||||
temperature: 0.1,
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content || '';
|
||||
// Get the content from the completion, or empty string if undefined
|
||||
let result = completion.choices[0]?.message?.content || '';
|
||||
|
||||
// Remove any code blocks (text between triple backticks) and any remaining backticks
|
||||
result = result.replace(/```[\s\S]*?```/g, '') // Remove code blocks with content
|
||||
.replace(/```/g, '') // Remove any remaining backticks
|
||||
.trim(); // Remove extra whitespace
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PyodideInterface } from '../types/pyodide';
|
||||
import { PyodideInterface } from '@/types/pyodide';
|
||||
|
||||
interface SandboxResult {
|
||||
stdout: string;
|
||||
@@ -7,42 +7,64 @@ interface SandboxResult {
|
||||
}
|
||||
|
||||
export class PyodideSandbox {
|
||||
private pyodide: PyodideInterface | null = null;
|
||||
private pyodide: any = null;
|
||||
private initialized = false;
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
// Check if we're in browser or Node environment
|
||||
// Only try to load Pyodide in browser environments
|
||||
if (typeof window !== 'undefined') {
|
||||
// Browser environment - use global loadPyodide
|
||||
this.pyodide = await window.loadPyodide();
|
||||
} else {
|
||||
// Node environment - use local pyodide from node_modules
|
||||
const { loadPyodide } = await import('pyodide');
|
||||
// Browser environment - load from CDN
|
||||
// @ts-ignore - loadPyodide is loaded from a script tag
|
||||
this.pyodide = await loadPyodide({
|
||||
indexURL: 'node_modules/pyodide'
|
||||
indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.27.2/full/'
|
||||
});
|
||||
|
||||
// Initialize Python environment
|
||||
await this.pyodide.loadPackagesFromImports(`
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import io
|
||||
import base64
|
||||
`);
|
||||
|
||||
this.initialized = true;
|
||||
} else {
|
||||
// In Node.js environment (including Docker), we'll use a mock implementation
|
||||
console.log("Running in Node.js environment - using mock Pyodide implementation");
|
||||
this.mockPyodideImplementation();
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
if (!this.pyodide) throw new Error('Failed to initialize Pyodide');
|
||||
|
||||
await this.pyodide.loadPackage(['pandas', 'numpy']);
|
||||
await this.pyodide.runPython(`
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
pd.set_option('display.max_rows', 100)
|
||||
pd.set_option('display.max_columns', 50)
|
||||
`);
|
||||
|
||||
this.initialized = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Pyodide:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a mock implementation for server-side rendering
|
||||
private mockPyodideImplementation() {
|
||||
console.log("Creating mock Pyodide implementation for server-side rendering");
|
||||
this.pyodide = {
|
||||
runPython: (code: string) => {
|
||||
console.log("[Server] Mock Pyodide runPython called - this would run in browser");
|
||||
return null;
|
||||
},
|
||||
runPythonAsync: async (code: string) => {
|
||||
console.log("[Server] Mock Pyodide runPythonAsync called - this would run in browser");
|
||||
return null;
|
||||
},
|
||||
setStdout: (options: { batched: (s: string) => void }) => {
|
||||
console.log("[Server] Mock Pyodide setStdout called");
|
||||
},
|
||||
setStderr: (options: { batched: (s: string) => void }) => {
|
||||
console.log("[Server] Mock Pyodide setStderr called");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async runDataAnalysis(
|
||||
code: string,
|
||||
csvData: string,
|
||||
@@ -56,6 +78,16 @@ export class PyodideSandbox {
|
||||
throw new Error('Pyodide not initialized');
|
||||
}
|
||||
|
||||
// If we're in a server environment, return a mock result
|
||||
if (typeof window === 'undefined') {
|
||||
console.log("[Server] Returning mock analysis result - actual analysis will run in browser");
|
||||
return {
|
||||
stdout: "Pyodide analysis is only available in browser environments.\nServer received code:\n" + code,
|
||||
stderr: "",
|
||||
result: null
|
||||
};
|
||||
}
|
||||
|
||||
const stdout: string[] = [];
|
||||
const stderr: string[] = [];
|
||||
|
||||
@@ -97,7 +129,7 @@ df = pd.read_csv(io.StringIO('''${csvData}'''))
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
if (this.pyodide) {
|
||||
if (this.pyodide && typeof window !== 'undefined') {
|
||||
try {
|
||||
// Simple cleanup - just delete our main DataFrame
|
||||
await this.pyodide.runPython(`
|
||||
@@ -107,8 +139,8 @@ if 'df' in globals():
|
||||
} catch (error) {
|
||||
console.warn('Error during cleanup:', error);
|
||||
}
|
||||
this.pyodide = null;
|
||||
}
|
||||
this.pyodide = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": ["./node_modules/@types", "./src/types"]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user