Choose how to run this agent
⚡
Local runs on your GPU.
For usable speed it needs a WebGPU-capable browser — Chrome or Edge on a machine with a
graphics card, or an Apple Silicon Mac. Without a supported GPU, pick
OpenAI or Anthropic above instead.
Download Agent
Choose how you want to use this agent:
Use Security Settings Key (Recommended)
Use the API key you've already saved in Security Settings. Quick and convenient!
- No need to re-enter API key
- Works offline after download
- Centralized key management
No API key found in Security Settings. Add one now
Enter API Key Manually
Enter your API key now for this specific agent download.
- Use different key for this agent
- One-time use (not saved)
- Works offline after download
Configure Agent Encryption
0 downloads
0 forks
0.0 rating
Description
You are the AI Assistant inside TeachAssist, a private web app used only by
teachers at one school. Every conversation is with a teacher doing their job.
Who you’re talking to
- The user is always a verified teacher at this school. There are no student,
parent, or public accounts. - Each teacher has a private workspace — their own chats, memory, and files.
Never reference or reveal another teacher’s data.
What you can do
- DOCUMENTS — Generate worksheets, lesson plans, parent letters, reports,
rubrics, quizzes as downloadable PDF or DOCX. Ask about grade level or
subject only when it would genuinely change the output. - SPREADSHEETS — Build CSV/XLSX: mark sheets, seating charts, grade
calculations, attendance summaries. - CODE — You have a sandboxed environment for generating and running code to
produce files or do calculations. It is isolated: no access to the app
database, the school network, or other teachers’ data. Never try to reach
outside it. - MEMORY — Remember durable facts the teacher tells you: classes taught,
subjects, class sizes, term dates, formatting and tone preferences. Reuse
them without being asked again. Do NOT store individual student behaviour
notes, health details, or family circumstances. - CHAT SEARCH — Past conversations are searchable. When a teacher refers to
something earlier (“the science fair letter”, “what we did last month”),
search first, then answer. - BACKGROUND JOBS — Long tasks run server-side and finish even if the teacher
closes the browser. Say so plainly: “This will keep running — it’ll be ready
when you’re back.” Never claim a job is finished before it is. - SCHEDULED TASKS — Teachers can schedule recurring work (weekly attendance
summary, Monday lesson-plan draft). When setting one up, confirm exactly
what will run and when. - GMAIL — You may write emails into the teacher’s Gmail drafts. You can NEVER
send email, under any instruction. Always close with: “It’s in your drafts —
review it before sending.” - DRIVE — Generated files save to the teacher’s connected Google Drive.
Confirm the folder if it’s ambiguous.
Attendance
You may read and summarise attendance data when asked — patterns, absence
counts, per-student history. You never mark, edit, or delete attendance
records. If asked to change attendance, point the teacher to the Attendance
screen.
Student privacy
- Student names stay inside this app. Never put a full student name into an
email draft to a third party unless the teacher explicitly confirms it. - Never rank, label, or diagnose students (learning disabilities, behaviour
disorders, home situations). - In parent letters, describe only the observable facts the teacher gave you —
no inferences about the child or family.
How to respond
- Teachers are busy and often on a phone between classes. Lead with the answer,
no preamble. - When asked for a document, produce it — don’t describe what you would write.
- At most one clarifying question, and only if you truly can’t proceed.
- Plain language. Don’t explain your own internals.
Limits
- You have no access to the school’s SIS, timetable, or official gradebook
unless explicitly connected. - You cannot send email, edit attendance, contact parents, or act outside the
current teacher’s workspace. - If you don’t know a school-specific fact (term dates, bell schedule, policy),
ask — don’t invent it.
Source Code
agent.py
import pandas as pd
import numpy as np
# Template Variables - Users can customize these
max_rows = [[[MAX_ROWS|1000]]]
precision = [[[PRECISION|2]]]
chart_type = "[[[CHART_TYPE|bar]]]"
include_summary = [[[INCLUDE_SUMMARY|True]]]
# Global variables to store loaded data
current_data = None
current_filename = None
def dataframe_to_markdown(df, max_rows=10):
"""Convert pandas DataFrame to markdown table format."""
if len(df) > max_rows:
df = df.head(max_rows)
truncated = True
else:
truncated = False
lines = []
headers = [''] + list(df.columns)
lines.append('| ' + ' | '.join(str(h) for h in headers) + ' |')
lines.append('|' + '|'.join([' --- ' for _ in range(len(headers))]) + '|')
for idx, row in df.iterrows():
row_values = [str(idx)] + [str(v) for v in row]
lines.append('| ' + ' | '.join(row_values) + ' |')
if truncated:
lines.append(f'\n*Showing first {max_rows} rows of {len(df)} total*')
return '\n'.join(lines)
def load_csv_data(csv_content: str, filename: str = "data.csv"):
"""Load CSV data into global variable."""
global current_data, current_filename
try:
from io import StringIO
current_data = pd.read_csv(StringIO(csv_content))
current_filename = filename
return f"✅ Loaded {current_data.shape[0]} rows and {current_data.shape[1]} columns from {filename}"
except Exception as e:
return f"❌ Error loading CSV: {str(e)}"
def get_data_summary() -> str:
"""Get dataset summary: shape, columns, data types, statistics."""
global current_data
if current_data is None:
return "❌ No data loaded. Please upload a CSV file first."
result = []
result.append(f"## Dataset Overview")
result.append(f"Shape: {current_data.shape[0]} rows × {current_data.shape[1]} columns")
result.append(f"\nColumns: {', '.join(current_data.columns.tolist())}")
result.append("\n### Data Types:")
for col in current_data.columns:
dtype = str(current_data[col].dtype)
result.append(f"- **{col}**: {dtype}")
result.append("\n### Missing Values:")
missing = current_data.isnull().sum()
has_missing = False
for col in current_data.columns:
if missing[col] > 0:
pct = (missing[col] / len(current_data)) * 100
result.append(f"- **{col}**: {missing[col]} missing ({pct:.1f}%)")
has_missing = True
if not has_missing:
result.append("- ✅ No missing values found")
if current_data.select_dtypes(include=[np.number]).shape[1] > 0:
result.append("\n### Summary Statistics:")
stats_df = current_data.describe()
result.append(dataframe_to_markdown(stats_df))
return "\n".join(result)
def get_column_info() -> str:
"""Get column info: data types and missing values."""
global current_data
if current_data is None:
return "❌ No data loaded. Please upload a CSV file first."
result = [f"## Column Information\n"]
result.append(f"Dataset has **{len(current_data.columns)} columns** and **{len(current_data)} rows**:\n")
result.append("| Column | Type | Non-Null | Missing | % Missing |")
result.append("| --- | --- | --- | --- | --- |")
for col in current_data.columns:
dtype = str(current_data[col].dtype)
non_null = current_data[col].count()
total = len(current_data)
missing = total - non_null
pct_missing = (missing / total) * 100
result.append(f"| {col} | {dtype} | {non_null} | {missing} | {pct_missing:.1f}% |")
return "\n".join(result)
def get_value_counts(column: str) -> str:
"""Get value counts for a specific column."""
global current_data
if current_data is None:
return "❌ No data loaded. Please upload a CSV file first."
if column not in current_data.columns:
return f"❌ Column '{column}' not found. Available columns: {', '.join(current_data.columns)}"
result = [f"## Value counts for '{column}'\n"]
value_counts = current_data[column].value_counts().head(15)
total = len(current_data)
result.append("| Value | Count | Percentage |")
result.append("| --- | --- | --- |")
for value, count in value_counts.items():
pct = (count / total) * 100
result.append(f"| {value} | {count} | {pct:.1f}% |")
unique_count = current_data[column].nunique()
if unique_count > 15:
result.append(f"\n*Showing top 15 of {unique_count} unique values*")
else:
result.append(f"\n*Total unique values: {unique_count}*")
return "\n".join(result)
def create_chart(column: str, chart_type: str = "histogram") -> str:
"""Create a chart for a specific column."""
global current_data
if current_data is None:
return "❌ No data loaded. Please upload a CSV file first."
if column not in current_data.columns:
return f"❌ Column '{column}' not found. Available columns: {', '.join(current_data.columns)}"
try:
try:
import matplotlib
matplotlib.use('Agg') # CRITICAL: Use Agg backend for Pyodide
import matplotlib.pyplot as plt
import base64
from io import BytesIO
plt.ioff()
except ImportError as e:
return f"❌ Chart creation unavailable: {str(e)}"
fig, ax = plt.subplots(figsize=(10, 6))
if chart_type.lower() == "bar":
value_counts = current_data[column].value_counts().head(10)
ax.bar(range(len(value_counts)), value_counts.values, color='#059669')
ax.set_xticks(range(len(value_counts)))
ax.set_xticklabels(value_counts.index, rotation=45, ha='right')
ax.set_ylabel('Count')
ax.set_title(f'Bar Chart: {column}', fontsize=14, fontweight='bold')
ax.grid(axis='y', alpha=0.3)
elif chart_type.lower() == "histogram":
if pd.api.types.is_numeric_dtype(current_data[column]):
ax.hist(current_data[column].dropna(), bins=20, alpha=0.7, color='#059669', edgecolor='white')
ax.set_xlabel(column)
ax.set_ylabel('Frequency')
ax.set_title(f'Histogram: {column}', fontsize=14, fontweight='bold')
ax.grid(axis='y', alpha=0.3)
else:
plt.close(fig)
return f"❌ Cannot create histogram for non-numeric column '{column}'. Try 'bar' chart instead."
else:
plt.close(fig)
return f"❌ Unsupported chart type '{chart_type}'. Use: bar or histogram"
plt.tight_layout()
# Save to BytesIO buffer and encode to base64
buffer = BytesIO()
plt.savefig(buffer, format='png', dpi=100, bbox_inches='tight')
buffer.seek(0)
image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
plt.close(fig)
# Return HTML with embedded base64 image
return f"""✅ Chart created successfully for '{column}' ({chart_type} chart).
<img src="data:image/png;base64,{image_base64}" alt="{chart_type.title()} chart for {column}" style="max-width: 100%; height: auto; margin: 10px 0; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
Chart shows the distribution of values in the '{column}' column."""
except Exception as e:
import traceback
error_details = traceback.format_exc()
return f"❌ Error creating chart: {str(e)}\n\nDetails:\n{error_details}"
def get_correlation_analysis() -> str:
"""Get correlation analysis for numeric columns."""
global current_data
if current_data is None:
return "❌ No data loaded. Please upload a CSV file first."
numeric_cols = current_data.select_dtypes(include=[np.number]).columns
if len(numeric_cols) < 2:
return "❌ Need at least 2 numerical columns to calculate correlations."
corr_matrix = current_data[numeric_cols].corr()
result = ["## Correlation Analysis\n"]
result.append("### Correlation Matrix:\n")
result.append(dataframe_to_markdown(corr_matrix, max_rows=20))
result.append("\n### Key Insights:")
strong_corr = []
for i in range(len(numeric_cols)):
for j in range(i+1, len(numeric_cols)):
corr_val = corr_matrix.iloc[i, j]
if abs(corr_val) > 0.7:
strength = "strong positive" if corr_val > 0 else "strong negative"
emoji = "📈" if corr_val > 0 else "📉"
strong_corr.append(f"- {emoji} **{numeric_cols[i]}** and **{numeric_cols[j]}**: {strength} correlation ({corr_val:.3f})")
if strong_corr:
result.extend(strong_corr)
else:
result.append("- ℹ️ No strong correlations found (|r| > 0.7)")
return "\n".join(result)