#!/bin/python3 # Chat with an intelligent assistant in your terminal from ollama import Client import re import pyperclip import sys import argparse import pygments from pygments.lexers import get_lexer_by_name from pygments.formatters import TerminalFormatter server = 'localhost:11434' model = 'llama3.1:8b-instruct-q8_0' temp = 0.2 pattern = r'```[a-z]*\n[\s\S]*?\n```' line_pattern = r'`[a-z]*[\s\S]*?`' def highlight_code(language_name, code): # Check if the language is specified in the first line lexer_name = language_name if lexer_name == None: lines = code.split('\n') for line in lines: if line.strip().startswith('```'): lexer_name = line.strip().split('```')[1].strip() break elif line.strip().startswith('lang:'): lexer_name = line.strip().split(':')[1].strip() break if lexer_name: try: # Try to get the lexer by name lexer = get_lexer_by_name(lexer_name) except ValueError: # If the lexer is not found, guess it lexer = guess_lexer(code.split('\n')[1:-1]) if not lexer: # If no lexer is guessed, default to bash lexer = get_lexer_by_name('bash') else: # If no language is specified, guess the lexer print("LEXER NAME " + lexer_name) lexer = guess_lexer(code.split('\n')[1:-1]) if not lexer: # If no lexer is guessed, default to bash lexer = get_lexer_by_name('bash') formatter = TerminalFormatter() just_code = code.split('\n')[0] newlines = '\n'.join(code.split('\n')[1:]) # if code is a code block, strip surrounding block markers lines = code.split('\n') if (len(lines) > 2) and ('```' in lines[0]) and ('```' in lines[-1]): just_code = '\n'.join(code.split('\n')[1:-1]) highlighted_code = pygments.highlight(just_code, lexer, formatter) return highlighted_code + newlines def extract_code_block(markdown_text): # Use the regular expression pattern to find all matches in the markdown text matches = re.finditer(pattern, markdown_text) # Iterate over the matches and extract the code blocks code_blocks = [] for match in matches: code_block = match.group(0) highlighted_code = highlight_code(None, code_block) # Add the highlighted code block to the list of code blocks code_blocks.append(highlighted_code) if len(code_blocks) == 0: line_matches = re.finditer(line_pattern, markdown_text) for match in line_matches: code_block = match.group(0) code_blocks.append(code_block[1:-1]) return code_blocks def copy_string_to_clipboard(string): try: pyperclip.copy(string) except: return code_history = [] history = [ {"role": "system", "content": "You are a helpful, smart, kind, and efficient AI assistant. You always fulfill the user's requests accurately and concisely."}, ] def chat(message, stream=True): history.append({"role": "user", "content": message}) completion = client.chat( model=model, options={"temperature":temp}, messages=history, stream=stream ) result = '' language = '' large_chunk = [] for chunk in completion: if stream: text = chunk['message']['content'] large_chunk.append(text) large_text = ''.join(large_chunk) # update language if entering or leaving code block if ('\n' in large_text) and ('```' in large_text): language = large_text.split('```')[1].split('\n')[0] if language == '': language = None print(large_text, end='', flush=True) large_chunk = [] large_text = '' # Only print full lines if '\n' in large_text: output = large_text if language: output = highlight_code(language, output) print(output, end='', flush=True) large_chunk = [] result += text if not stream: result = completion['message']['content'] if stream: print(large_text, flush=True) history.append({"role": 'assistant', 'content': result}) return result def parse_args(): # Create the parser parser = argparse.ArgumentParser(description='Copy and open a source file in TextEdit') # Add the --follow-up (-f) argument parser.add_argument('--follow-up', '-f', nargs='?', const=True, default=False, help='Ask a follow up question when piping in context') # Add the --copy (-c) argument parser.add_argument('--copy', '-c', action='store_true', help='copy a codeblock if it appears') # Add the --shell (-s) argument parser.add_argument('--shell', '-s', nargs='?', const=True, default=False, help='output a shell command that does as described') # Add the --model (-m) argument parser.add_argument('--model', '-m', nargs='?', const=True, default=False, help='Specify model') # Add the --temp (-t) argument parser.add_argument('--temp', '-t', nargs='?', const=True, default=False, help='Specify temperature') # Add the --host (-h) argument parser.add_argument('--host', nargs='?', const=True, default=False, help='Specify host of ollama server') # Parse the arguments return parser.parse_args() def set_host(host): global server server = host def arg_follow_up(args): sys.stdin = open('/dev/tty') if args.follow_up != True: second_input = args.follow_up else: second_input = input('> ') return second_input def arg_shell(args): query = 'Form a shell command based on the following description. Only output a working shell command .\nDescription: ' if args.shell != True: query += args.shell else: query += input('> ') result = chat(query, False) result = blocks[0] if len(blocks := extract_code_block(result)) else result print(blocks) copy_string_to_clipboard(result) def handle_piped_input(args): all_input = sys.stdin.read() query = 'Use the following context to answer the question. There will be no follow up questions from the user so make sure your answer is complete:\nSTART CONTEXT\n' + all_input + '\nEND CONTEXT\nAfter you answer the question, reflect on your answer and determine if it answers the question correctly.' if args.copy: query += 'Answer the question using a codeblock for any code or shell scripts\n' if args.follow_up: query += arg_follow_up(args) query += '\n' result = chat(query) blocks = extract_code_block(result) if args.copy and len(blocks): copy_string_to_clipboard(blocks[0]) def handle_non_piped_input(args): if args.shell: arg_shell(args) exit() if args.follow_up: user_input = arg_follow_up(args) result = chat(user_input) exit() while True: try: user_input = input('> ') except (EOFError, KeyboardInterrupt): print() exit() else: chat(user_input) client = None def main(): args = parse_args() if args.host: set_host(args.host) # Point to the local server global client client = Client(host=server) if args.model: global model model = args.model if args.temp: global temp temp = float(args.temp) if not sys.stdin.isatty(): handle_piped_input(args) else: handle_non_piped_input(args) if __name__ == '__main__': main()