-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdockerfile
More file actions
257 lines (209 loc) · 17.4 KB
/
dockerfile
File metadata and controls
257 lines (209 loc) · 17.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
FROM python:3.11-slim
RUN apt-get update&&apt-get install -y curl&&curl -fsSL https://ollama.com/install.sh|sh&&apt-get clean&&rm -rf /var/lib/apt/lists/*&&pip install --no-cache-dir Flask==3.0.0
WORKDIR /app
RUN mkdir -p /app/projects&&mkdir -p /app/templates
RUN echo '#!/bin/bash\nollama serve &\nsleep 5\nif ! ollama list|grep -q "wolftech-innovations/Gem";then\n ollama pull wolftech-innovations/Gem\nfi\npython app.py' > /start.sh&&chmod +x /start.sh
RUN cat > /app/app.py << 'PYEOF'
from flask import Flask, render_template, request, jsonify, send_from_directory
import subprocess
import os
import json
import time
from pathlib import Path
import threading
import re
app = Flask(__name__)
app.config['PROJECTS_DIR'] = '/app/projects'
class ProjectManager:
def __init__(self, projects_dir):
self.projects_dir = Path(projects_dir)
self.projects_dir.mkdir(exist_ok=True)
self.active_generations = {}
def list_projects(self):
projects = []
for project_dir in self.projects_dir.iterdir():
if project_dir.is_dir():
prompt_file = project_dir / 'prompt.txt'
files = [f.name for f in project_dir.iterdir() if f.is_file() and f.name != 'prompt.txt']
projects.append({
'name': project_dir.name,
'prompt': prompt_file.read_text() if prompt_file.exists() else '',
'files': files,
'created': project_dir.stat().st_mtime
})
return sorted(projects, key=lambda x: x['created'], reverse=True)
def create_project(self, name):
project_dir = self.projects_dir / name
project_dir.mkdir(exist_ok=True)
prompt_file = project_dir / 'prompt.txt'
if not prompt_file.exists():
prompt_file.write_text('# Enter your code generation prompt here\n')
return True
def delete_project(self, name):
project_dir = self.projects_dir / name
if project_dir.exists():
import shutil
shutil.rmtree(project_dir)
return True
return False
def get_project_files(self, name):
project_dir = self.projects_dir / name
files = []
for file_path in project_dir.rglob('*'):
if file_path.is_file() and file_path.name != 'prompt.txt':
rel_path = file_path.relative_to(project_dir)
files.append({
'path': str(rel_path),
'size': file_path.stat().st_size,
'modified': file_path.stat().st_mtime
})
return files
def get_file_content(self, project_name, file_path):
full_path = self.projects_dir / project_name / file_path
if full_path.exists():
return full_path.read_text()
return None
def save_prompt(self, project_name, prompt):
prompt_file = self.projects_dir / project_name / 'prompt.txt'
prompt_file.write_text(prompt)
return True
def generate_code(self, project_name, prompt):
project_dir = self.projects_dir / project_name
def run_generation():
try:
self.active_generations[project_name] = {'status': 'running', 'progress': 'Calling Gem...'}
result = subprocess.run(
['ollama', 'run', 'wolftech-innovations/Gem', prompt],
capture_output=True,
text=True,
timeout=300,
cwd=str(project_dir)
)
raw_output = result.stdout.strip()
cleaned_output = self.extract_code(raw_output)
if cleaned_output:
self.active_generations[project_name]['progress'] = 'Writing files...'
self.parse_and_write_files(project_dir, cleaned_output, prompt)
self.active_generations[project_name] = {'status': 'complete', 'progress': 'Done'}
else:
self.active_generations[project_name] = {'status': 'error', 'progress': 'No code generated'}
except Exception as e:
self.active_generations[project_name] = {'status': 'error', 'progress': str(e)}
thread = threading.Thread(target=run_generation)
thread.start()
return True
def extract_code(self, raw_output):
lines = raw_output.split('\n')
code_lines = []
in_code_block = False
for line in lines:
stripped = line.strip()
if stripped.startswith('>>>') or stripped.startswith('Send a message'):
continue
if stripped.startswith('```'):
in_code_block = not in_code_block
continue
if in_code_block or (not stripped.startswith('>>>') and stripped):
code_lines.append(line)
code = '\n'.join(code_lines).strip()
return code
def parse_and_write_files(self, project_dir, output, prompt):
lines = output.split('\n')
current_file = None
current_content = []
file_marker_pattern = re.compile(r'^//\s*([a-zA-Z0-9_\-./]+\.[a-zA-Z0-9]+)\s*$')
for line in lines:
match = file_marker_pattern.match(line.strip())
if match:
if current_file and current_content:
self.write_file(project_dir / current_file, '\n'.join(current_content))
current_file = match.group(1)
current_content = []
else:
current_content.append(line)
if current_file and current_content:
self.write_file(project_dir / current_file, '\n'.join(current_content))
elif not current_file:
inferred = self.infer_filename(prompt, output)
self.write_file(project_dir / inferred, output)
def infer_filename(self, prompt, code):
prompt_lower = prompt.lower()
extensions = {
'python': '.py', 'javascript': '.js', 'typescript': '.ts',
'java': '.java', 'c++': '.cpp', 'html': '.html', 'css': '.css'
}
for lang, ext in extensions.items():
if lang in prompt_lower:
return f'main{ext}' if ext == '.py' else f'index{ext}'
if 'print(' in code or 'def ' in code:
return 'main.py'
elif 'function' in code or 'const' in code:
return 'index.js'
return 'output.txt'
def write_file(self, filepath, content):
filepath.parent.mkdir(parents=True, exist_ok=True)
filepath.write_text(content)
pm = ProjectManager(app.config['PROJECTS_DIR'])
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/projects', methods=['GET'])
def list_projects():
return jsonify(pm.list_projects())
@app.route('/api/projects', methods=['POST'])
def create_project():
data = request.json
name = data.get('name')
if name:
pm.create_project(name)
return jsonify({'success': True})
return jsonify({'success': False}), 400
@app.route('/api/projects/<name>', methods=['DELETE'])
def delete_project(name):
if pm.delete_project(name):
return jsonify({'success': True})
return jsonify({'success': False}), 404
@app.route('/api/projects/<name>/files', methods=['GET'])
def get_project_files(name):
return jsonify(pm.get_project_files(name))
@app.route('/api/projects/<name>/files/<path:filepath>', methods=['GET'])
def get_file_content(name, filepath):
content = pm.get_file_content(name, filepath)
if content is not None:
return jsonify({'content': content})
return jsonify({'error': 'File not found'}), 404
@app.route('/api/projects/<name>/prompt', methods=['POST'])
def save_prompt(name):
data = request.json
prompt = data.get('prompt')
if prompt:
pm.save_prompt(name, prompt)
return jsonify({'success': True})
return jsonify({'success': False}), 400
@app.route('/api/projects/<name>/generate', methods=['POST'])
def generate_code(name):
data = request.json
prompt = data.get('prompt')
if prompt:
pm.generate_code(name, prompt)
return jsonify({'success': True})
return jsonify({'success': False}), 400
@app.route('/api/projects/<name>/status', methods=['GET'])
def get_status(name):
status = pm.active_generations.get(name, {'status': 'idle', 'progress': ''})
return jsonify(status)
@app.route('/api/projects/<name>/download/<path:filepath>')
def download_file(name, filepath):
return send_from_directory(
os.path.join(app.config['PROJECTS_DIR'], name),
filepath,
as_attachment=True
)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
PYEOF
RUN cat > /app/templates/index.html << 'HTMLEOF'
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>GEM - WolfTech Innovations</title><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Courier New',monospace;background:#000;color:#fff;height:100vh;overflow:hidden}.container{display:grid;grid-template-columns:300px 1fr;height:100vh}.sidebar{background:#0a0a0a;border-right:1px solid #333;display:flex;flex-direction:column}.header{padding:20px;border-bottom:1px solid #333}.header h1{font-size:18px;font-weight:normal;margin-bottom:5px}.header .subtitle{font-size:11px;color:#666}.projects-header{padding:15px 20px;border-bottom:1px solid #333;display:flex;justify-content:space-between;align-items:center}.projects-header h2{font-size:14px;font-weight:normal}.btn{background:#fff;color:#000;border:none;padding:5px 12px;cursor:pointer;font-family:inherit;font-size:12px;transition:opacity .2s}.btn:hover{opacity:.8}.btn-sm{padding:3px 8px;font-size:11px}.projects-list{flex:1;overflow-y:auto}.project-item{padding:12px 20px;border-bottom:1px solid #1a1a1a;cursor:pointer;transition:background .2s}.project-item:hover{background:#1a1a1a}.project-item.active{background:#1a1a1a;border-left:2px solid #fff}.project-name{font-size:13px;margin-bottom:4px}.project-meta{font-size:10px;color:#666}.main-content{display:flex;flex-direction:column}.editor-container{flex:1;display:flex;flex-direction:column;padding:20px;overflow:hidden}.editor-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px}.editor-header h3{font-size:16px;font-weight:normal}.status{font-size:11px;color:#666;padding:5px 10px;border:1px solid #333}.status.running{color:#fff;border-color:#fff}.editor{flex:1;display:flex;gap:20px;overflow:hidden}.prompt-section{flex:1;display:flex;flex-direction:column}.prompt-section h4{font-size:12px;margin-bottom:10px;color:#999}textarea{flex:1;background:#0a0a0a;color:#fff;border:1px solid #333;padding:15px;font-family:'Courier New',monospace;font-size:13px;resize:none;outline:none}textarea:focus{border-color:#666}.files-section{flex:1;display:flex;flex-direction:column}.files-list{flex:1;background:#0a0a0a;border:1px solid #333;overflow-y:auto}.file-item{padding:10px 15px;border-bottom:1px solid #1a1a1a;cursor:pointer;display:flex;justify-content:space-between;align-items:center}.file-item:hover{background:#1a1a1a}.file-name{font-size:12px}.file-size{font-size:10px;color:#666}.actions{margin-top:15px;display:flex;gap:10px}.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.9);justify-content:center;align-items:center;z-index:1000}.modal.active{display:flex}.modal-content{background:#0a0a0a;border:1px solid #333;padding:30px;min-width:400px}.modal-content h3{font-size:16px;margin-bottom:20px;font-weight:normal}input[type="text"]{width:100%;background:#000;color:#fff;border:1px solid #333;padding:10px;font-family:inherit;font-size:13px;margin-bottom:20px;outline:none}input[type="text"]:focus{border-color:#666}.modal-actions{display:flex;gap:10px;justify-content:flex-end}.empty-state{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;color:#666}.empty-state h3{font-size:16px;margin-bottom:10px;font-weight:normal}.empty-state p{font-size:12px}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#0a0a0a}::-webkit-scrollbar-thumb{background:#333}::-webkit-scrollbar-thumb:hover{background:#555}</style></head><body><div class="container"><div class="sidebar"><div class="header"><h1>GEM</h1><div class="subtitle">WolfTech Innovations</div></div><div class="projects-header"><h2>PROJECTS</h2><button class="btn btn-sm"onclick="showNewProjectModal()">NEW</button></div><div class="projects-list"id="projectsList"></div></div><div class="main-content"><div id="emptyState"class="empty-state"><h3>NO PROJECT SELECTED</h3><p>Create or select a project to begin</p></div><div id="editorContainer"class="editor-container"style="display:none"><div class="editor-header"><h3 id="projectTitle">Project</h3><div class="status"id="statusIndicator">IDLE</div></div><div class="editor"><div class="prompt-section"><h4>PROMPT</h4><textarea id="promptEditor"placeholder="Enter your code generation prompt..."></textarea><div class="actions"><button class="btn"onclick="savePrompt()">SAVE</button><button class="btn"onclick="generateCode()">GENERATE</button><button class="btn"onclick="deleteProject()"style="background:#333;color:#fff">DELETE PROJECT</button></div></div><div class="files-section"><h4>FILES</h4><div class="files-list"id="filesList"></div></div></div></div></div></div><div class="modal"id="newProjectModal"><div class="modal-content"><h3>NEW PROJECT</h3><input type="text"id="projectNameInput"placeholder="project-name"/><div class="modal-actions"><button class="btn"style="background:#333;color:#fff"onclick="hideNewProjectModal()">CANCEL</button><button class="btn"onclick="createProject()">CREATE</button></div></div></div><script>let c=null,s=null;async function loadProjects(){const r=await fetch('/api/projects'),p=await r.json(),l=document.getElementById('projectsList');l.innerHTML='';p.forEach(x=>{const i=document.createElement('div');i.className='project-item';if(c===x.name)i.classList.add('active');i.innerHTML=`<div class="project-name">${x.name}</div><div class="project-meta">${x.files.length} files</div>`;i.onclick=()=>selectProject(x.name);l.appendChild(i)})}async function selectProject(n){c=n;document.getElementById('emptyState').style.display='none';document.getElementById('editorContainer').style.display='flex';document.getElementById('projectTitle').textContent=n;const r=await fetch('/api/projects'),p=await r.json(),x=p.find(i=>i.name===n);document.getElementById('promptEditor').value=x.prompt;await loadFiles(n);await loadProjects();startStatusCheck()}async function loadFiles(n){const r=await fetch(`/api/projects/${n}/files`),f=await r.json(),l=document.getElementById('filesList');l.innerHTML='';if(f.length===0){l.innerHTML='<div style="padding:20px;text-align:center;color:#666;font-size:12px">No files yet</div>';return}f.forEach(x=>{const i=document.createElement('div');i.className='file-item';i.innerHTML=`<div class="file-name">${x.path}</div><div class="file-size">${(x.size/1024).toFixed(1)} KB</div>`;i.onclick=()=>downloadFile(c,x.path);l.appendChild(i)})}async function savePrompt(){const p=document.getElementById('promptEditor').value;await fetch(`/api/projects/${c}/prompt`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt:p})})}async function generateCode(){const p=document.getElementById('promptEditor').value;await savePrompt();await fetch(`/api/projects/${c}/generate`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt:p})});updateStatus('RUNNING',true)}async function deleteProject(){if(!confirm(`Delete project "${c}"?`))return;await fetch(`/api/projects/${c}`,{method:'DELETE'});c=null;document.getElementById('emptyState').style.display='flex';document.getElementById('editorContainer').style.display='none';await loadProjects();stopStatusCheck()}function downloadFile(p,f){window.open(`/api/projects/${p}/download/${f}`,'_blank')}function showNewProjectModal(){document.getElementById('newProjectModal').classList.add('active');document.getElementById('projectNameInput').value='';document.getElementById('projectNameInput').focus()}function hideNewProjectModal(){document.getElementById('newProjectModal').classList.remove('active')}async function createProject(){const n=document.getElementById('projectNameInput').value.trim();if(!n)return;await fetch('/api/projects',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:n})});hideNewProjectModal();await loadProjects();selectProject(n)}function updateStatus(t,r){const i=document.getElementById('statusIndicator');i.textContent=t;i.className='status'+(r?' running':'')}function startStatusCheck(){stopStatusCheck();s=setInterval(async()=>{if(!c)return;const r=await fetch(`/api/projects/${c}/status`),x=await r.json();if(x.status==='running'){updateStatus('RUNNING: '+x.progress,true)}else if(x.status==='complete'){updateStatus('COMPLETE',false);await loadFiles(c);stopStatusCheck()}else if(x.status==='error'){updateStatus('ERROR: '+x.progress,false);stopStatusCheck()}else{updateStatus('IDLE',false)}},1000)}function stopStatusCheck(){if(s){clearInterval(s);s=null}}document.getElementById('projectNameInput').addEventListener('keypress',e=>{if(e.key==='Enter')createProject()});loadProjects()</script></body></html>
HTMLEOF
EXPOSE 5000
CMD ["/start.sh"]