Grammarly still sucks. And Go…. when you don’t want to rewrite things in Rust. This is a part 2 of an another blog.

goroutines looks easy

Continuing from the last part where I created this using mostly PowerShell.

Edit (July 23, 2023 (Sunday)) - I have polished my GitHub repo a bit: https://github.com/anyfactor/Chatgpt_grammar_checker

What changed?

The PowerShell script was bad:

  • It was unreadable to me atleast.
  • The JSON parsing was unnecessarily verbose.
  • I actually need async support.
  • Setting content in clipboard was a hit or miss.

The AHK script was bad:

  • I actually need some kind of async support.
  • I don’t need the msgbox alert system because when running the operation through cmd, the terminal pops up anyway.
  • It was copying the corrections made Corrections Made section which was annoying.

Current state of the script

Grammarcheck with go+ahk

Autohotkey


F8::{
  ;copies the content from current selection
  A_Clipboard := ""  ; Start off empty to allow ClipWait to detect when the text has arrived.
  Send "^c"
  ClipWait  ; Wait for the clipboard to contain text.

  ; async operation via threading of some sort
  ; https://www.reddit.com/r/AutoHotkey/comments/mqbyfz/can_i_have_multiple_functionsactions_running_at/
  SetTimer trayalert, -1, 1
  SetTimer core_grammar_operation, -1, 1
  return

  core_grammar_operation(){
    ; runs the go project
    RunWait A_ComSpec ' /c "<path_to_file_main.exe>"'

    ; delay before content is posted.
    Sleep 800
    Send "^v"
  }

  trayalert(){
    ; tray alert for script being copied
    input_prompt := A_Clipboard
    TrayTip "Grammar Check Triggered", input_prompt
    Sleep 4000   ; Let it display for 3 seconds.
    HideTrayTip
  }    
  HideTrayTip() {
    ; tray alert helper function to hide the tray
    TrayTip  ; Attempt to hide it the normal way.
    if SubStr(A_OSVersion,1,3) = "10." {
      A_IconHidden := true
      Sleep 200  ; It may be necessary to adjust this sleep.
      A_IconHidden := false
    }
  }
}

Breakdown:

  • I opted to remove the msgbox for showing the grammar correction because it wasn’t needed. The cmd section triggered the terminal where I can show the grammar correction bit from go.
  • I implemented a threading operation for running the go code and triggering the trayalert simultaneously. I have no clue how threading/async in AHK, I just winged it.
  • It pastes only the corrected sections from the clipboard.

Go

First of all, the Go script is amazing! However, if you have any optimization advice, it would be greatly appreciated. I wrote the script entirely using ChatGPT prompts. This script looks long, but it is kidna self explantory.

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/atotto/clipboard"
)

func copy_to_clipboard(text string) {
	err := clipboard.WriteAll(text)
	if err != nil {
		fmt.Println("Error copying text to clipboard:", err)
		return
	}
}

func extract_text(text, stoppingPointText string) string {
	splitText := strings.Split(text, stoppingPointText)
	if len(splitText) > 0 {
		result := strings.TrimSpace(splitText[0])
		result = strings.Trim(result, "\"'")
		return result
	}
	return ""
}

func main() {
	token := ""

	clipboard_content, err := clipboard.ReadAll()
	if err != nil {
		fmt.Println("Error reading clipboard:", err)
		return
	}

	fmt.Println("Clipboard content:\n\n", clipboard_content)

	headers := map[string]string{
		"Content-Type":  "application/json",
		"Authorization": "Bearer " + token,
	}

	jsonData := map[string]interface{}{
		"model": "gpt-3.5-turbo",
		"messages": []map[string]string{
			{"role": "user", "content": "Fix the grammar errors and at the end list out the mistakes made:\n\n " + clipboard_content},
		},
	}

	jsonValue, _ := json.Marshal(jsonData)

	req, err := http.NewRequest("POST", "https://api.openai.com/v1/chat/completions", strings.NewReader(string(jsonValue)))
	if err != nil {
		fmt.Println("Error creating HTTP request:", err)
		return
	}

	for key, value := range headers {
		req.Header.Set(key, value)
	}

	client := &http.Client{}
	response, err := client.Do(req)
	if err != nil {
		fmt.Println("Error making HTTP request:", err)
		return
	}
	defer response.Body.Close()

	var responseData map[string]interface{}
	err = json.NewDecoder(response.Body).Decode(&responseData)
	if err != nil {
		fmt.Println("Error decoding JSON response:", err)
		return
	}

	choices := responseData["choices"].([]interface{})
	firstChoice := choices[0].(map[string]interface{})
	message := firstChoice["message"].(map[string]interface{})
	content := message["content"].(string)

	if strings.Contains(content, "Mistakes made:") {
		extractedText := extract_text(content, "Mistakes made:")
		copy_to_clipboard(extractedText)
		fmt.Println("\n\nExtracted content:", extractedText)
	} else if strings.Contains(content, "Mistakes:") {
		extractedText := extract_text(content, "Mistakes:")
		copy_to_clipboard(extractedText)
		fmt.Println("\n\nExtracted content:", extractedText)
	} else {
		fmt.Println("Text mismatch")
		return
	}

	fmt.Println("\n\n" + content)
	var exit_statement string
	fmt.Scanln(&exit_statement)
	fmt.Println(exit_statement)
}

