From 4d82322545bb4f2b509b51d52f9fa35061468434 Mon Sep 17 00:00:00 2001 From: retoor Date: Sat, 4 Jan 2025 06:00:03 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 10 ++ Makefile | 7 ++ README.md | 4 + cgi-bin/gpt.py | 32 ++++++ cgi-bin/gpt_template.html | 64 ++++++++++++ chat.h | 31 ++++++ http.h | 210 ++++++++++++++++++++++++++++++++++++++ line.h | 53 ++++++++++ main.c | 185 +++++++++++++++++++++++++++++++++ markdown.h | 115 +++++++++++++++++++++ messages.h | 32 ++++++ openai.h | 84 +++++++++++++++ plugin.h | 43 ++++++++ 13 files changed, 870 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100755 cgi-bin/gpt.py create mode 100644 cgi-bin/gpt_template.html create mode 100644 chat.h create mode 100644 http.h create mode 100644 line.h create mode 100644 main.c create mode 100644 markdown.h create mode 100644 messages.h create mode 100644 openai.h create mode 100644 plugin.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a9a1fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.vscode +.venv +.history +.backup* +auth.h +context.txt +gpt +gpt.c +r +.docs \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9741842 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +all: build run + +build: + gcc main.c -lssl -lcrypto -ljson-c -Ofast -o r -Werror -Wall -lpython3.14 -I/usr/include/python3.14 -lreadline -lncurses + +run: + ./r diff --git a/README.md b/README.md new file mode 100644 index 0000000..e470198 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# R + +## Project description +R \ No newline at end of file diff --git a/cgi-bin/gpt.py b/cgi-bin/gpt.py new file mode 100755 index 0000000..51d3905 --- /dev/null +++ b/cgi-bin/gpt.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# Not written by retoor! This is generated boiler plate to give an example! + +import cgi +import cgitb +from xmlrpc.client import ServerProxy +client = ServerProxy("https://api.molodetz.nl/rpc") +ask_gpt = client.gpt4o_mini + +cgitb.enable() + +print("Content-Type: text/html") +print() + +import pathlib + + +form = cgi.FieldStorage() +question = form.getvalue("question", "") + +page_source = pathlib.Path(__file__).parent.joinpath("gpt_template.html").read_text() + +if question: + try: + response = ask_gpt(question) + except Exception as e: + response = f"Error: {e}" + page_source = page_source.replace("...", response) + page_source = page_source.replace("display:none;","") + +print(page_source) diff --git a/cgi-bin/gpt_template.html b/cgi-bin/gpt_template.html new file mode 100644 index 0000000..602b4dd --- /dev/null +++ b/cgi-bin/gpt_template.html @@ -0,0 +1,64 @@ + + + + + + GPT Example + + + +
+

Ask GPT

