diff options
-rwxr-xr-x | awk/scheme/s.awk | 139 | ||||
-rw-r--r-- | chibi/pi.scm | 17 | ||||
-rw-r--r-- | html/matt-chat/ChicagoFLF.ttf | bin | 0 -> 31256 bytes | |||
-rw-r--r-- | html/matt-chat/cat.png | bin | 0 -> 2573 bytes | |||
-rw-r--r-- | html/matt-chat/com.user.server.plist | 16 | ||||
-rw-r--r-- | html/matt-chat/index.html | 1266 | ||||
-rw-r--r-- | html/matt-chat/pokemon.js | 157 | ||||
-rwxr-xr-x | html/matt-chat/server.sh | 23 |
8 files changed, 1618 insertions, 0 deletions
diff --git a/awk/scheme/s.awk b/awk/scheme/s.awk new file mode 100755 index 0000000..7c8bba6 --- /dev/null +++ b/awk/scheme/s.awk @@ -0,0 +1,139 @@ +#!/usr/bin/awk -f + +# Set debug mode +DEBUG = 1 # Change to 0 to disable debug output + +# Environment to store variable bindings +BEGIN { + print "Welcome to the AWK Scheme Interpreter!" + print "Type your Scheme expressions below (type 'exit' to quit):" + while (1) { + printf "> " + if (getline input <= 0) { + print "Error reading input. Exiting." + break + } + if (input == "exit") { + print "Exiting the interpreter." + exit + } + if (input == "") { + print "Empty input received, continuing..." + continue + } + + print "Input received: " input # Echo the input + ast = parse(input) # Parse the input + + # Print the entire AST for debugging + for (i = 1; i <= length(ast); i++) { + print "AST[" i "] = " ast[i] + } + + # Evaluate the AST + if (length(ast) > 0) { + result = eval(ast) # Evaluate the AST + print "Result: " result # Print the result + } else { + print "Parsed AST is empty." + } + } +} + +# Function to parse input into an AST +function parse(input) { + # Remove outer whitespace + gsub(/^\s+|\s+$/, "", input) + + # Check if input is empty after trimming + if (input == "") { + print "Input is empty after trimming" + return "" + } + + # Debugging: Print input before processing + print "Debug: Raw input for parsing: " input + + # Remove parentheses at start and end + if (substr(input, 1, 1) == "(") { + input = substr(input, 2) + } + if (substr(input, length(input), 1) == ")") { + input = substr(input, 1, length(input) - 1) + } + + # Debugging: Print input after removing outer parentheses + print "Debug: Input after removing outer parentheses: " input + + # Split the input into tokens + gsub(/\(/, " ( ", input) + gsub(/\)/, " ) ", input) + gsub(/\s+/, " ", input) # normalize whitespace + gsub(/^\s+|\s+$/, "", input) # trim + + # Debugging: Print input after tokenization + print "Debug: Input after tokenization: " input + + n = split(input, ast, " ") + + # Debugging: Print the number of tokens + print "Debug: Number of tokens: " n + + return ast +} + +# Function to evaluate the AST +function eval(ast, i, result) { + # Debugging: Print the current AST being evaluated + print "Debug: Evaluating AST: " ast[1] " " ast[2] " " ast[3] + + # Handle numbers directly + if (ast[1] ~ /^[+-]?[0-9]+$/) { + print "Debug: Returning number: " ast[1] + return ast[1] + 0 # Convert string to number + } + + # Handle addition + if (ast[1] == "+") { + result = 0 + for (i = 2; i <= length(ast); i++) { + print "Debug: Adding operand: " ast[i] + result += eval(ast[i]) # Recursively evaluate operands + } + return result + } + + # Handle subtraction + if (ast[1] == "-") { + result = eval(ast[2]) # Start with the first operand + for (i = 3; i <= length(ast); i++) { + print "Debug: Subtracting operand: " ast[i] + result -= eval(ast[i]) # Subtract subsequent operands + } + return result + } + + # Handle multiplication + if (ast[1] == "*") { + result = 1 + for (i = 2; i <= length(ast); i++) { + print "Debug: Multiplying operand: " ast[i] + result *= eval(ast[i]) # Multiply operands + } + return result + } + + # Handle division + if (ast[1] == "/") { + result = eval(ast[2]) # Start with the first operand + for (i = 3; i <= length(ast); i++) { + print "Debug: Dividing by operand: " ast[i] + result /= eval(ast[i]) # Divide by subsequent operands + } + return result + } + + # If we reach here, the operation is not recognized + return "Error: Unknown operation " ast[1] +} + diff --git a/chibi/pi.scm b/chibi/pi.scm new file mode 100644 index 0000000..a32742c --- /dev/null +++ b/chibi/pi.scm @@ -0,0 +1,17 @@ +(define (greory-leibniz-terms n) + (cond ((= n 0) '()) + ((even? n) (cons 1/g (greory-leibniz-terms (+ (- n 1) /2)))) + (else (cons (/(-1) (* 2 n +3)) (/(*x^2) x)))))) + +(define pi-approximation + (define x '()) + (define f (lambda (y) y)) + + (display "Approximating Pi using Gregory-Leibniz series...\n") + (for-each + lambda (term) + (define n (car term)) + (set! x (+ x (* 4 / n))) + (f (f (g (g (/(*f f 4)) (/(*x^2) x))))))))) )) + +(display pi-approximation)) \ No newline at end of file diff --git a/html/matt-chat/ChicagoFLF.ttf b/html/matt-chat/ChicagoFLF.ttf new file mode 100644 index 0000000..60691e1 --- /dev/null +++ b/html/matt-chat/ChicagoFLF.ttf Binary files differdiff --git a/html/matt-chat/cat.png b/html/matt-chat/cat.png new file mode 100644 index 0000000..7d4c0b9 --- /dev/null +++ b/html/matt-chat/cat.png Binary files differdiff --git a/html/matt-chat/com.user.server.plist b/html/matt-chat/com.user.server.plist new file mode 100644 index 0000000..b5fb9dd --- /dev/null +++ b/html/matt-chat/com.user.server.plist @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> + <string>com.user.server</string> + <key>ProgramArguments</key> + <array> + <string>/Users/eli/Code/institute/tour/html/matt-chat/server.sh</string> + </array> + <key>RunAtLoad</key> + <true/> + <key>KeepAlive</key> + <true/> +</dict> +</plist> \ No newline at end of file diff --git a/html/matt-chat/index.html b/html/matt-chat/index.html new file mode 100644 index 0000000..2bc8119 --- /dev/null +++ b/html/matt-chat/index.html @@ -0,0 +1,1266 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="Chatty chat chat chat. A super simple chat interface for the Ollama API."> + <title>matt chat is not a cat</title> + <meta name="theme-color" content="#007BFF"> + <link rel="icon" href="cat.png" type="image/x-icon"> + <link rel="shortcut icon" href="cat.png" type="image/x-icon"> + <link rel="apple-touch-icon" href="cat.png"> + <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet"> + <style> + body { + font-family: Arial, sans-serif; + font-size: 22px; + margin: 0; + padding: 20px; + background-color: #f7f7f7; + max-width: 800px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + height: 100vh; + overflow: hidden; + } + #chat-container { + background-color: white; + border: 1px solid #ccc; + border-radius: 8px; + padding: 1em; + margin: 0 auto; + flex: 1; + overflow-y: auto; + width: 100%; + max-height: 400px; + scroll-behavior: smooth; + } + #user-input { + width: 100%; + padding: 10px; + border-radius: 4px; + border: 1px solid #ddd; + font-size: 16px; + margin-top: 10px; + box-sizing: border-box; + } + #send-button { + padding: 10px 15px; + border-radius: 4px; + background-color: #007BFF; + color: white; + border: none; + cursor: pointer; + margin-top: 10px; + width: 100%; + } + #send-button:hover { + background-color: #0056b3; + } + + .model-select-container { + align-self: flex-start; + width: 100%; + display: flex; + justify-content: space-between; + padding: 1em; + } + + .model-select-container label { + margin-left: 10px; + } + + .message { + white-space: pre-wrap; + margin-bottom: 10px; + padding: 1em; + border-radius: 8px; + background-color: #f1f1f1; + display: block; + max-width: 100%; + } + + .user-message { + background-color: #007BFF; + color: white; + text-align: right; + margin-left: 20px; + } + + .bot-message { + background-color: #f0f0f0; + color: #333; + text-align: left; + margin-right: 20px; + } + + @media (max-width: 600px) { + #chat-container { + max-height: 300px; + } + } + + body.dark-mode { + background-color: #333; + color: #f7f7f7; + } + + #chat-container.dark-mode { + background-color: #444; + border: 1px solid #555; + } + + #user-input.dark-mode { + background-color: #555; + color: #f7f7f7; + border: 1px solid #666; + } + + #send-button.dark-mode { + background-color: #007BFF; + color: white; + } + + .message.dark-mode { + background-color: #555; + color: #f7f7f7; + } + + .user-message.dark-mode { + background-color: #007BFF; + color: white; + } + + .bot-message.dark-mode { + background-color: #666; + color: #f7f7f7; + } + + .bot-time { + margin: 0.5em 0; + font-size: 0.9em; + color: #888; + text-align: center; + } + + /* Professional theme */ + body.theme-professional { + font-family: Arial, sans-serif; + font-size: 22px; + } + + /* Molly Millions theme */ + body.theme-molly-millions { + font-family: "Courier New", monospace; + font-size: 22px; + margin: 0; + padding: 20px; + background-color: #0a0a0a; + color: #00ff00; + max-width: 800px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + height: 100vh; + overflow: hidden; + } + + .theme-molly-millions #chat-container { + background-color: #000000; + border: 2px solid #00ff00; + border-radius: 0; + padding: 1em; + margin: 0 auto; + flex: 1; + overflow-y: auto; + width: 100%; + max-height: 400px; + scroll-behavior: smooth; + box-shadow: 0 0 10px #00ff00; + } + + .theme-molly-millions #user-input { + width: 100%; + padding: 10px; + border-radius: 0; + border: 2px solid #00ff00; + background-color: #000000; + color: #00ff00; + font-family: "Courier New", monospace; + font-size: 16px; + margin-top: 10px; + box-sizing: border-box; + } + + .theme-molly-millions #send-button { + padding: 10px 15px; + border-radius: 0; + background-color: #000000; + color: #00ff00; + border: 2px solid #00ff00; + cursor: pointer; + margin-top: 10px; + width: 100%; + font-family: "Courier New", monospace; + text-transform: uppercase; + } + + .theme-molly-millions #send-button:hover { + background-color: #00ff00; + color: #000000; + } + + .theme-molly-millions .message { + white-space: pre-wrap; + margin-bottom: 10px; + padding: 1em; + border-radius: 0; + border: 1px solid #00ff00; + background-color: #0a0a0a; + display: block; + max-width: 100%; + } + + .theme-molly-millions .user-message { + background-color: #001100; + color: #00ff00; + border: 1px solid #00ff00; + text-align: right; + margin-left: 20px; + } + + .theme-molly-millions .bot-message { + background-color: #000000; + color: #00ff00; + border: 1px solid #00ff00; + text-align: left; + margin-right: 20px; + } + + .theme-molly-millions .bot-time { + color: #005500; + } + + /* Cloud theme */ + body.theme-cloud { + font-family: "Press Start 2P", "Courier New", monospace; + font-size: 18px; + margin: 0; + padding: 20px; + background: linear-gradient(135deg, #1a1b4b 0%, #162057 50%, #1a1b4b 100%); + color: #ffffff; + max-width: 800px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + height: 100vh; + overflow: hidden; + } + + .theme-cloud #chat-container { + background: rgba(0, 0, 32, 0.75); + border: 3px solid #4080ff; + border-radius: 3px; + padding: 1em; + margin: 0 auto; + flex: 1; + overflow-y: auto; + width: 100%; + max-height: 400px; + scroll-behavior: smooth; + box-shadow: 0 0 15px rgba(64, 128, 255, 0.3); + } + + .theme-cloud #user-input { + width: 100%; + padding: 10px; + border: 2px solid #4080ff; + background: rgba(0, 0, 32, 0.75); + color: #ffffff; + font-family: "Press Start 2P", "Courier New", monospace; + font-size: 14px; + margin-top: 10px; + box-sizing: border-box; + } + + .theme-cloud #send-button { + padding: 10px 15px; + background: linear-gradient(to bottom, #4080ff 0%, #2048c0 100%); + color: white; + border: 2px solid #2048c0; + cursor: pointer; + margin-top: 10px; + width: 100%; + font-family: "Press Start 2P", "Courier New", monospace; + font-size: 14px; + text-transform: uppercase; + text-shadow: 2px 2px #000000; + } + + .theme-cloud #send-button:hover { + background: linear-gradient(to bottom, #50a0ff 0%, #3060e0 100%); + } + + .theme-cloud .message { + white-space: pre-wrap; + margin-bottom: 10px; + padding: 1em; + border: 2px solid #4080ff; + background: rgba(0, 0, 32, 0.5); + display: block; + max-width: 100%; + font-size: 14px; + } + + .theme-cloud .user-message { + background: rgba(64, 128, 255, 0.2); + color: #ffffff; + border: 2px solid #4080ff; + text-align: right; + margin-left: 20px; + text-shadow: 1px 1px #000000; + } + + .theme-cloud .bot-message { + background: rgba(32, 64, 128, 0.2); + color: #ffffff; + border: 2px solid #4080ff; + text-align: left; + margin-right: 20px; + text-shadow: 1px 1px #000000; + } + + .theme-cloud .bot-time { + color: #80c0ff; + font-size: 12px; + text-shadow: 1px 1px #000000; + } + + .theme-cloud #counter { + color: #80c0ff !important; + text-shadow: 1px 1px #000000; + } + + .theme-cloud .model-select-container { + background: rgba(0, 0, 32, 0.75); + border: 2px solid #4080ff; + padding: 10px; + margin-bottom: 10px; + width: 100%; + box-sizing: border-box; + } + + .theme-cloud #model-select { + background: rgba(0, 0, 32, 0.75); + color: #ffffff; + border: 1px solid #4080ff; + padding: 5px; + font-family: "Press Start 2P", "Courier New", monospace; + font-size: 12px; + } + + /* Classic Mac theme */ + @font-face { + font-family: 'ChicagoFLF'; + src: url('/ChicagoFLF.ttf') format('truetype'); + } + + body.theme-classic { + font-family: 'ChicagoFLF', 'Monaco', monospace; + font-size: 14px; + margin: 0; + padding: 20px; + background-color: #DDDDDD; + color: #000000; + max-width: 800px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + height: 100vh; + overflow: hidden; + image-rendering: pixelated; + } + + .theme-classic #chat-container { + background-color: #FFFFFF; + border: 2px solid #000000; + border-radius: 2px; + padding: 1em; + margin: 0 auto; + flex: 1; + overflow-y: auto; + width: 100%; + max-height: 400px; + scroll-behavior: smooth; + box-shadow: 2px 2px 0px #000000; + } + + .theme-classic #user-input { + width: 100%; + padding: 8px; + border: 2px solid #000000; + background-color: #FFFFFF; + color: #000000; + font-family: 'ChicagoFLF', 'Monaco', monospace; + font-size: 14px; + margin-top: 10px; + box-sizing: border-box; + border-radius: 2px; + } + + .theme-classic #send-button { + padding: 4px 15px; + background-color: #FFFFFF; + color: #000000; + border: 2px solid #000000; + border-radius: 2px; + cursor: pointer; + margin-top: 10px; + width: 100%; + font-family: 'ChicagoFLF', 'Monaco', monospace; + font-size: 14px; + box-shadow: 2px 2px 0px #000000; + } + + .theme-classic #send-button:hover { + background-color: #000000; + color: #FFFFFF; + } + + .theme-classic #send-button:active { + box-shadow: 1px 1px 0px #000000; + transform: translate(1px, 1px); + } + + .theme-classic .message { + white-space: pre-wrap; + margin-bottom: 10px; + padding: 8px; + border: 2px solid #000000; + background-color: #FFFFFF; + display: block; + max-width: 100%; + font-size: 14px; + border-radius: 2px; + } + + .theme-classic .user-message { + background-color: #FFFFFF; + color: #000000; + text-align: right; + margin-left: 20px; + box-shadow: 2px 2px 0px #000000; + } + + .theme-classic .bot-message { + background-color: #FFFFFF; + color: #000000; + text-align: left; + margin-right: 20px; + box-shadow: 2px 2px 0px #000000; + } + + .theme-classic .bot-time { + color: #666666; + font-size: 12px; + text-align: center; + margin: 4px 0; + } + + .theme-classic #counter { + color: #000000 !important; + } + + .theme-classic .model-select-container { + background-color: #FFFFFF; + border: 2px solid #000000; + padding: 8px; + margin-bottom: 10px; + width: 100%; + box-sizing: border-box; + border-radius: 2px; + box-shadow: 2px 2px 0px #000000; + } + + .theme-classic #model-select { + background-color: #FFFFFF; + color: #000000; + border: 2px solid #000000; + padding: 2px; + font-family: 'ChicagoFLF', 'Monaco', monospace; + font-size: 14px; + border-radius: 2px; + } + + .theme-classic input[type="checkbox"] { + appearance: none; + -webkit-appearance: none; + width: 16px; + height: 16px; + border: 2px solid #000000; + background-color: #FFFFFF; + position: relative; + vertical-align: middle; + margin-right: 5px; + } + + .theme-classic input[type="checkbox"]:checked::after { + content: '✓'; + position: absolute; + left: 1px; + top: -2px; + font-size: 14px; + } + + /* LCARS Theme */ + body.theme-lcars { + font-family: "Helvetica Neue", Arial, sans-serif; + font-size: 18px; + margin: 0; + padding: 20px; + background-color: #000; + color: #FF9966; + max-width: 800px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + height: 100vh; + overflow: hidden; + } + + .theme-lcars #chat-container { + background-color: #000; + border: none; + border-radius: 0; + padding: 1em; + margin: 0 auto; + flex: 1; + overflow-y: auto; + width: 100%; + max-height: 400px; + scroll-behavior: smooth; + position: relative; + } + + .theme-lcars #chat-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2em; + background: #CC6699; + border-radius: 20px 20px 0 0; + } + + .theme-lcars #user-input { + width: 100%; + padding: 10px; + border: none; + background-color: #000; + color: #FF9966; + font-family: "Helvetica Neue", Arial, sans-serif; + font-size: 16px; + margin-top: 10px; + box-sizing: border-box; + border-left: 2em solid #CC6699; + } + + .theme-lcars #send-button { + padding: 10px 15px; + background-color: #CC6699; + color: #000; + border: none; + cursor: pointer; + margin-top: 10px; + width: 100%; + font-family: "Helvetica Neue", Arial, sans-serif; + font-weight: bold; + font-size: 16px; + text-transform: uppercase; + border-radius: 0 0 20px 20px; + } + + .theme-lcars #send-button:hover { + background-color: #FF9966; + } + + .theme-lcars .message { + white-space: pre-wrap; + margin-bottom: 10px; + padding: 1em; + border: none; + display: block; + max-width: 100%; + position: relative; + } + + .theme-lcars .user-message { + background-color: #000; + color: #FF9966; + text-align: right; + margin-left: 20px; + border-right: 1em solid #CC6699; + } + + .theme-lcars .bot-message { + background-color: #000; + color: #99CCFF; + text-align: left; + margin-right: 20px; + border-left: 1em solid #9999CC; + } + + .theme-lcars .bot-time { + color: #CC6699; + font-size: 0.8em; + text-align: center; + margin: 4px 0; + } + + .theme-lcars #counter { + color: #99CCFF !important; + } + + .theme-lcars .model-select-container { + background-color: #000; + border: none; + padding: 10px; + margin-bottom: 10px; + width: 100%; + box-sizing: border-box; + display: flex; + align-items: center; + border-radius: 20px; + position: relative; + overflow: hidden; + } + + .theme-lcars .model-select-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 2em; + background: #9999CC; + border-radius: 20px 0 0 20px; + } + + .theme-lcars #model-select { + background-color: #000; + color: #FF9966; + border: none; + padding: 5px; + margin-left: 3em; + font-family: "Helvetica Neue", Arial, sans-serif; + font-size: 16px; + } + + .theme-lcars input[type="checkbox"] { + appearance: none; + -webkit-appearance: none; + width: 16px; + height: 16px; + border: 2px solid #CC6699; + background-color: #000; + position: relative; + vertical-align: middle; + margin-right: 5px; + } + + .theme-lcars input[type="checkbox"]:checked { + background-color: #CC6699; + } + + .theme-lcars input[type="checkbox"]:checked::after { + content: '✓'; + position: absolute; + left: 2px; + top: -2px; + color: #000; + font-size: 14px; + } + </style> +</head> +<body> + + <div class="model-select-container"> + <select id="model-select"></select> + <label> + <input type="checkbox" id="retain-history" /> Build Context As You Chat? + </label> + </div> + + <div id="chat-container"> + <!-- Messages will appear here --> + </div> + + <!-- New container for user input and send button --> + <div id="input-container" style="width: 100%; display: flex; flex-direction: column; margin-top: 10px;"> + <div id="counter" style="text-align: left; font-size: 0.9em; color: #555;"> + Characters: <span id="char-count">0</span> | Words: <span id="word-count">0</span> + </div> + <textarea id="user-input" placeholder="Type your message..."></textarea> + <button id="send-button">Send</button> + </div> + + <script> + // ================================================== + // MATT CHAT IS NOT A CAT + // This is a simple chat interface for the Ollama API + // ================================================== + // + // This configuration object is used to define all local variables for your needs + // Set the base url for the ollama api, and then list all the models you want to use + // The context window size is the number of previous exchanges to keep... + // though this is relatively naive at the moment + + const config = {} + + const localConfig = { + apiUrl: "http://localhost:11434/v1", + completionsEndpoint: "http://localhost:11434/v1/chat/completions", + modelsEndpoint: "http://localhost:11434/v1/models", + contextWindowSize: 6, + systemMessage: "You are a helpful assistant. If you don't know something you'll let me know. Your name is Matt.", + maxTokens: 4096, + summarizeThreshold: 3584, + }; + + const mattConfig = { + apiUrl: "http://100.108.91.106:11434/v1", + completionsEndpoint: "http://100.108.91.106:11434/v1/chat/completions", + modelsEndpoint: "http://100.108.91.106:11434/v1/models", + contextWindowSize: 6, + systemMessage: "You are a helpful assistant. If you don't know something you'll let me know. Your name is Matt.", + maxTokens: 4096, + summarizeThreshold: 3584, + } + + let conversationHistory = { + summary: null, + current: [], + full: [] + }; + + let isCatMode = false; // Flag to track cat mode + + const API_MODELS_ENDPOINT = config.modelsEndpoint; + + // Add this near the top with other constants + const AVAILABLE_THEMES = { + 'professional': 'Professional -- boring, like wearing a tie', + 'molly-millions': 'Molly Millions\' manicure', + 'cloud': 'Cloud -- it took a lot of self control not to add sound effects', + 'classic': 'Classic -- this is not a fish', + 'lcars': 'LCARS -- boldly going' + }; + + function handleError(message) { + console.error(message); + addMessage(message, "bot"); + } + + function showLoadingMessage() { + return addMessage("Loading models...", "bot"); + } + + async function populateModelSelect() { + const modelSelect = document.getElementById("model-select"); + modelSelect.innerHTML = ""; // Clear existing options + + const loadingMessage = showLoadingMessage(); + const modelIds = []; + + try { + const response = await fetch(config.modelsEndpoint); + if (!response.ok) throw new Error('Failed to fetch models'); + + const data = await response.json(); + console.log("API Response:", data); + + if (Array.isArray(data.data)) { + data.data.forEach(model => { + const option = document.createElement("option"); + option.value = model.id; + option.textContent = model.id; + modelSelect.appendChild(option); + modelIds.push(model.id); + }); + console.log("Model IDs:", modelIds); + } else { + handleError("Expected an array of models, but got: " + JSON.stringify(data)); + } + } catch (error) { + handleError("Error fetching models: " + error.message); + } finally { + loadingMessage.remove(); + if (modelIds.length > 0) { + addMessage(`Models loaded successfully! Ready to chat.\n\nAvailable models: ${modelIds.join(', ')}`, "bot"); + } else { + addMessage("No models available to chat.", "bot"); + } + } + } + + document.addEventListener("DOMContentLoaded", () => { + populateModelSelect(); + const modelSelect = document.getElementById("model-select"); + const savedModel = localStorage.getItem("selectedModel"); + if (savedModel) { + modelSelect.value = savedModel; + } + modelSelect.addEventListener("change", () => { + localStorage.setItem("selectedModel", modelSelect.value); + }); + const savedTheme = localStorage.getItem('selectedTheme') || 'professional'; + switchTheme(savedTheme); + }); + + function addMessage(message, sender = "user") { + const chatContainer = document.getElementById("chat-container"); + const messageElement = document.createElement("div"); + messageElement.classList.add("message", sender === "user" ? "user-message" : "bot-message"); + messageElement.textContent = message; + chatContainer.appendChild(messageElement); + messageElement.scrollIntoView({ behavior: "smooth", block: "end" }); + chatContainer.scrollTop = chatContainer.scrollHeight; // Make sure the chat is scrolled to the bottom + return messageElement; // Return the message element so it is easier to use + } + + // Fancy format milliseconds into a more readable format + function formatDuration(duration) { + const minutes = Math.floor(duration / (1000 * 60)); + const seconds = Math.floor((duration % (1000 * 60)) / 1000); + const milliseconds = duration % 1000; + + if (minutes > 0) { + return `${minutes}m ${seconds}.${Math.floor(milliseconds / 10)}s`; + } + return `${seconds}.${Math.floor(milliseconds / 10)}s`; + } + + // Character and word counter + function updateCounter() { + const userInput = document.getElementById("user-input"); + const charCount = document.getElementById("char-count"); + const wordCount = document.getElementById("word-count"); + + const text = userInput.value; + const characters = text.length; + const words = text.trim() ? text.trim().split(/\s+/).length : 0; // Count words + + charCount.textContent = characters; + wordCount.textContent = words; + } + + // Event listener to update the counter on input + document.getElementById("user-input").addEventListener("input", updateCounter); + + function toggleCatMode() { + isCatMode = !isCatMode; // Toggle the flag + if (isCatMode) { + config.systemMessage += " You are a cat."; // Append the phrase + } else { + config.systemMessage = config.systemMessage.replace(" You are a large, fluffy cat. You are a little aloof, but kind.", ""); // Remove the phrase + } + addMessage(`Cat mode is now ${isCatMode ? "enabled" : "disabled"}.`, "bot"); // Inform the user + } + + async function sendMessage() { + const userInput = document.getElementById("user-input"); + const userMessage = userInput.value.trim(); + + if (!userMessage) return; + + // Check for slash commands + if (userMessage.toLowerCase() === '/dark' || userMessage.toLowerCase() === '/darkmode') { + toggleDarkMode(); + userInput.value = ""; // Clear input after command + updateCounter(); // Reset counters + return; + } + + if (userMessage.toLowerCase() === '/clear') { + clearChat(); + userInput.value = ""; // Clear input after command + updateCounter(); // Reset counters + return; + } + + if (userMessage.toLowerCase() === '/help') { + displayHelp(); + userInput.value = ""; // Clear input after command + updateCounter(); // Reset counters + return; + } + + if (userMessage.toLowerCase() === '/cat' || userMessage.toLowerCase() === '/catmode') { + toggleCatMode(); // Toggle cat mode + userInput.value = ""; // Clear input after command + updateCounter(); // Reset counters + return; + } + + if (userMessage.toLowerCase() === '/context') { + const context = viewCurrentContext(); + addMessage(`Current conversation has ${context.currentMessages} messages\nEstimated tokens: ${context.estimatedTokens}`, "bot"); + return; + } + + if (userMessage.toLowerCase().startsWith('/theme')) { + const requestedTheme = userMessage.toLowerCase().split(' ')[1]; + if (!requestedTheme) { + // If no theme is specified, lets show all available themes + addMessage(`Available themes: ${Object.keys(AVAILABLE_THEMES).join(', ')}`, "bot"); + } else if (AVAILABLE_THEMES[requestedTheme]) { + switchTheme(requestedTheme); + } else { + addMessage(`Unknown theme. Available themes: ${Object.keys(AVAILABLE_THEMES).join(', ')}`, "bot"); + } + userInput.value = ""; + updateCounter(); + return; + } + + if (userMessage.toLowerCase() === '/matt') { + Object.assign(config, mattConfig); + addMessage("Switched to Matt's config", "bot"); + userInput.value = ""; + updateCounter(); + populateModelSelect(); // Refresh the model list for the new endpoint + return; + } + + if (userMessage.toLowerCase() === '/local') { + Object.assign(config, localConfig); + addMessage("Switched to local config", "bot"); + userInput.value = ""; + updateCounter(); + populateModelSelect(); // Refresh the model list for the new endpoint + return; + } + + addMessage(userMessage, "user"); + userInput.value = ""; // Clear input after sending the message + + // Reset the counter + document.getElementById("char-count").textContent = "0"; + document.getElementById("word-count").textContent = "0"; + + // Create and add loading indicator + const loadingIndicator = document.createElement("div"); + loadingIndicator.id = "loading-indicator"; + loadingIndicator.classList.add("message", "bot-message"); + loadingIndicator.textContent = "..."; + document.getElementById("chat-container").appendChild(loadingIndicator); + scrollToBottom(); + + // Start animation for this specific indicator + const animationInterval = animateLoadingIndicator(loadingIndicator); + + const startTime = Date.now(); // Capture the start time + + try { + const modelSelect = document.getElementById("model-select"); + const selectedModel = modelSelect.value; + const retainHistory = document.getElementById("retain-history").checked; // Check the checkbox state + + // Prepare the messages for the API + const messagesToSend = await prepareMessages(userMessage); + + const response = await fetch(config.completionsEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: selectedModel, + messages: messagesToSend, + }), + }); + + if (!response.ok) { + throw new Error('Error communicating with Ollama API'); + } + + const data = await response.json(); + console.log("API Response:", data); + + if (data.choices && data.choices.length > 0) { + const botResponse = data.choices[0].message.content; + + // Clear loading indicator + clearInterval(animationInterval); + loadingIndicator.remove(); + + // Add bot's response to chat and history + addMessage(botResponse, "bot"); + conversationHistory.current.push({ role: "assistant", content: botResponse }); + + // Calculate and display duration + const duration = Date.now() - startTime; + const timeTakenMessage = formatDuration(duration); + const timeDisplay = document.createElement("div"); + timeDisplay.classList.add("bot-time"); + timeDisplay.textContent = `Response time: ${timeTakenMessage}`; + document.getElementById("chat-container").appendChild(timeDisplay); + scrollToBottom(); + + } else { + console.error("No response from API"); + loadingIndicator.remove(); + addMessage("Sorry, I didn't get a response from the assistant.", "bot"); + } + + if (conversationHistory.current.length > 10) { + conversationHistory.current.shift(); // Remove the oldest message + } + + } catch (error) { + console.error("Error:", error); + clearInterval(animationInterval); + loadingIndicator.remove(); + addMessage("Sorry, there was an error processing your request.", "bot"); + } + } + + function animateLoadingIndicator(indicator) { + let dots = 0; + return setInterval(() => { + dots = (dots + 1) % 6; + if (indicator && document.contains(indicator)) { + indicator.textContent = '.'.repeat(dots || 1); + } + }, 500); + } + + document.getElementById("send-button").addEventListener("click", sendMessage); + + document.getElementById("user-input").addEventListener("keypress", function (e) { + if (e.key === "Enter") { + e.preventDefault(); // Prevent line break + sendMessage(); + } + }); + + function toggleDarkMode() { + const body = document.body; + const chatContainer = document.getElementById("chat-container"); + const userInput = document.getElementById("user-input"); + const sendButton = document.getElementById("send-button"); + + body.classList.toggle("dark-mode"); + chatContainer.classList.toggle("dark-mode"); + userInput.classList.toggle("dark-mode"); + sendButton.classList.toggle("dark-mode"); + + // Update message classes + const messages = document.querySelectorAll(".message"); + messages.forEach(message => { + message.classList.toggle("dark-mode"); + }); + + // Save preference to local storage + const isDarkMode = body.classList.contains("dark-mode"); + localStorage.setItem("darkMode", isDarkMode); + } + + // Load dark mode preference from local storage on page load + document.addEventListener("DOMContentLoaded", () => { + const darkModePreference = localStorage.getItem("darkMode"); + if (darkModePreference === "true") { + toggleDarkMode(); // Activate dark mode if preference is set + } + }); + + function clearChat() { + const chatContainer = document.getElementById("chat-container"); + chatContainer.innerHTML = ""; + conversationHistory = { + summary: null, + current: [], + full: [] + }; + } + + function displayHelp() { + const helpMessage = ` +Available commands:\n + /dark - Toggle dark mode when using the professional theme + /cat - Toggle cat mode + /context - Show the current conversation's context + /clear - Clear the chat history + /help - Show this message + /theme [theme-name] - Switch theme (available themes: ${Object.keys(AVAILABLE_THEMES).join(', ')}) + without a theme name, this will show all available themes, too + /local - Switch to local Ollama instance + /matt - Switch to Matt's Ollama instance + `; + addMessage(helpMessage, "bot"); + } + + function estimateTokens(text) { + // Rough estimation: ~4 chars per token for English text + return Math.ceil(text.length / 4); + } + + function getContextSize(messages) { + return messages.reduce((sum, msg) => sum + estimateTokens(msg.content), 0); + } + + async function summarizeConversation(messages) { + try { + const modelSelect = document.getElementById("model-select"); + const selectedModel = modelSelect.value; + + const response = await fetch(config.completionsEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: selectedModel, + messages: messages, + }), + }); + + const data = await response.json(); + return data.choices[0].message.content; + } catch (error) { + console.error("Error summarizing conversation:", error); + return null; + } + } + + async function prepareMessages(userMessage) { + const messages = []; + + // Always start with system message + messages.push({ role: "system", content: config.systemMessage }); + + if (document.getElementById("retain-history").checked) { + // If we have a summary, add it more naturally + if (conversationHistory.summary) { + messages.push({ + role: "system", + content: `Previous discussion: ${conversationHistory.summary}` + }); + } + + // Add current conversation segment + messages.push(...conversationHistory.current); + } + + // Add the new message to history before we check for summarization + const newMessage = { role: "user", content: userMessage }; + conversationHistory.current.push(newMessage); + messages.push(newMessage); + + // Do we need to summarize? + const totalTokens = getContextSize(messages); + if (totalTokens > config.summarizeThreshold) { + // Move current messages to full history, except for the newest message + conversationHistory.full.push(...conversationHistory.current.slice(0, -1)); + + // Supposedly this is a more natural summarization prompt... + const summary = await summarizeConversation([ + { + role: "system", + content: "Summarize this conversation's key points and context that would be important for continuing the discussion naturally. Be concise but maintain essential details." + }, + ...conversationHistory.full + ]); + + if (summary) { + conversationHistory.summary = summary; + // Keep only the most recent messages for immediate context + conversationHistory.current = conversationHistory.current.slice(-4); + + // Rebuild messages array with new summary + return [ + { role: "system", content: config.systemMessage }, + { role: "system", content: `Previous discussion: ${summary}` }, + ...conversationHistory.current + ]; + } + } + + return messages; + } + + // Clean up old messages periodically + function pruneConversationHistory() { + if (conversationHistory.full.length > 100) { + // Keep only the last 100 messages in full history + conversationHistory.full = conversationHistory.full.slice(-100); + } + } + + // Call this after successful responses + setInterval(pruneConversationHistory, 60000); // Clean up every minute + + function viewCurrentContext() { + const context = { + summary: conversationHistory.summary, + currentMessages: conversationHistory.current.length, + fullHistoryMessages: conversationHistory.full.length, + estimatedTokens: getContextSize(conversationHistory.current) + }; + console.log("Current Context:", context); + return context; + } + + function scrollToBottom() { + const chatContainer = document.getElementById("chat-container"); + chatContainer.scrollTop = chatContainer.scrollHeight; + } + + function switchTheme(themeName) { + // Remove all theme classes + Object.keys(AVAILABLE_THEMES).forEach(theme => { + document.body.classList.remove(`theme-${theme}`); + }); + + // Add the new theme class + document.body.classList.add(`theme-${themeName}`); + + // Update meta theme-color + const metaThemeColor = document.querySelector('meta[name="theme-color"]'); + if (metaThemeColor) { + switch(themeName) { + case 'molly-millions': + metaThemeColor.setAttribute('content', '#00ff00'); + break; + case 'cloud': + metaThemeColor.setAttribute('content', '#4080ff'); + break; + case 'classic': + metaThemeColor.setAttribute('content', '#DDDDDD'); + break; + case 'lcars': + metaThemeColor.setAttribute('content', '#CC6699'); + break; + case 'professional': + default: + metaThemeColor.setAttribute('content', '#007BFF'); + break; + } + } + + localStorage.setItem('selectedTheme', themeName); + addMessage(`Theme switched to: ${AVAILABLE_THEMES[themeName]}`, "bot"); + } + + // Initialize with localConfig + Object.assign(config, localConfig); + </script> +</body> +</html> diff --git a/html/matt-chat/pokemon.js b/html/matt-chat/pokemon.js new file mode 100644 index 0000000..e707e7b --- /dev/null +++ b/html/matt-chat/pokemon.js @@ -0,0 +1,157 @@ +// Pokemon API functionality using functional programming approach + +// Base URL for the PokeAPI +const POKE_API_BASE = 'https://pokeapi.co/api/v2'; + +// Utility function to fetch data from the API +const fetchPokeData = async (endpoint) => { + try { + const response = await fetch(`${POKE_API_BASE}${endpoint}`); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + return await response.json(); + } catch (error) { + console.error('Error fetching Pokemon data:', error); + throw error; + } +}; + +// Function to get Pokemon basic info +const getPokemonInfo = async (pokemonName) => { + try { + const data = await fetchPokeData(`/pokemon/${pokemonName.toLowerCase()}`); + return { + name: data.name, + id: data.id, + types: data.types.map(type => type.type.name), + abilities: data.abilities.map(ability => ({ + name: ability.ability.name, + isHidden: ability.is_hidden + })), + stats: data.stats.map(stat => ({ + name: stat.stat.name, + value: stat.base_stat + })), + height: data.height / 10, // Convert to meters + weight: data.weight / 10, // Convert to kilograms + sprite: data.sprites.front_default + }; + } catch (error) { + throw new Error(`Could not find Pokemon: ${pokemonName}`); + } +}; + +// Function to get ability details +const getAbilityInfo = async (abilityName) => { + try { + const data = await fetchPokeData(`/ability/${abilityName.toLowerCase()}`); + return { + name: data.name, + effect: data.effect_entries.find(e => e.language.name === 'en')?.effect || 'No effect description available.', + pokemon: data.pokemon.map(p => p.pokemon.name) + }; + } catch (error) { + throw new Error(`Could not find ability: ${abilityName}`); + } +}; + +// Function to get move details +const getMoveInfo = async (moveName) => { + try { + const data = await fetchPokeData(`/move/${moveName.toLowerCase()}`); + return { + name: data.name, + type: data.type.name, + power: data.power, + accuracy: data.accuracy, + pp: data.pp, + effect: data.effect_entries.find(e => e.language.name === 'en')?.effect || 'No effect description available.' + }; + } catch (error) { + throw new Error(`Could not find move: ${moveName}`); + } +}; + +const getEvolutionInfo = async (pokemonName) => { + const data = await fetchPokeData(`/pokemon-species/${pokemonName.toLowerCase()}`); + return data.evolution_chain; +}; + +// Function to format Pokemon info into a readable message +const formatPokemonInfo = (info) => { + const spriteImage = info.sprite ? `<img src="${info.sprite}" alt="${info.name} sprite" style="width: 100px; height: auto;" />` : ''; + return ` +🔍 Pokemon: ${info.name.toUpperCase()} (#${info.id}) +📊 Types: ${info.types.join(', ')} +💪 Abilities: ${info.abilities.map(a => `${a.name}${a.isHidden ? ' (Hidden)' : ''}`).join(', ')} +📈 Stats: +${info.stats.map(s => ` ${s.name}: ${s.value}`).join('\n')} +📏 Height: ${info.height}m +⚖️ Weight: ${info.weight}kg +${spriteImage} + `.trim(); +}; + +// Function to format ability info into a readable message +const formatAbilityInfo = (info) => { + return ` +🔰 Ability: ${info.name.toUpperCase()} +📝 Effect: ${info.effect} +✨ Pokemon with this ability: ${info.pokemon.join(', ')} + `.trim(); +}; + +// Function to format move info into a readable message +const formatMoveInfo = (info) => { + return ` +⚔️ Move: ${info.name.toUpperCase()} +🎯 Type: ${info.type} +💥 Power: ${info.power || 'N/A'} +🎲 Accuracy: ${info.accuracy || 'N/A'} +🔄 PP: ${info.pp} +📝 Effect: ${info.effect} + `.trim(); +}; + +const formatEvolutionInfo = (info) => { + return ` +🔗 Evolution Chain: ${info.name.toUpperCase()} + `.trim(); +}; + +// Main handler for Pokemon commands +const handlePokemonCommand = async (args) => { + if (!args.length) { + return "Usage: /pokemon [pokemon|ability|move] [name]"; + } + + const [type, ...nameArgs] = args; + const name = nameArgs.join(' ').replace(/\s+/g, '-'); // Replace spaces with hyphens + + if (!name) { + return "Please provide a name to search for."; + } + + try { + switch (type.toLowerCase()) { + case 'pokemon': + const pokemonInfo = await getPokemonInfo(name); + return formatPokemonInfo(pokemonInfo); + case 'ability': + const abilityInfo = await getAbilityInfo(name); + return formatAbilityInfo(abilityInfo); + case 'move': + const moveInfo = await getMoveInfo(name); + return formatMoveInfo(moveInfo); + case 'evolution-chain': + const evolutionInfo = await getEvolutionInfo(name); + return formatEvolutionInfo(evolutionInfo); + default: + return "Invalid type. Use: pokemon, ability, or move."; + } + } catch (error) { + return `Error: ${error.message}`; + } +}; + +// Export the handler for use in main application +export { handlePokemonCommand }; \ No newline at end of file diff --git a/html/matt-chat/server.sh b/html/matt-chat/server.sh new file mode 100755 index 0000000..b294acd --- /dev/null +++ b/html/matt-chat/server.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# check that the ollama server is running, if it isn't, start it in the background and continue with the script +if ! pgrep -f ollama; then + ollama start & +fi + +# check that port 38478 is free +if lsof -i :38478; then + echo "Port 38478 is already in use. Please choose a different port." + exit 1 +fi + +# Start a simple HTTP server using Python on port 38478 and run it in the background +python3 -m http.server 38478 & + + +# nvim ~/Library/LaunchAgents/com.user.server.plist +# cp com.user.server.plist ~/Library/LaunchAgents/ +# launchctl load ~/Library/LaunchAgents/com.user.server.plist +# launchctl start com.user.server +# launchctl list | grep com.user.server +# launchctl unload ~/Library/LaunchAgents/com.user.server.plist \ No newline at end of file |