Skip to main content
alexanderp3125138
Inspiring
March 13, 2024
Answered

Script to integrate local LLM for proofreading

  • March 13, 2024
  • 1 reply
  • 9633 views

Hey folks,

I'm thinking of writing a script to add local LLM api support to FrameMaker. I basically want to get AI assisted proofreading/text correction by doing the following (using a local lightweight Mistral-7B model):

1. Select text, then send it via button press to an LLM client  running on my computer (LM Studio or ollama) for proofreading
2. Receive & process the proofreading results
3. Apply the suggestions to the text in FrameMaker

 

Before I dive into what might just be an impossible task due to ExtendScript limitations (I'd have to do quite a lot of digging, I'm not a real coder), do you think this is doable? I'd be very grateful for any insights, or tips where to start. 🙂🙏

    Correct answer frameexpert

    Thanks for your reply, I really appreciate it. 🙂


    I'm looking for a way to actually select a paragraph with Extendscript. The idea is that I want the user to be able to either select text with the mouse, or, if nothing is selected, simply select the whole next paragraph. This kind of advanced either/or functionality will be reserved for later though. I'll stitch it together once both parts are working independently. 

     

    Right now, option 1 is working fine, the text is sent over to the llm via api, connection is staying alive as long as needed, corrected text is received and pasted back into the document, overwriting the previous (selected) text. 

     

    Now for option 2, if the text gets copied and sent over automatically without selecting it first, it gets corrected, too, but then inserted into the paragraph at insertion point, so I end up with the old and new text.

     

    So all I need is a way to select the next paragraph relative to the cursor position. I think I could integrate this easily with the getText function that's already working. I could also paste my whole script text here, but that would probably just unnecessarily blow up my post, and I don't want to bother you guys even more with walls of text. 😉


    These two short videos will explain text locations and text ranges:

    https://youtu.be/6ywYWU4VC7g?si=pA7llQ5vS8nIlqwt

    https://youtu.be/fH5giAcDp6Q?si=lerkUNDTWdSoLKjO

    If you are in a hurry, the second one will show you how to select a paragraph.

    1 reply

    frameexpert
    Community Expert
    March 13, 2024

    How can your LLM receive inputs? You could send text via a commandline from ExtendScript. ExtendScript does allow some integration with other programs via C++.

    alexanderp3125138
    Inspiring
    March 13, 2024

    Input is received via local api (LM Studio = http://localhost:1234/, ollama = http://localhost:11434/). I'm wondering if it's possible to create some sort of automated "roundtrip", with text pushed to the api, and completion received automatically.

    alexanderp3125138
    Inspiring
    March 15, 2024

    I gave you a headstart with this post:

    https://community.adobe.com/t5/framemaker-discussions/get-framemaker-text-as-a-string/td-p/14491503

    You will see that I have created a function for getting text from any FrameMaker text object (TextRange, Pgf, Flow, etc.). Functions like this are useful because you can plug them into any of your scripts as needed.


    Slowly getting there... my script does send highlighted text over the api now, and processes the corrected text. I have two issues left, any help would be greatly appreciated:

    1. the returned text always replaces the whole paragraph instead of the highlighted text, and 

    2. Sometimes I get a weird error from this snippet:

    alert("Failed to parse JSON response: " + e.toString()); 

    The error message is "reference error: ) has no value". Is that maybe related to the charset, i.e. is my llm returning unsupported characters?

    Here's my updated code, I'll truncate/delete my post from above, as it's not relevant anymore:

    var doc, postData, postRequest, conn, response, bodyStart, body, jsonResponse, correctedText;
    
    // Make variables for the active document and the current text selection.
    doc = app.ActiveDoc;
    
    if (doc.ObjectValid() == true) {
        var textRange = doc.TextSelection; // TextRange object
        var highlightedText = getText(textRange, doc); // Use the getText function to get the highlighted text
    
        if (highlightedText !== "") {
            // Prepare your JSON payload using the highlighted text
            postData = JSON.stringify({
                "messages": [
                    {
                        "role": "user",
                        "content": "You are a precise and accurate proof reader. You only ever give the corrected text, but no further explanation! Correct the following text: " + highlightedText
                    }
                ]
            });
    
            // Create the POST request
            postRequest = "POST /v1/chat/completions HTTP/1.1\r\n" +
                "Host: localhost:1234\r\n" +
                "Content-Type: application/json; charset=utf-8\r\n" + // Specify UTF-8 charset
                "Content-Length: " + postData.length + "\r\n" +
                "\r\n" +
                postData;
    
            conn = new Socket;
            // Attempt to open our socket connection
            if (conn.open("localhost:1234", "binary")) {
                conn.write(postRequest);
    
                // Response reading
                response = conn.read(999999); // Adjust as necessary
                conn.close();
    
                // Parsing
                bodyStart = response.indexOf("\r\n\r\n") + 4;
                body = response.substring(bodyStart);
    
                // Assuming JSON response
                try {
                    jsonResponse = JSON.parse(decodeURIComponent(escape(body))); // Decode the response body
                    correctedText = jsonResponse.choices[0].message.content; // Adjust based on actual JSON field
                    alert("Corrected Text:\n\n" + correctedText);
    
                    // Start replacing the text as per the example provided
                    // First, select the text to be replaced
                    textRange.beg.obj = textRange.end.obj = textRange.beg.obj; // Assuming the text range is within a single paragraph/object
                    textRange.beg.offset = 0;
                    textRange.end.offset = Constants.FV_OBJ_END_OFFSET;
                    doc.TextSelection = textRange;
                    textRange = doc.TextSelection;
                    textRange.end.offset--;
                    doc.TextSelection = textRange;
    
                    // Clear the selected text
                    doc.Clear(0);
    
                    // Insert the corrected text
                    doc.AddText(textRange.beg, correctedText);
    
                } catch (e) {
                    alert("Failed to parse JSON response: " + e.toString());
                }
            } else {
                alert("Failed to open a connection to the API.");
            }
        } else {
            alert("No text is highlighted or selected. Please highlight the text you want to correct.");
        }
    } else {
        alert("No active document found. Cannot continue.");
    }
    
    alert("Script complete!");