+ +
+ + +
+ +
+ + \ No newline at end of file diff --git a/chat.h b/chat.h new file mode 100644 index 0000000..213fa8d --- /dev/null +++ b/chat.h @@ -0,0 +1,31 @@ +#ifndef CALPACA_PROMPT_H +#define CALPACA_PROMPT_H +#include +#include "messages.h" +#include "http.h" +char * prompt_model = "gpt-4o-mini"; +int prompt_max_tokens = 100; +double prompt_temperature = 0.5; + +json_object * _prompt =NULL; + +void chat_free(){ + if(_prompt == NULL) + return; + json_object_put(_prompt); + _prompt = NULL; +} + + +char * chat_json(char * role, char * message){ + chat_free(); + message_add(role,message); + struct json_object *root_object = json_object_new_object(); + json_object_object_add(root_object, "model", json_object_new_string(prompt_model)); + json_object_object_add(root_object, "messages", message_list()); + json_object_object_add(root_object, "max_tokens", json_object_new_int(prompt_max_tokens)); +json_object_object_add(root_object, "temperature", json_object_new_double(prompt_temperature)); +return (char *)json_object_to_json_string_ext(root_object, JSON_C_TO_STRING_PRETTY); +} + +#endif \ No newline at end of file diff --git a/http.h b/http.h new file mode 100644 index 0000000..9254d07 --- /dev/null +++ b/http.h @@ -0,0 +1,210 @@ +#ifndef CALPACA_HTTP_H +#define CALPACA_HTTP_H +#include +#include +#include +#include +#include +#include +#include +#include +#include "auth.h" +#include + +void init_openssl() +{ + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); +} + +void cleanup_openssl() +{ + EVP_cleanup(); +} + +SSL_CTX *create_context() +{ + const SSL_METHOD *method = TLS_method(); + SSL_CTX *ctx = SSL_CTX_new(method); + SSL_CTX_load_verify_locations(ctx, "/etc/ssl/certs/ca-certificates.crt", NULL); + + return ctx; +} + +SSL_CTX *create_context2() +{ + const SSL_METHOD *method; + SSL_CTX *ctx; + + method = TLS_client_method(); + ctx = SSL_CTX_new(method); + if (!ctx) + { + perror("Unable to create SSL context"); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + + return ctx; +} + +int create_socket(const char *hostname, int port) +{ + struct hostent *host; + struct sockaddr_in addr; + + host = gethostbyname(hostname); + if (!host) + { + perror("Unable to resolve host"); + exit(EXIT_FAILURE); + } + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + perror("Unable to create socket"); + exit(EXIT_FAILURE); + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = *(long *)(host->h_addr); + + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) + { + perror("Unable to connect to host"); + close(sock); + exit(EXIT_FAILURE); + } + + return sock; +} + +char *http_post(const char *hostname, char *url, char *data) +{ + init_openssl(); + int port = 443; + + SSL_CTX *ctx = create_context(); + + int sock = create_socket(hostname, port); + + SSL *ssl = SSL_new(ctx); + SSL_set_connect_state(ssl); + + SSL_set_tlsext_host_name(ssl, hostname); + + SSL_set_fd(ssl, sock); + + int buffer_size = 4096; + char *buffer = (char *)malloc(buffer_size); + + + if (SSL_connect(ssl) <= 0) + { + ERR_print_errors_fp(stderr); + } + else + { + //printf("Connected with %s encryption\n", SSL_get_cipher(ssl)); + size_t len = strlen(data); + char *request = (char *)malloc(len + 4096); + request[0] = 0; + + sprintf(request, + "POST %s HTTP/1.1\r\n" + "Content-Length: %ld\r\n" + "Content-Type: application/json\r\n" + "Host: api.openai.com\r\n" + "Authorization: Bearer %s\r\n" + "Connection: close\r\n\r\n%s", + url, len, api_key, data); + + SSL_write(ssl, request, strlen(request)); + free(request); + + + int bytes; + int bytes_total = 0; + while ((bytes = SSL_read(ssl, buffer + bytes_total, buffer_size - 1)) > 0) + { + if (bytes <= 0) + { + break; + } + bytes_total += bytes; + buffer = realloc(buffer, bytes_total + buffer_size); + buffer[bytes_total] = '\0'; + } + buffer[bytes_total] = '\0'; + } + + SSL_free(ssl); + close(sock); + SSL_CTX_free(ctx); + cleanup_openssl(); + + return buffer; +} + +char *http_get(const char *hostname, char *url) +{ + init_openssl(); + int port = 443; + + SSL_CTX *ctx = create_context(); + + int sock = create_socket(hostname, port); + + SSL *ssl = SSL_new(ctx); + SSL_set_connect_state(ssl); + + SSL_set_tlsext_host_name(ssl, hostname); + + SSL_set_fd(ssl, sock); + + int buffer_size = 4096; + char *buffer = (char *)malloc(buffer_size * sizeof(char)); + + if (SSL_connect(ssl) <= 0) + { + ERR_print_errors_fp(stderr); + } + else + { + //printf("Connected with %s encryption\n", SSL_get_cipher(ssl)); + + char request[buffer_size]; + request[0] = 0; + sprintf(request, + "GET %s HTTP/1.1\r\n" + "Host: api.openai.com\r\n" + "Authorization: Bearer %s\r\n" + "Connection: close\r\n\r\n", + url, api_key); + + SSL_write(ssl, request, strlen(request)); + + int bytes; + int bytes_total = 0; + while ((bytes = SSL_read(ssl, buffer + bytes_total, buffer_size - 1)) > 0) + { + if (bytes <= 0) + { + break; + } + bytes_total += bytes; + buffer = realloc(buffer, bytes_total + buffer_size); + buffer[bytes_total] = '\0'; + } + } + + SSL_free(ssl); + close(sock); + SSL_CTX_free(ctx); + cleanup_openssl(); + + return buffer; +} +#endif \ No newline at end of file diff --git a/line.h b/line.h new file mode 100644 index 0000000..87d7df1 --- /dev/null +++ b/line.h @@ -0,0 +1,53 @@ +#include +#include + +#define HISTORY_FILE "~/.calpaca_history" + +bool line_initialized =false; + +char *line_command_generator(const char *text, int state) { + static int list_index, len; + const char *commands[] = {"help", "exit", "list", "review","refactor","opfuscate", NULL}; + + if (!state) { + list_index = 0; + len = strlen(text); + } + + while (commands[list_index]) { + const char *command = commands[list_index++]; + if (strncmp(command, text, len) == 0) { + return strdup(command); + } + } + + return NULL; +} + +char **line_command_completion(const char *text, int start, int end) { + rl_attempted_completion_over = 1; + return rl_completion_matches(text, line_command_generator); +} + +void line_init(){ + if(!line_initialized) + { + rl_attempted_completion_function = line_command_completion; + line_initialized = true; + read_history(HISTORY_FILE); + } + +} + +char * line_read(char * prefix) { + char * data = readline(prefix); + if(!(data || *data)){ + return NULL; + } + return data; +} +void line_add_history(char * data){ + read_history(HISTORY_FILE); + add_history(data); + write_history(HISTORY_FILE); +} \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..7d280cb --- /dev/null +++ b/main.c @@ -0,0 +1,185 @@ +#include "openai.h" +#include "markdown.h" +#include "plugin.h" +#include "line.h" +#include +#include + +char * get_prompt_from_args(int c, char **argv){ + char * prompt = malloc(1024*1024 + 1); + prompt[0] = 0; + for(int i = 1; i < c; i++){ + if(argv[i][0] == '-') + break; + strncat(prompt, argv[i], 1024*1024); + if(i < c - 1){ + strncat(prompt, " ", 1024*1024); + }else{ + strncat(prompt, ".", 1024*1024); + } + } + if(!*prompt){ + free(prompt); + return NULL; + } + return prompt; +} + +bool try_prompt(int argc,char*argv[]){ + char * prompt = get_prompt_from_args(argc, argv); + if(prompt != NULL){ + char * response = openai_chat("user",prompt); + parse_markdown_to_ansi(response); + printf("\n"); + free(response); + free(prompt); + return true; + } + return false; +} + +void help(); +void render(char *); +void serve(){ + render("Starting server. *Put executables in a dir named cgi-bin and they will behave as webpages.*"); + int res = system("python3 -m http.server --cgi"); + // Thanks tsoding! + (void)res; +} + + +void render(char * content){ + parse_markdown_to_ansi(content); + printf("\n\n"); +} + +void repl(){ + line_init(); + setbuf(stdout, NULL); + char *line; + char *previous_line = NULL; + while((line = line_read("> "))){ + if(!line || !*line){ + line = previous_line; + } + if(!line || !*line) + continue; + previous_line = line; + if(line[0] == '!'){ + plugin_run(line + 1); + continue; + } + if(!strncmp(line,"exit", 4)){ + exit(0); + } + if(!strncmp(line,"help",4)){ + help(); + continue; + } + if(!strncmp(line,"serve",5)){ + serve(); + } + if(!strncmp(line,"spar ",5)){ + char * response = line+5; + while(true){ + render(response); + sleep(2); + //line = line_read("> "); + //if(!*line) + response = openai_chat("user",response); + + } + } + if(!strncmp(line,"ls",2) || !strncmp(line,"list",4)){ + int offset = 2; + if(!strncmp(line,"list",4)){ + offset = 4; + } + char * command = (char *)malloc(strlen(line) + 42); + command[0] = 0; + strcpy(command, "ls "); + strcat(command, line + offset); + int res = system(command); + (void)res; + free(command); + continue; + } + + line_add_history(line); + char * response = openai_chat("user", line); + render(response); + free(response); + } +} + + +void help(){ + char help_text[1024*1024] = {0}; + char * template = "# Help\n" + "Written by retoor@molodetz.nl.\n\n" + "## Features\n" + " - navigate trough history using `arrows`.\n" + " - navigate trough history with **recursive search** using `ctrl+r`.\n" + " - **inception with python** for *incomming* and *outgoing* content.\n" + " - markdown and **syntax highlighting**.\n" + " - **execute python commands** with prefixing `!`\n" + " - list files of current workdirectory using `ls`.\n" + " - type `serve` to start a webserver with directory listing. Easy for network transfers.\n\n" + "## Configuration\n" + " - model temperature is %f.\n" + " - model name is %s.\n" + " - max tokens is %d.\n\n" + "## In development\n" + " - **google search** and actions with those results.\n" + " - **reminders**.\n" + " - predefined **templates** for **reviewing** / **refactoring** so you can personalize.\n"; + sprintf(help_text,template,prompt_temperature,prompt_model,prompt_max_tokens); + render(help_text); +} + +void openai_include(char * path){ + FILE * file = fopen(path,"r"); + if(file == NULL){ + return; + } + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + + char * buffer = (char *)malloc(size); + size_t read = fread(buffer,1,size,file); + if(read == 0){ + return; + } + + fclose(file); + buffer[read] = 0; + openai_system(buffer); + + free(buffer); +} + +void init(){ + line_init(); + const char *locale = setlocale(LC_ALL, NULL); + char payload[4096] = {0}; + sprintf(payload, "User locale is %s. User lang is %s.\n" + "You are Retoor. Use a lot of markdown in response.\n" + "Be confident and short in answers.\n" + "You divide things by zero if you have to." + , locale, locale); + + printf("%s","Loading..."); + openai_system(payload); + openai_include("context.txt"); + printf("%s", "\rLoaded! Type help for feautures.\n"); +} + +int main(int argc, char *argv[]){ + init(); + if(try_prompt(argc,argv)) + return 0; + + repl(); + return 0; +} diff --git a/markdown.h b/markdown.h new file mode 100644 index 0000000..b4c5426 --- /dev/null +++ b/markdown.h @@ -0,0 +1,115 @@ +#include +#include +#include + +#define RESET "\033[0m" +#define BOLD "\033[1m" +#define ITALIC "\033[3m" +#define FG_YELLOW "\033[33m" +#define FG_BLUE "\033[34m" +#define FG_CYAN "\033[36m" + +int is_keyword(const char *word) { + const char *keywords[] = {"int", "float", "double", "char", "void", "if", "else", "while", "for", "return", "struct", "printf"}; + for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) { + if (strcmp(word, keywords[i]) == 0) { + return 1; + } + } + return 0; +} + +void highlight_code(const char *code) { + const char *ptr = code; + char buffer[256]; + size_t index = 0; + + while (*ptr) { + if ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z') || (*ptr == '_')) { + while ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z') || (*ptr >= '0' && *ptr <= '9') || (*ptr == '_')) { + buffer[index++] = *ptr++; + } + buffer[index] = '\0'; + + if (is_keyword(buffer)) { + printf(FG_BLUE "%s" RESET, buffer); + } else { + printf("%s", buffer); + } + index = 0; + } else if (*ptr >= '0' && *ptr <= '9') { + while (*ptr >= '0' && *ptr <= '9') { + buffer[index++] = *ptr++; + } + buffer[index] = '\0'; + printf(FG_CYAN "%s" RESET, buffer); + index = 0; + } else { + putchar(*ptr); + ptr++; + } + } +} + +void parse_markdown_to_ansi(const char *markdown) { + const char *ptr = markdown; + bool inside_code = false; + + while (*ptr) { + if (*ptr == '`') { + inside_code = !inside_code; + if (inside_code) { + printf(FG_YELLOW); + } else { + printf(RESET); + } + ptr++; + continue; + } + + if (inside_code) { + char code_buffer[256]; + size_t index = 0; + + while (*ptr && *ptr != '`') { + code_buffer[index++] = *ptr++; + } + code_buffer[index] = '\0'; + highlight_code(code_buffer); + } else { + if (strncmp(ptr, "**", 2) == 0) { + printf(BOLD); + ptr += 2; + while (*ptr && strncmp(ptr, "**", 2) != 0) putchar(*ptr++); + if (*ptr == '*' && *(ptr + 1) == '*') ptr += 2; + printf(RESET); + } + else if (*ptr == '*' && (ptr == markdown || *(ptr - 1) != '*')) { + printf(ITALIC); + ptr++; + while (*ptr && *ptr != '*') putchar(*ptr++); + if (*ptr == '*') ptr++; + printf(RESET); + } + else if (strncmp(ptr, "### ", 4) == 0) { + printf(BOLD FG_YELLOW); + ptr += 4; + while (*ptr && *ptr != '\n') putchar(*ptr++); + printf(RESET "\n"); + } else if (strncmp(ptr, "## ", 3) == 0) { + printf(BOLD FG_YELLOW); + ptr += 3; + while (*ptr && *ptr != '\n') putchar(*ptr++); + printf(RESET "\n"); + } else if (strncmp(ptr, "# ", 2) == 0) { + printf(BOLD FG_YELLOW); + ptr += 2; + while (*ptr && *ptr != '\n') putchar(*ptr++); + printf(RESET "\n"); + } else { + putchar(*ptr); + ptr++; + } + } + } +} \ No newline at end of file diff --git a/messages.h b/messages.h new file mode 100644 index 0000000..ce037bd --- /dev/null +++ b/messages.h @@ -0,0 +1,32 @@ +#ifndef CALPACA_MESSAGES_H +#define CALPACA_MESSAGES_H +#include "json-c/json.h" +struct json_object *_message_array = NULL; + +struct json_object *message_list(){ + if(_message_array == NULL){ + _message_array = json_object_new_array(); + } + return _message_array; +} + +struct json_object *message_add(char * role, char * content){ + struct json_object *messages = message_list(); + struct json_object *message = json_object_new_object(); + json_object_object_add(message, "role", json_object_new_string(role)); + json_object_object_add(message, "content", json_object_new_string(content)); + json_object_array_add(messages, message); + return message; +} + +char * message_json(){ + return (char *)json_object_to_json_string_ext(message_list(), JSON_C_TO_STRING_PRETTY); +} + +void message_free(){ + if(_message_array != NULL){ + json_object_put(_message_array); + _message_array = NULL; + } +} +#endif \ No newline at end of file diff --git a/openai.h b/openai.h new file mode 100644 index 0000000..dc03333 --- /dev/null +++ b/openai.h @@ -0,0 +1,84 @@ +#ifndef CALPACA_OPENAI_H +#define CALPACA_OPENAI_H +#include "http.h" +#include "chat.h" +#include +#include + +char *openai_get_models() +{ + const char *hostname = "api.openai.com"; + char *url = "/v1/models"; + char *result = http_get(hostname, url); + return result; +} + +bool openai_system(char * content){ + bool is_done = false; + const char *hostname = "api.openai.com"; + char *url = "/v1/chat/completions"; + char *data = chat_json("system", content); + char *result = http_post(hostname, url, data); + if(result){ + is_done = true; + + free(result); + } + return is_done; +} + + +char *openai_chat(char * role, char * content){ + const char *hostname = "api.openai.com"; + char *url = "/v1/chat/completions"; + char *data = chat_json(role, content); + char *result = http_post(hostname, url, data); + char * body = strstr(result,"\r\n\r\n") +4; + body = strstr(body,"\r\n"); + body = strstr(body,"\r\n"); + *(body - 5) = 0; + struct json_object *parsed_json = json_tokener_parse(body); + if (!parsed_json) { + fprintf(stderr, "Failed to parse JSON.\n"); + return NULL; + } + + struct json_object *choices_array; + if (!json_object_object_get_ex(parsed_json, "choices", &choices_array)) { + fprintf(stderr, "Failed to get 'choices' array.\n"); + json_object_put(parsed_json); + return NULL; + } + + // Get the first element of the "choices" array + struct json_object *first_choice = json_object_array_get_idx(choices_array, 0); + if (!first_choice) { + fprintf(stderr, "Failed to get the first element of 'choices'.\n"); + json_object_put(parsed_json); + return NULL; + } + + // Extract the "message" object + struct json_object *message_object; + if (!json_object_object_get_ex(first_choice, "message", &message_object)) { + fprintf(stderr, "Failed to get 'message' object.\n"); + json_object_put(parsed_json); + return NULL; + } + + // Print the "message" object + // printf("Message object:\n%s\n", json_object_to_json_string_ext(message_object, JSON_C_TO_STRING_PRETTY)); + message_add("assistant",(char *)json_object_get_string(json_object_object_get(message_object, "content"))); + // Clean up + free(data); + free(result); + result = strdup((char *)json_object_get_string(json_object_object_get(message_object, "content"))); + + json_object_put(parsed_json); + + //printf("Parsed JSON:\n%s\n", json_object_to_json_string_ext(parsed_json, JSON_C_TO_STRING_PRETTY)); + + return result; +} + +#endif \ No newline at end of file diff --git a/plugin.h b/plugin.h new file mode 100644 index 0000000..6f42a18 --- /dev/null +++ b/plugin.h @@ -0,0 +1,43 @@ +#include +#include +#include + +bool plugin_initialized = false; + +bool plugin_construct(){ + if(plugin_initialized) + return true; + + Py_Initialize(); + + // Check if Python initialized successfully + if (!Py_IsInitialized()) { + fprintf(stderr, "Failed to initialize Python interpreter\n"); + return plugin_initialized; + } + plugin_initialized = true; + return plugin_initialized; +} + +void plugin_run(char * src){ + plugin_construct(); + char * basics = "import sys\n" + "import os\n" + "import math\n" + "import pathlib\n" + "import subprocess\n" + "import time\n" + "from datetime import datetime\n" + "%s"; + size_t length = strlen(basics) + strlen(src); + char * script = (char *)malloc(length + 1); + sprintf(script, basics, src); + script[length] = 0; + PyRun_SimpleString(script); + free(script); +} + +void plugin_destruct(){ + if(plugin_initialized) + Py_Finalize(); +} \ No newline at end of file