commit 4d82322545bb4f2b509b51d52f9fa35061468434 Author: retoor Date: Sat Jan 4 06:00:03 2025 +0100 Initial commit. 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