Breakdown:

  • Retrieves the incorrect section from the clipboard.
  • Makes an API call to ChatGPT.
  • Displays the response from the API call.
  • Extracts the corrected version from the API response and removes the “Mistakes made” section.
  • Copies the extracted correct section to the clipboard.

One thing I don’t like:

I would have preferred to write this section:

if strings.Contains(content, "Mistakes made:") {
    extractedText := extract_text(content, "Mistakes made:")
    copy_to_clipboard(extractedText)
    fmt.Println("\n\nExtracted content:", extractedText)
} else if strings.Contains(content, "Mistakes:") {
    extractedText := extract_text(content, "Mistakes:")
    copy_to_clipboard(extractedText)
    fmt.Println("\n\nExtracted content:", extractedText)
} else {
    fmt.Println("Text mismatch")
    return
}

like this:

if strings.Contains(content, "Mistakes made:") {
    extractedText := extract_text(content, "Mistakes made:")
} else if strings.Contains(content, "Mistakes:") {
    extractedText := extract_text(content, "Mistakes:")
} else {
    fmt.Println("Text mismatch")
    return
}

copy_to_clipboard(extractedText)
fmt.Println("\n\nExtracted content:", extractedText)

But I am getting unused variable error, which is fine, I guess. But, I kinda want to ditch my variables this way sometimes, because I write mostly Python code.

Why not Rust?

I tried Rust for this project, but it was quite painful. ChatGPT support for generating good Rust code needs work. And trust me when I say this, I don’t know how to write Go code. But I was able to cobble up something, didn’t I? But I don’t have that confidence to debug ChatGPT generated Rust code.

Go is uncool but it works

Writing async code is challenging. I encountered a couple of bugs while working with the clipboard, and I feel like Rust is overly statically typed for scripting. Go is not cool, but it works. I would like to use goroutines for some section of the code.

goroutines looks easy

Conclusion

  • Simple quality of life improvements include exiting the terminal prompt if the user doesn’t press enter within a certain period.
  • Display a meme or joke on the tray alert while the script runs in the background. Since we are making an API call, every second feels like an eternity. So need something look at.
  • I don’t think I can optimize it further, as the bottleneck is in the API call. But if you have any advice, feel free to let me know.

Footnote

Responses to my script:

Then why send your text to a slow third-party in the first place? There are craptons of spelling and grammar checkers available which will work offline, be significantly faster, consume less resources, and not change the meaning of your text. We solved this problem decades ago, we don’t need to shove AI in everything.

My response:

Grammar checkers are essentially typo checkers and are not context aware to be truly grammar checkers. Context aware grammar checking means the program has to consider each line of text and identify grammar mistakes. As traditional grammar checkers check typos in real time, every mistake you do is flashed in front of you. You have to stop evaluate, fix and continue. This is distracting.

My solution is to get your thoughts immediately out of your head in a big chunk. Check the grammar of that chunk of text in a single button press wait a few seconds and then just paste the text.

The tone shift is good in technical writing, but in forum style communication it kinda dehumanizes the comment. But it is not a major thing.

I use grammarly pro, but I find this solution to be extremely robust as this solution is platform and software agnostic. Like writing in my text editor - LiteXL.

I have no desire to send what I write to third-parties I don’t trust.

My response:

I honestly find this response ironic, considering the person said that in open internet forum which is being scraped by dozens, if not hundreds of third party services.

The problem with this statement is that “it is my choice” that I am not sending the data third party providers. But in reality adopting a system that truly takes away your writing quirks, emotion and anything that is truly personal should be embraced if you are that conscious about data privacy philosophy. You chose one evil over several and just say this is not me on the internet.

Conclusion:

Open source evengelism sucks.