Skip to content
Snippets Groups Projects
Commit b96aaf7a authored by MaxEhrlicherSchmidt's avatar MaxEhrlicherSchmidt
Browse files

init

parents
No related branches found
No related tags found
No related merge requests found
.idea
.env
# Setup
1. create a .env file in the /assignment_9_updated directory
2. Add `USERNAME=<zedat username>
PW=<zedat password>` to the .env file
3. run `pip install -r requirements.txt`
4. run `python app.py`
5. open http:/localhost:5000 in the browser
\ No newline at end of file
# README
This folder contains a short tutorial and a sample project for Assignment 9 of the HCI course at the FU Berlin, summer term 2024.
First, please go through the tutorial in the jupyter notebook titled 'tutorial.ipynb'. You can start the notebook by typing `jupyter notebook` in your terminal and selecting the notebook file.
You can then examine the sample project by running `python app.py` in your terminal. The project uses Flask to deploy a simple website that sends requests to an LLM running on the 'moritz.imp.fu-berlin.de' server and displays the responses. Users can also change certain parameters of the LLM using sliders.
To change the design of the website, you can edit the 'index.html' file in the templates folder.
**Note:** If you are missing packages, you can install the required ones by typing `pip install -r requirements.txt` inside a terminal.
\ No newline at end of file
import os
from flask import Flask, render_template, request, jsonify
from flask_cors import CORS # Import CORS
import paramiko
import json
import dotenv
import time
# Enable CORS for all routes
app = Flask(__name__)
CORS(app)
# SSH Configuration
SERVER_1 = 'andorra.imp.fu-berlin.de'
SERVER_2 = 'moritz.imp.fu-berlin.de'
REMOTE_PORT = 22
USERNAME = None
PASSWORD = None
# Function to connect to the first server
def ssh_to_server_1(server1_ip, username1, password1):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(server1_ip, username=username1, password=password1)
return client
# Function to tunnel through the first server and connect to the second server
def ssh_to_server_2_through_server_1(server1_client, server2_ip, username2, password2):
# Create a transport (tunnel) using the connection from server 1
transport = server1_client.get_transport()
# Open a direct TCP connection from server1 to server2
dest_addr = (server2_ip, 22) # destination address (server 2)
local_addr = ('127.0.0.1', 0) # bind address for local end of the tunnel
channel = transport.open_channel("direct-tcpip", dest_addr, local_addr)
# Now establish SSH connection to server2 using the tunnel through server1
server2_client = paramiko.SSHClient()
server2_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Connect to server 2 using the channel (through server 1)
server2_client.connect(server2_ip, username=username2, password=password2, sock=channel)
return server2_client
def execute_remote_command(command):
"""Connects to the remote server over SSH and executes a command."""
try:
# Connect to the first server
server1_client = ssh_to_server_1(SERVER_1, USERNAME, PASSWORD)
print("Connected to server 1")
# Connect to the second server through server 1
server2_client = ssh_to_server_2_through_server_1(server1_client, SERVER_2, USERNAME, PASSWORD)
print("Connected to server 2 through server 1")
# Execute the command on the remote server
stdin, stdout, stderr = server2_client.exec_command(command)
# Retrieve the output and error
output = stdout.read().decode()
error = stderr.read().decode()
server2_client.close()
server1_client.close()
print("returning output\n")
return {"output": output}
except Exception as e:
return {"error": str(e)}
@app.route('/')
def index():
return render_template('index.html')
@app.route('/submit', methods=['POST'])
def submit():
# time.sleep(1)
# return jsonify(""".center-checkbox {
# display: flex;
# justify-content: center;
# align-items: center;
#}""")
data = request.json
prompt = data['prompt']
return get_llm_response(prompt)
@app.route('/explain', methods=['POST'])
def explain():
# time.sleep(1)
# return jsonify(""".center-checkbox {
# display: flex; // uses display flex style
# justify-content: center; // align the items in the box horizontally centered
# align-items: center; // align the items in the box vertically centered
#}""")
data = request.json
code = data['code']
system_prompt = "You are a code snippet generator. You will get a code snippet. Take that code snippet, do not change it. Only add comments in the code to explain it to a beginner. No explanations or messages outside of the code. No quotes around the code. No language specification."
return get_llm_response(code, system_prompt)
def get_llm_response(prompt, system_prompt=None, seed=42, temperature=1.0, top_k=40):
if system_prompt is None:
system_prompt = "You are a code snippet generator. Only generate code. Use best practices. As short as possible. No options. No explanation. No comment. No quotes around the code. No language specification."
payload = {
"model": "llama3.1",
# "prompt": system_user_prompt + user_prompt,
"stream": False,
"options": {
"seed": seed,
"temperature": temperature,
"top_k": top_k
},
"messages": [
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": prompt
}
],
}
# Convert the payload to a JSON string for sending via SSH
payload_json = json.dumps(payload)
# Prepare the curl command to run on the remote server
curl_command = f"curl http://localhost:11434/api/chat -d '{payload_json}'"
# Execute the curl command on the remote server via SSH
result = execute_remote_command(curl_command)
result = json.loads(result['output'])
print(result)
return jsonify(result['message']['content'])
if __name__ == '__main__':
dotenv.load_dotenv()
if not PASSWORD or not USERNAME:
USERNAME = os.getenv('USERNAME')
PASSWORD = os.getenv('PW')
app.run()
bcrypt==4.2.0
blinker==1.8.2
cffi==1.17.1
click==8.1.7
cryptography==43.0.1
Flask==3.0.3
importlib_metadata==8.5.0
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
paramiko==3.5.0
pycparser==2.22
PyNaCl==1.5.0
Werkzeug==3.0.4
zipp==3.20.2
python-dotenv
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CodeSnippet Generator</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css" rel="stylesheet">
<style>
body, html {
height: 100%;
width: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #111111;
color: #e0e0e0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.search-container {
width: 100%;
height: 100%;
max-width: 700px;
display: flex;
flex-direction: column;
padding: 0 20px;
}
.hljs {
background: transparent !important;
}
.chip-container {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
padding-top: 1.5em;
}
.chip {
padding: 8px 12px;
background-color: #333;
color: white;
border-radius: 15px;
cursor: pointer;
font-size: small;
}
.input-container {
display: flex;
align-items: center;
padding: 10px 0;
}
input[type="text"] {
flex: 1;
padding: 10px;
font-size: medium;
background-color: #ffffff;
color: #111111;
border: none;
border-radius: 30px;
outline: none;
}
button {
background: none;
border: none;
color: white;
cursor: pointer;
padding: 0 15px;
font-size: 20px;
}
.results-container {
flex: 1;
margin-top: 15px;
color: white;
}
.result {
margin-bottom: 15px;
padding: 10px;
background-color: #2c2c2c;
border-radius: 10px;
position: relative;
}
.code-block {
background-color: #333;
margin: 0;
padding: 0;
border-radius: 10px;
white-space: pre-wrap;
word-wrap: break-word;
}
.copy-button, .explain-button {
background-color: #595959;
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
margin-right: 10px;
}
.copy-button svg {
fill: white;
width: 16px;
height: 16px;
}
.explain-button svg {
fill: white;
width: 20px;
height: 20px;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
}
.dot {
height: 12px;
width: 12px;
margin: 0 5px;
background-color: #ffffff;
border-radius: 50%;
display: inline-block;
animation: bounce 1.2s infinite ease-in-out;
}
.dot:nth-child(1) {
animation-delay: -0.3s;
}
.dot:nth-child(2) {
animation-delay: -0.15s;
}
@keyframes bounce {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.3);
}
}
.explanation {
margin-top: 10px;
padding: 10px;
background-color: #595959;
border-radius: 10px;
}
.explaining {
background-color: #595959;
}
.explained {
background-color: #7ca17e;
}
</style>
</head>
<body>
<div class="search-container">
<!-- Chip suggestions -->
<div class="chip-container">
<div class="chip" onclick="setInput('center a divbox')">center a divbox</div>
<div class="chip" onclick="setInput('read json file in python')">read json file in python</div>
<div class="chip" onclick="setInput('swap key and value in python')">swap key and value in python</div>
</div>
<!-- Search bar -->
<div class="input-container">
<input type="text" id="prompt" name="prompt" placeholder="Search for Code..." onkeydown="checkSubmit(event)" autofocus>
<button id="submitButton" onclick="submitForm()">&#x1F50D;</button>
</div>
<!-- Results area -->
<div class="results-container" id="resultsContainer"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
let currentResult = '';
let explanationShown = false;
// Pre-fill input field when chip is clicked
function setInput(text) {
document.getElementById('prompt').value = text;
submitForm();
}
// Handle 'Enter' key submission
function checkSubmit(event) {
if (event.key === 'Enter') {
submitForm();
}
}
async function submitForm() {
const resultsContainer = document.getElementById('resultsContainer');
const inputField = document.getElementById('prompt');
const searchQuery = inputField.value;
if (searchQuery.trim() === "") return;
// Clear previous results
resultsContainer.innerHTML = '';
// Show loading animation
const loadingDiv = document.createElement('div');
loadingDiv.classList.add('loading');
loadingDiv.innerHTML = '<span class="dot"></span><span class="dot"></span><span class="dot"></span>';
resultsContainer.appendChild(loadingDiv);
try {
// Create the request payload
const data = {
prompt: searchQuery
};
// Send the request to the Flask server
const response = await fetch('http://localhost:5000/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
const result = await response.json();
currentResult = result; // Store the current result for explanation
explanationShown = false;
// Remove the loading message
loadingDiv.remove();
// Display the bot's response as a result
const resultDiv = document.createElement('div');
resultDiv.classList.add('result');
const codeBlock = document.createElement('pre');
codeBlock.classList.add('code-block');
codeBlock.innerHTML = `<code contenteditable="true">${escapeHtml(result)}</code>`;
const copyButton = document.createElement('button');
copyButton.classList.add('copy-button');
copyButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M16 1h-11c-1.104 0-2 .896-2 2v14h2v-14h11v-2zm5 4h-11c-1.104 0-2 .896-2 2v14c0 1.104.896 2 2 2h11c1.104 0 2-.896 2-2v-14c0-1.104-.896-2-2-2zm0 16h-11v-14h11v14z"/></svg>`;
copyButton.onclick = () => copyCode(codeBlock.textContent, copyButton);
const explainButton = document.createElement('button');
explainButton.classList.add('explain-button');
explainButton.title="Add comments to code"
explainButton.innerHTML = `?`;
explainButton.onclick = () => toggleExplanation(resultDiv, codeBlock, explainButton);
resultDiv.appendChild(codeBlock);
resultDiv.appendChild(copyButton);
resultDiv.appendChild(explainButton);
resultsContainer.appendChild(resultDiv);
// Automatically highlight code with highlight.js
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
});
} else {
loadingDiv.remove();
const errorDiv = document.createElement('div');
errorDiv.classList.add('result');
errorDiv.textContent = `Error ${response.status}: ${await response.text()}`;
resultsContainer.appendChild(errorDiv);
}
} catch (error) {
loadingDiv.remove();
const errorDiv = document.createElement('div');
errorDiv.classList.add('result');
errorDiv.textContent = 'Error: ' + error.message;
resultsContainer.appendChild(errorDiv);
}
}
async function toggleExplanation(resultDiv, codeBlock, explainButton) {
if (!explanationShown) {
explainButton.classList.add('explaining');
explainButton.classList.remove('explained');
explainButton.innerHTML = `<span class="dot"></span><span class="dot"></span><span class="dot"></span>`;
try {
const data = {
code: currentResult
};
const response = await fetch('http://localhost:5000/explain', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
const explanation = await response.json();
codeBlock.innerHTML = `<code contenteditable="true">${escapeHtml(explanation)}</code>`;
explanationShown = true;
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
});
explainButton.classList.remove('explaining');
explainButton.classList.add('explained');
explainButton.innerHTML = `?`;
} else {
const errorDiv = document.createElement('div');
errorDiv.classList.add('explanation');
errorDiv.textContent = `Error ${response.status}: ${await response.text()}`;
resultDiv.appendChild(errorDiv);
}
} catch (error) {
const errorDiv = document.createElement('div');
errorDiv.classList.add('explanation');
errorDiv.textContent = 'Error: ' + error.message;
resultDiv.appendChild(errorDiv);
}
} else {
// Show the original code
codeBlock.innerHTML = `<code contenteditable="true">${escapeHtml(currentResult)}</code>`;
explanationShown = false;
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
});
explainButton.classList.remove('explained');
explainButton.innerHTML = `?`;
}
}
function copyCode(code, button) {
navigator.clipboard.writeText(code).then(() => {
changeIconToCheckmark(button);
}).catch(err => {
console.error('Failed to copy code: ', err);
});
}
function changeIconToCheckmark(button) {
// Change icon to a checkmark
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
<path d="M9 16.172l-3.536-3.536 1.414-1.414 2.122 2.122 5.657-5.657 1.414 1.414-7.071 7.071z"/>
</svg>`;
// Change it back after 3 seconds
setTimeout(() => {
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M16 1h-11c-1.104 0-2 .896-2 2v14h2v-14h11v-2zm5 4h-11c-1.104 0-2 .896-2 2v14c0 1.104.896 2 2 2h11c1.104 0 2-.896 2-2v-14c0-1.104-.896-2-2-2zm0 16h-11v-14h11v14z"/></svg>`;
}, 3000);
}
function escapeHtml(text) {
return text.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
</script>
</body>
</html>
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment