A lightweight, hands-on chat interface for teaching effective AI prompting. Students practice writing prompts, iterate on responses, and submit their best attempt — all through a familiar ChatGPT-like interface.
- Conversational UI — clean, minimal interface similar to ChatGPT
- Streaming responses — real-time token streaming via Server-Sent Events
- Markdown rendering — responses are fully formatted with syntax-highlighted code blocks (20+ languages)
- File attachments — attach images and documents for the AI to reference
- Session persistence — conversations are saved to a local JSON file and restored on page reload
- Multi-session sidebar — create, switch between, and delete past conversations
- Cosmo persona — a friendly AI tutor who encourages good prompting habits
| Layer | Technology |
|---|---|
| Frontend | Vanilla JS, HTML, CSS — no framework |
| Design system | CodeSignal Bespoke Design System (git submodule) |
| Backend | Node.js + Express |
| AI orchestration | |
| Markdown | marked + highlight.js |
| Bundler | esbuild |
- Node.js 18+
git clone --recurse-submodules https://github.com/your-org/chat-cpt.git
cd chat-cptIf you already cloned without --recurse-submodules:
git submodule update --init --recursivenpm installCreate a .env file in the project root:
Never commit
.env— it is listed in.gitignore.
Builds the JS bundle once, then starts the server with --watch for automatic restarts on server changes:
npm run devBuilds and starts the server:
npm startThe app is available at http://localhost:3000.
Runtime behaviour can be tuned via chat-config.json in the project root. Copy the example file to get started:
cp chat-config.example.json chat-config.json| Key | Type | Default | Description |
|---|---|---|---|
initialPrompt |
string |
"" |
Text pre-populated in the composer on every page load and new chat. |
model |
string |
first available | Default model ID used when creating new sessions. |
allowedModels |
string[] |
[] (all) |
Whitelist of exact model IDs for the picker. Empty means all models from current-models.txt. |
allowedModelFamilies |
string[] |
[] (all) |
Filter models by provider prefix (e.g. ["openai", "openrouter/deepseek"]). Can be combined with allowedModels. |
modelDisplayNames |
object |
{} |
Map of model ID to display label shown in the dropdown (e.g. {"openai/gpt-5": "GPT-5"}). Models without an entry use the default provider/model label. |
temperature |
number |
0.7 |
Controls randomness (0–2). Lower = more focused, higher = more creative. Ignored when thinking is enabled. |
systemPromptExtra |
string |
"" |
Additional instructions appended to Cosmo's system prompt at session creation. |
title |
string |
"ChatCPT" |
Override the app name shown in the sidebar header and browser tab. |
heading |
string |
"What's on your mind?" |
Override the empty-state heading shown before the first message. |
footer |
string |
"Cosmo can make mistakes..." |
Override the disclaimer text below the composer. |
hideSettings |
boolean |
false |
Hide the settings button from the sidebar. |
hideHistory |
boolean |
false |
Hide the conversation history sidebar. Only one chat exists at a time; "New chat" deletes the current conversation (with confirmation). |
hideFileUpload |
boolean |
false |
Hide the image and file attachment buttons from the composer. |
Example chat-config.json:
{
"initialPrompt": "Write a prompt that asks Cosmo to explain what a large language model is.",
"model": "anthropic/claude-sonnet-4-6",
"allowedModels": ["anthropic/claude-sonnet-4-6", "openrouter/deepseek/deepseek-r1"],
"modelDisplayNames": {
"anthropic/claude-sonnet-4-6": "Claude Sonnet 4.6",
"openrouter/deepseek/deepseek-r1": "DeepSeek R1"
},
"temperature": 0.7,
"systemPromptExtra": "Focus all examples on Python.",
"title": "My AI Tutor",
"heading": "How can I help you today?",
"footer": "AI responses may be inaccurate. Always verify.",
"hideSettings": true,
"hideHistory": true,
"hideFileUpload": false
}
chat-config.jsonis committed to source control so you can version your configuration alongside the project.chat-config.example.jsonserves as a reference template.
chat-cpt/
├── agents/
│ └── cosmo-tutor/ # Agent definition
│ ├── settings.json
│ ├── protocol.yaml
│ └── prompts/
│ ├── system.md # Cosmo's persona + instructions
│ └── user-message.md
├── design-system/ # Git submodule (CodeSignal Bespoke DS)
├── public/
│ ├── index.html
│ ├── app.js # Frontend source (bundled by esbuild)
│ ├── app.bundle.js # Generated — do not edit directly
│ └── app.css
├── server.js # Express server + orchestration proxy
├── chat-config.json # Runtime configuration (see Configuration section)
├── chat-config.example.json # Reference template for chat-config.json
├── chat-sessions.json # Auto-generated session storage (gitignored)
├── .env # Secret credentials (gitignored)
└── package.json
Conversations are stored in chat-sessions.json at the project root. This file is gitignored and lives only on the local machine. Each entry contains:
{
"session_id": "...",
"created_at": "...",
"updated_at": "...",
"messages": [
{ "role": "user", "content": "...", "files": [], "timestamp": "..." },
{ "role": "assistant", "content": "...", "files": [], "timestamp": "..." }
],
"selected_submission": null
}