Add initial test script to print "hello"

This commit is contained in:
JamesTheGiblet 2026-01-08 17:51:49 +00:00
parent f0e2c975aa
commit 962e167667
8 changed files with 595 additions and 0 deletions

BIN
.gitignore vendored

Binary file not shown.

0
data/training_data.jsonl Normal file
View file

View file

@ -0,0 +1,594 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="apple-touch-icon" sizes="192x192" href="/favicon-192x192.png">
<!-- 'theme-color' is not supported by Firefox, Firefox for Android, Opera.
It is used by Chrome and some Android browsers for UI theming. -->
<!-- <meta name="theme-color" content="#1e1e1e"> -->
<!-- For Safari on iOS -->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>BuddAI Web</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<link id="hljs-theme" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
<style>
:root {
--bg-color: #1e1e1e;
--text-color: #d4d4d4;
--header-bg: #252526;
--border-color: #3e3e3e;
--input-bg: #3c3c3c;
--user-msg-bg: #007acc;
--user-msg-text: white;
--assistant-msg-bg: #2d2d2d;
--btn-bg: #0e639c;
--btn-hover: #1177bb;
--code-bg: #111;
--code-border: #444;
--code-text: #9cdcfe;
}
body.light-mode {
--bg-color: #ffffff;
--text-color: #333333;
--header-bg: #f3f3f3;
--border-color: #e1e1e1;
--input-bg: #ffffff;
--user-msg-bg: #0078d4;
--user-msg-text: white;
--assistant-msg-bg: #f4f4f4;
--btn-bg: #0078d4;
--btn-hover: #106ebe;
--code-bg: #f6f8fa;
--code-border: #d1d5da;
--code-text: #24292e;
}
.sidebar-left { width: 260px; background: var(--header-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; flex-shrink: 0; transition: width 0.3s, opacity 0.3s; overflow: hidden; }
.sidebar-left.collapsed { width: 0; opacity: 0; border: none; }
.session-list { flex: 1; overflow-y: auto; }
.session-item { padding: 12px 15px; cursor: pointer; border-bottom: 1px solid var(--border-color); font-size: 0.85em; color: var(--text-color); transition: background 0.2s; display: flex; justify-content: space-between; align-items: center; }
.session-info { flex: 1; overflow: hidden; }
.rename-input { width: 100%; background: var(--input-bg); color: var(--text-color); border: 1px solid var(--btn-bg); padding: 4px; border-radius: 3px; font-family: inherit; }
.edit-icon, .delete-icon { opacity: 0; cursor: pointer; padding: 4px; font-size: 1.1em; margin-left: 2px; }
.session-item:hover .edit-icon, .session-item:hover .delete-icon { opacity: 0.5; }
.edit-icon:hover, .delete-icon:hover { opacity: 1; }
.delete-icon:hover { color: #ff4444; }
.session-item:hover { background: var(--bg-color); }
.session-item.active { background: var(--btn-bg); color: white; border-color: var(--btn-bg); }
.new-chat-btn { margin: 15px; padding: 10px; background: var(--btn-bg); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
.new-chat-btn:hover { background: var(--btn-hover); }
.session-date { font-weight: bold; display: block; margin-bottom: 4px; }
.session-id { font-size: 0.8em; opacity: 0.6; font-family: monospace; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: var(--bg-color); color: var(--text-color); margin: 0; display: flex; justify-content: center; height: 100vh; transition: background 0.3s, color 0.3s; }
#root { width: 100%; max-width: 100%; display: flex; flex-direction: column; height: 100%; }
.main-layout { display: flex; flex: 1; overflow: hidden; }
.chat-section { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.chat-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 15px; }
.side-panel { width: 45%; border-left: 1px solid var(--border-color); display: flex; flex-direction: column; background: var(--bg-color); }
.side-header { padding: 10px 15px; background: var(--header-bg); border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; font-size: 0.9em; }
.side-content { flex: 1; overflow: auto; background: var(--code-bg); position: relative; }
.side-content pre { margin: 0; padding: 15px; min-height: 100%; box-sizing: border-box; }
.message { padding: 15px; border-radius: 8px; max-width: 85%; line-height: 1.5; }
.timestamp { font-size: 0.75em; opacity: 0.7; margin-bottom: 4px; display: block; font-family: monospace; }
.user { align-self: flex-end; background: var(--user-msg-bg); color: var(--user-msg-text); }
.assistant { align-self: flex-start; background: var(--assistant-msg-bg); border: 1px solid var(--border-color); }
.input-area { padding: 20px; background: var(--header-bg); border-top: 1px solid var(--border-color); display: flex; gap: 10px; }
input { flex: 1; padding: 12px; border-radius: 4px; border: 1px solid var(--border-color); background: var(--input-bg); color: var(--text-color); outline: none; }
button { padding: 12px 24px; background: var(--btn-bg); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
button:hover { background: var(--btn-hover); }
.stop-btn { background: #d32f2f; }
.stop-btn:hover { background: #b71c1c; }
.header { padding: 15px 20px; background: var(--header-bg); border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
.clear-btn { background: transparent; border: 1px solid var(--border-color); color: var(--text-color); padding: 5px 12px; font-size: 0.8em; cursor: pointer; border-radius: 4px; opacity: 0.8; }
.clear-btn:hover { background: var(--border-color); opacity: 1; }
.status-badge { font-size: 0.7rem; padding: 2px 8px; border-radius: 10px; margin-left: 10px; text-transform: uppercase; font-weight: bold; letter-spacing: 0.5px; }
.online { color: #4caf50; border: 1px solid #4caf50; background: rgba(76, 175, 80, 0.1); }
.offline { color: #f44336; border: 1px solid #f44336; background: rgba(244, 67, 54, 0.1); }
.connecting { color: #ff9800; border: 1px solid #ff9800; background: rgba(255, 152, 0, 0.1); }
.suggestions { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 8px; }
.suggestion-pill { background: var(--bg-color); border: 1px solid var(--btn-bg); color: var(--btn-bg); padding: 6px 12px; border-radius: 15px; font-size: 0.85em; cursor: pointer; transition: all 0.2s; }
.suggestion-pill:hover { background: var(--btn-bg); color: white; }
/* Code Blocks */
.code-wrapper { position: relative; margin: 10px 0; border: 1px solid var(--code-border); border-radius: 6px; overflow: hidden; }
.code-header { display: flex; justify-content: space-between; align-items: center; background: var(--header-bg); padding: 5px 10px; font-size: 0.8em; border-bottom: 1px solid var(--border-color); color: var(--text-color); opacity: 0.8; }
.copy-code-btn { background: transparent; border: 1px solid var(--border-color); color: var(--text-color); padding: 2px 8px; font-size: 0.9em; cursor: pointer; border-radius: 3px; }
.copy-code-btn:hover { background: var(--border-color); }
pre { background: var(--code-bg); padding: 15px; margin: 0; overflow-x: auto; }
code { font-family: 'Consolas', 'Courier New', monospace; color: var(--code-text); }
p { margin: 0 0 10px 0; }
@keyframes flame-flicker {
0% { transform: scale(1); filter: drop-shadow(0 0 2px #ff9800); }
50% { transform: scale(1.1) rotate(-2deg); filter: drop-shadow(0 0 6px #ff4500); }
100% { transform: scale(1) rotate(2deg); filter: drop-shadow(0 0 2px #ff9800); }
}
.loading-flame { font-size: 24px; animation: flame-flicker 0.6s infinite; display: inline-block; }
.hljs { background: transparent !important; padding: 0 !important; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// Configure Marked for Code Copy
const renderer = new marked.Renderer();
renderer.code = (code, language) => {
// Handle Marked v12+ signature where first arg is a token object
if (typeof code === 'object' && code !== null && code.text) {
language = code.lang;
code = code.text;
}
const validLang = (language && hljs.getLanguage(language)) ? language : 'plaintext';
let highlighted = code;
try {
const result = hljs.highlight(code, { language: validLang });
highlighted = result.value || code;
} catch (e) { /* fallback */ }
return `<div class="code-wrapper">
<div class="code-header">
<span>${language || 'text'}</span>
<div style="display:flex; gap:5px;">
<button class="copy-code-btn" onclick="window.copyToClipboard(this)">Copy</button>
<button class="copy-code-btn" onclick="window.sendToSidebar(this)">Sidebar</button>
</div>
</div>
<pre><code class="hljs ${validLang}">${highlighted}</code></pre>
</div>`;
};
marked.setOptions({ renderer });
window.copyToClipboard = (btn) => {
const wrapper = btn.closest('.code-wrapper');
const code = wrapper.querySelector('code').innerText;
navigator.clipboard.writeText(code).then(() => {
const original = btn.innerText;
btn.innerText = 'Copied!';
setTimeout(() => btn.innerText = original, 2000);
});
};
window.sendToSidebar = (btn) => {
const wrapper = btn.closest('.code-wrapper');
const code = wrapper.querySelector('code').innerText;
if (window.updateSidebar) window.updateSidebar(code);
};
window.downloadCode = (content) => {
if (!content) return;
if (typeof content !== 'string') return;
let ext = 'txt';
// Heuristic for Arduino
if (content.includes('void setup()') && content.includes('void loop()')) {
ext = 'ino';
} else {
try {
const result = hljs.highlightAuto(content);
if (result.language) {
const map = {
python: 'py', javascript: 'js', cpp: 'cpp', c: 'c',
arduino: 'ino', html: 'html', css: 'css',
csharp: 'cs', java: 'java', bash: 'sh', json: 'json',
markdown: 'md', sql: 'sql'
};
ext = map[result.language] || result.language;
}
} catch (e) {}
}
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `buddai_generated.${ext}`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const { useState, useEffect, useRef } = React;
function App() {
const [history, setHistory] = useState([]);
const [sessions, setSessions] = useState([]);
const [currentSessionId, setCurrentSessionId] = useState(null);
const [showSidebar, setShowSidebar] = useState(true);
const [editingSession, setEditingSession] = useState(null);
const [renameText, setRenameText] = useState("");
const [feedbackGiven, setFeedbackGiven] = useState({});
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [status, setStatus] = useState("connecting");
const [forgeMode, setForgeMode] = useState("2");
const [theme, setTheme] = useState("dark");
const [sidebarContent, setSidebarContent] = useState("");
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const endRef = useRef(null);
const abortControllerRef = useRef(null);
const socketRef = useRef(null);
const scrollToBottom = () => {
endRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
window.updateSidebar = (code) => {
setSidebarContent(code);
setIsSidebarOpen(true);
};
document.body.className = theme === 'light' ? 'light-mode' : '';
const hljsTheme = document.getElementById('hljs-theme');
if (hljsTheme) {
hljsTheme.href = theme === 'light'
? "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-light.min.css"
: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css";
}
}, [theme]);
useEffect(() => {
scrollToBottom();
}, [history]);
const fetchSessions = () => fetch("/api/sessions").then(res => res.json()).then(data => setSessions(data.sessions || [])).catch(console.error);
useEffect(() => {
// Check System Status
const checkStatus = async () => {
try {
const res = await fetch("/");
setStatus(res.ok ? "online" : "offline");
} catch {
setStatus("offline");
}
};
checkStatus();
const timer = setInterval(checkStatus, 10000);
fetchSessions();
// Load History
fetch("/api/history")
.then(res => res.json())
.then(data => {
if (data.history) {
setHistory(data.history.filter(m => m.role === 'user' || m.role === 'assistant'));
}
})
.catch(console.error);
return () => clearInterval(timer);
}, []);
useEffect(() => {
// Initialize WebSocket
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/api/ws/chat`;
socketRef.current = new WebSocket(wsUrl);
return () => {
if (socketRef.current) socketRef.current.close();
};
}, []);
const handleRename = async (e) => {
if (e.key === 'Enter') {
await fetch("/api/session/rename", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_id: editingSession, title: renameText })
});
setEditingSession(null);
fetchSessions();
}
};
const handleDeleteSession = async (e, sessionId) => {
e.stopPropagation();
if (!window.confirm("Delete this chat?")) return;
await fetch("/api/session/delete", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_id: sessionId })
});
if (sessionId === currentSessionId) {
setHistory([]);
setCurrentSessionId(null);
}
fetchSessions();
};
const handleFeedback = async (messageId, positive) => {
if (!messageId || feedbackGiven[messageId]) return;
setFeedbackGiven(prev => ({ ...prev, [messageId]: positive ? 'positive' : 'negative' }));
try {
await fetch("/api/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message_id: messageId, positive: positive })
});
} catch (e) {
console.error("Feedback submission failed", e);
// Revert UI on failure
setFeedbackGiven(prev => { const n = {...prev}; delete n[messageId]; return n; });
}
};
const loadSession = async (sessionId) => {
setLoading(true);
try {
const res = await fetch("/api/session/load", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_id: sessionId })
});
const data = await res.json();
setHistory(data.history || []);
setCurrentSessionId(data.session_id);
} catch (e) { console.error(e); }
setLoading(false);
};
const startNewSession = async () => {
const res = await fetch("/api/session/new", { method: "POST" });
const data = await res.json();
setCurrentSessionId(data.session_id);
setHistory([]);
fetchSessions();
};
const sendMessage = async (textOverride = null) => {
const msgText = typeof textOverride === 'string' ? textOverride : input;
if (!msgText.trim()) return;
const userMsg = { role: "user", content: msgText, timestamp: new Date().toISOString() };
setHistory(prev => [...prev, userMsg]);
if (!textOverride) setInput("");
setLoading(true);
// Use WebSocket if available
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
// Add placeholder for streaming response
setHistory(prev => [...prev, { role: "assistant", content: "" }]);
socketRef.current.send(JSON.stringify({
message: msgText,
forge_mode: forgeMode,
user_id: "default" // In a real app, get from auth context
}));
socketRef.current.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'token') {
setHistory(prev => {
const newHistory = [...prev];
const lastMsg = newHistory[newHistory.length - 1];
if (lastMsg.role === 'assistant') {
lastMsg.content += data.content;
}
return newHistory;
});
} else if (data.type === 'end') {
setLoading(false);
setHistory(prev => {
const newHistory = [...prev];
const lastMsg = newHistory[newHistory.length - 1];
if (lastMsg && lastMsg.role === 'assistant') {
lastMsg.id = data.message_id;
}
return newHistory;
});
if (!currentSessionId) fetchSessions();
}
};
} else {
// Fallback to HTTP
try {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: msgText, forge_mode: forgeMode })
});
const data = await res.json();
setHistory(prev => [...prev, { role: "assistant", content: data.response, id: data.message_id }]);
if (!currentSessionId) fetchSessions();
} catch (err) {
setHistory(prev => [...prev, { role: "assistant", content: "Error connecting to BuddAI server." }]);
}
setLoading(false);
}
};
const stopGeneration = () => {
if (abortControllerRef.current) abortControllerRef.current.abort();
};
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append("file", file);
setLoading(true);
setHistory(prev => [...prev, { role: "assistant", content: `📥 Uploading and indexing **${file.name}**...` }]);
try {
const res = await fetch("/api/upload", { method: "POST", body: formData });
const data = await res.json();
setHistory(prev => [...prev, { role: "assistant", content: data.message }]);
} catch (err) {
setHistory(prev => [...prev, { role: "assistant", content: "❌ Upload failed." }]);
}
setLoading(false);
e.target.value = null;
};
const parseContent = (content) => {
const parts = content.split("\n\nPROACTIVE: > ");
const text = parts[0];
let suggestions = [];
if (parts.length > 1) {
// Split "1. Suggestion 2. Suggestion" patterns
suggestions = parts[1].split(/\d+\.\s/).map(s => s.trim()).filter(s => s);
}
return { text, suggestions };
};
return (
<>
<div className="header">
<div style={{display:'flex', alignItems:'center'}}>
<img src="/favicon.ico" alt="BuddAI" style={{height: '24px', marginRight: '10px'}} />
<h3 style={{margin:0}}>BuddAI v3.2</h3>
<span className={`status-badge ${status}`}>{status}</span>
</div>
<div style={{display:'flex', gap:'10px'}}>
<input
type="file"
id="upload-input"
style={{display:'none'}}
onChange={handleFileUpload}
/>
<button className="clear-btn" onClick={() => document.getElementById('upload-input').click()}>📂 Upload</button>
<button className="clear-btn" onClick={() => setShowSidebar(!showSidebar)}>{showSidebar ? 'Hide History' : 'Show History'}</button>
<button className="clear-btn" onClick={() => setIsSidebarOpen(!isSidebarOpen)}>{isSidebarOpen ? 'Hide Code' : 'Show Code'}</button>
<button className="clear-btn" onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>{theme === 'dark' ? '☀️' : '🌙'}</button>
<select
value={forgeMode}
onChange={(e) => setForgeMode(e.target.value)}
style={{background: 'var(--input-bg)', color: 'var(--text-color)', border: '1px solid var(--border-color)', padding: '5px', borderRadius: '4px', fontSize: '0.8em'}}>
<option value="1">Aggressive (Combat)</option>
<option value="2">Balanced (Standard)</option>
<option value="3">Graceful (Smooth)</option>
</select>
<button className="clear-btn" onClick={() => setHistory([])}>Clear</button>
</div>
</div>
<div className="main-layout">
<div className={`sidebar-left ${!showSidebar ? 'collapsed' : ''}`}>
<button className="new-chat-btn" onClick={startNewSession}>+ New Chat</button>
<div className="session-list">
{sessions.map(s => (
<div key={s.id} className={`session-item ${s.id === currentSessionId ? 'active' : ''}`} onClick={() => loadSession(s.id)}>
{editingSession === s.id ? (
<input
className="rename-input"
value={renameText}
onChange={e => setRenameText(e.target.value)}
onKeyDown={handleRename}
onBlur={() => setEditingSession(null)}
autoFocus
onClick={e => e.stopPropagation()}
/>
) : (
<>
<div className="session-info">
<span className="session-date">{s.title || (new Date(s.date).toLocaleDateString() + ' ' + new Date(s.date).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}))}</span>
{!s.title && <span className="session-id">{s.id}</span>}
</div>
<div style={{display:'flex'}}>
<span className="edit-icon" onClick={(e) => {
e.stopPropagation();
setEditingSession(s.id);
setRenameText(s.title || "");
}}>✏️</span>
<span className="delete-icon" onClick={(e) => handleDeleteSession(e, s.id)}>🗑️</span>
</div>
</>
)}
</div>
))}
</div>
</div>
<div className="chat-section">
<div className="chat-container">
{history.length === 0 && <div style={{textAlign: 'center', marginTop: '50px', color: '#666'}}><p>Ready to build.</p></div>}
{history.map((msg, i) => {
const { text, suggestions } = msg.role === 'assistant' ? parseContent(msg.content) : { text: msg.content, suggestions: [] };
const timeStr = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '';
return (
<div key={i} className={`message ${msg.role}`}>
{timeStr && <span className="timestamp">{timeStr}</span>}
<div dangerouslySetInnerHTML={{ __html: marked.parse(text) }} />
{suggestions.length > 0 && (
<div className="suggestions">
{suggestions.map((s, idx) => (
<div key={idx} className="suggestion-pill" onClick={() => sendMessage(s)}>{s}</div>
))}
</div>
)}
{msg.role === 'assistant' && msg.id && !loading && (
<div className="feedback-btns">
<button
className={`feedback-btn ${feedbackGiven[msg.id] === 'positive' ? 'selected' : ''}`}
onClick={() => handleFeedback(msg.id, true)}
disabled={!!feedbackGiven[msg.id]}
title="Good response"
>👍</button>
<button
className={`feedback-btn ${feedbackGiven[msg.id] === 'negative' ? 'selected' : ''}`}
onClick={() => handleFeedback(msg.id, false)}
disabled={!!feedbackGiven[msg.id]}
title="Bad response"
>👎</button>
</div>
)}
</div>
);
})}
{loading && <div className="message assistant"><span className="loading-flame">🔥</span></div>}
<div ref={endRef} />
</div>
<div className="input-area">
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyPress={e => e.key === 'Enter' && sendMessage()}
placeholder="Ask BuddAI to build something..."
autoFocus
/>
{loading ? (
<button className="stop-btn" onClick={stopGeneration}>Stop</button>
) : (
<button onClick={() => sendMessage()}>Send</button>
)}
</div>
</div>
{isSidebarOpen && (
<div className="side-panel">
<div className="side-header">
<span>Code Workspace</span>
<div style={{display: 'flex', gap: '5px'}}>
<button className="copy-code-btn" onClick={() => setSidebarContent("")}>Clear</button>
<button className="copy-code-btn" onClick={() => navigator.clipboard.writeText(sidebarContent)}>Copy</button>
<button className="copy-code-btn" onClick={() => window.downloadCode(sidebarContent)}>Download</button>
</div>
</div>
<div className="side-content">
<pre><code className="hljs" dangerouslySetInnerHTML={{__html: sidebarContent ? hljs.highlightAuto(sidebarContent).value : '// Select code from chat to view here'}} /></pre>
</div>
</div>
)}
</div>
</>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -0,0 +1 @@
print("hello")