[C++] 286 FLTK : ChatGPTアプリの製作 その15 経過時間の測定

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

ChatGPTアプリの送受信に掛かる時間を測定して表示するようにしました。

これまでFLTKアプリで時間経過の概念を扱ったことがなかったので結構難航しました。

ストップウォッチの値が増えていく様子を表示したかったのですが、並列処理が出来なくて断念しました。

ウィンドウ範囲外の見えないボタンとしてStartボタン、Stopボタン、Resetボタンを配置し、送信ボタンのコールバック関数(sendCB関数)の中でdo_callback関数を使ってボタンを押すというややこしい仕様になっています。

ストップウォッチのGUI数値が送受信中に変化しなくてよいのであれば、もっと単純な方法で実装できていました。ソースコードの構成が複雑になるだけなので、明日以降簡潔な方法に変更します

こういったところはSDLに分があるのかもしれません。

void sendCB(Fl_Widget*, void*) {
    // ストップウォッチ開始
    resetBtn -> do_callback();
    startBtn -> do_callback();

    <中略>

    // ストップウォッチ停止
    stopBtn -> do_callback();
}
#include <FLstd.h>
#include <cppstd.h>
#include "Stopwatch.h"

// 経過時間を表示するFl_Output
extern Fl_Window* window;
extern Fl_Output* timeShow;

// ストップウォッチのインスタンスを作成する
Stopwatch stopwatch;

// スタートボタンが押された時のコールバック関数
void startCallback(Fl_Widget* widget, void*) {
    stopwatch.start();
}

// ストップボタンが押された時のコールバック関数
void stopCallback(Fl_Widget* widget, void*) {
    stopwatch.stop();
}

// リセットボタンが押された時のコールバック関数
void resetCallback(Fl_Widget* widget, void*) {
    stopwatch.reset();
    timeShow->value("");
}

// タイマーが起動した時のコールバック関数
void timerCallback(void*) {
    // 経過時間を取得して、Fl_Outputに表示する
    double elapsedTime = stopwatch.elapsedTime();
    stringstream ss;
    // ss << std::fixed << elapsedTime;
    ss << std::fixed << std::setprecision(1) << elapsedTime;
    // cout << "経過時間: " << ss.str() << endl;
    timeShow->value(ss.str().c_str());

    window -> redraw();

    // タイマーを再起動する
    Fl::repeat_timeout(0.01, timerCallback);
}

[C++] 285 FLTK : ChatGPTアプリの製作 その14 コードの取り出し

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

ChatGPTアプリ製作開始からだらだらと1週間掛けて、ようやく基本的な機能を一通り実装しました。

回答からコードを取り出し、何番目のコードでも選択してクリップボードにコピーできます。

次は特定分野のドキュメントを読み込ませたアシスタントBotを作成したいです。都度ドキュメントを送ると課金がかさみレスポンスも悪くなるので、一回読み込ませて単発の質問をいくつか投げかけるという仕様が妥当なところかと思います。

void advanceCB(Fl_Widget*, void*){
    if (codeNumCur == codeNum){
        codeNumCur = 0;
    }

    // 初期化
    codeNumShow -> value("");

    Fl_Text_Buffer* bufferReset = new Fl_Text_Buffer();
    bufferReset -> text("");
    codeDisplay->buffer(bufferReset);

    window -> redraw();

    // code取り出し&表示
    codes = getCode(jsonResponse);
    cout << "getCode検出コード数: " << codes.size() << endl;

    // codeNumCur加算
    codeNumCur++;

    if (codes.size() > 0){
        bufferCode = new Fl_Text_Buffer();
        bufferCode -> text(codes[codeNumCur-1].c_str());
        codeDisplay -> buffer(bufferCode);
        codeDisplay -> wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 5);
    }

    // コード数表示
    codeNum = codes.size();
    if (codeNum > 0){
        codeNumStr =  to_string(codeNumCur) + " / " + to_string(codeNum);
        cout << "codeNumStr: " << codeNumStr << endl;
        codeNumShow -> value(codeNumStr.c_str());
    }
}

[C++] 284 FLTK : ChatGPTアプリの製作 その13 パースエラーの捕捉 try-catch文

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

jsonデータのパースエラーを捕捉するtry-catch文をメモ書きしておきます。

    try{
        resJson = json::parse(res);
    }
    catch (const json::parse_error& e) {
        std::cerr << "Parse error:\n" << e.what() << std::endl;

        Fl_Text_Buffer* bufferError = new Fl_Text_Buffer();
        bufferError -> text(e.what());
        noticeDisplay->buffer(bufferError);
        noticeDisplay -> wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 5);

        return;
    }
    catch (const char* error) {
        cerr << "Error: " << error << endl;

        Fl_Text_Buffer* bufferError = new Fl_Text_Buffer();
        bufferError -> text(error);
        noticeDisplay->buffer(bufferError);
        noticeDisplay -> wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 5);

        return;
    }
    catch (...){
        cerr << "その他のError" << endl;

        const char* error = "その他のError";
        Fl_Text_Buffer* bufferError = new Fl_Text_Buffer();
        bufferError -> text(error);
        noticeDisplay->buffer(bufferError);
        noticeDisplay -> wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 5);

        return;
    }

[C++] 283 FLTK : ChatGPTアプリの製作 その12 前後の質問表示 

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

一連の質問についてボタンで前後移動できるようにしました。

質問回数表示で数値がダブって描画されるというトラブルが発生しましたが、window -> redraw();で解決しました。

あとはGUI左下の回答内コード表示が出来れば完成です。

アプリが落ちないよう例外処理を随時追加していきます。

void backCB(Fl_Widget*, void*){
    questionNumShow -> value("");
    window -> redraw();

    // jsonDataからuserとassistantのcontentを取り出し表示
    if (arrowCount == 0){
        currentCount = countQ - 2;

        if(currentCount < 0){
            currentCount = 0;
        }

        arrowCount++;
    } else {
        if (currentCount > 0){
            currentCount--;
        }
    }

    cout << "currentCount: " << currentCount << endl;

    // jsonData確認
    string jsonDataString = jsonData.dump(2);
    cout << "jsonDataString:\n" << jsonDataString << endl;

    auto messages = jsonData["messages"];

    int i = 0;
    for (auto message : messages) {
        // roleがuserの場合はcontentを出力
        if (message["role"] == "user") {
            // cout << "user: " << message["content"] << endl;
            if (i == currentCount){
                cout << "user: " << message["content"] << endl;
                input -> value(message["content"].get<string>().c_str());
            }
            i++;
        }
    }

    i = 0;
    for (auto message : messages) {
        // roleがassistantの場合はcontentを出力
        if (message["role"] == "assistant") {
            // cout << "assistant: " << message["content"] << endl;
            if (i == currentCount){
                cout << "assistant: " << message["content"] << endl;

                Fl_Text_Buffer* buffer = new Fl_Text_Buffer();
                buffer -> text(message["content"].get<string>().c_str());
                output -> buffer(buffer);
            }
            i++;
        }
    }

    // 質問回数表示
    string countStr =  to_string(currentCount + 1) + " / " + to_string(countQ);
    cout << "countStr: " << countStr << endl;
    questionNumShow -> value(countStr.c_str());
}

[C++] 282 FLTK : ChatGPTアプリの製作 その11 エスケープシーケンス処理

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

string型をjson型データに変換する際、エスケープシーケンスを適切に前処理しないと変換に失敗します。

そこで前処理するescapeSequence関数を作成しました。内容についてはさらに詰める必要がありますが、今のところ私が使う範囲では正常に動作します。

C++で文字列を扱うと色々と泥沼にハマります。

ようやくOpenAIのアカウント管理ページに課金が反映されました。6日間それなりに使っても0.5ドル(70円)でした。お試し期間としてもらっている18ドルを使い切るのはまだまだ先のようです。値下げ前だったら10倍の700円ということを考えると激安もいいところです。

今日の夕方から急にAPIが重くなったりサーバ内部エラーになるのですが、トラフィックがきつくなってきたのでしょうか。

void escapeSequence(string& str){
    size_t pos = 0;
    while ((pos = str.find("\\", pos)) != string::npos) {
        str.replace(pos, 1, "\\\\");
        pos += 2;
    }

    pos = 0;
    while ((pos = str.find('"', pos)) != string::npos) {
        str.replace(pos, 1, "\\\"");
        pos += 2;
    }

    pos = 0;
    while ((pos = str.find("'", pos)) != string::npos) {
        str.replace(pos, 1, "\'");
        pos += 2;
    }

    pos = 0;
    while ((pos = str.find('\n', pos)) != string::npos) {
        str.replace(pos, 1, "\\n");
        pos += 2;
    }

    pos = 0;
    while ((pos = str.find('\t', pos)) != string::npos) {
        str.replace(pos, 1, "\\t");
        pos += 2;
    }

    pos = 0;
    while ((pos = str.find('\b', pos)) != string::npos) {
        str.replace(pos, 1, "\\b");
        pos += 2;
    }

    pos = 0;
    while ((pos = str.find('\f', pos)) != string::npos) {
        str.replace(pos, 1, "\\f");
        pos += 2;
    }
}
ChatGPT課金額推移

[C++] 281 FLTK : ChatGPTアプリの製作 その10 AIリネーム機能 文字列削除関数

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

AIリネーム機能を改良しました。半角スペースやカギ括弧を削除するための関数を作成しました。この関数は今後も重宝しそうです。

こういった気の利いた関数をChatGPTに書かせようとすると高確率でデタラメを提示してくるので、最初から自分で書く方が速かったりします。完成目前の仕上げはしっかりやってくれることもあります。

ここまで機能を揃えるともうChatGPT Plusを解約しても問題ないでしょう。来週は月更新せずに済みそうです。

void deleteString(string& str, const char* del) {
    size_t size = strlen(del);
    size_t pos = 0;
    while ((pos = str.find(del, pos)) != string::npos) {
        // cout << "pos: " << pos << endl;
        str.erase(pos, size);
    }
}

void renameCB(Fl_Widget*, void*){
    input -> value("これまでの質問回答の内容から15文字程度で日本語タイトルを考えて下さい。英単語が混ざってもかまいません。");
    sendBtn->do_callback();
    
    Fl_Text_Buffer* buffer = output->buffer();
    title = buffer->text();

    cout << "title加工前: " << title << endl;
    
    // 半角スペースと「」を削除
    deleteString(title, " ");
    deleteString(title, "「");
    deleteString(title, "」");

    cout << "title加工後: " << title << endl;

    vector<string> tokens;
    string delimiter = ".";
    size_t pos = 0;
    string token;
    string jsonFileStr = string(jsonFile);
    while ((pos = jsonFileStr.find(delimiter)) != string::npos) {
        token = jsonFileStr.substr(0, pos);
        tokens.push_back(token);
        jsonFileStr.erase(0, pos + delimiter.length());
    }
    tokens.push_back(jsonFile);

    string jsonFileNew = tokens[0] + "_" + title + ".json";

    cout << "jsonFileNew: " << jsonFileNew << endl;

    int result = std::rename(jsonFile.c_str(), jsonFileNew.c_str());
    if (result == 0) {
        std::printf("File renamed successfully.\n");
    } else {
        std::printf("Error renaming file.\n");
    }

    Fl_Text_Buffer* bufferNotice = new Fl_Text_Buffer();
    string notice = "JSONファイルをリネームしました。\n" + jsonFileNew;
    bufferNotice -> text(notice.c_str());
    noticeDisplay -> buffer(bufferNotice);
}

[C++] 280 FLTK : ChatGPTアプリの製作 その9 AIリネーム機能実装

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

せっかくAIを使っているのですから、作成しているJSONファイルの命名を手伝わせています。

指定の文字数は守らない、カギ括弧を付けるなと指示しても付けてくる、など全然ルールを守りませんが最低限のことはやってくれます。

カギ括弧はこちらで削除したいものの、良い方法が分からず模索中です。

void renameCB(Fl_Widget*, void*){
    input -> value("これまでの質問回答の内容から15文字程度で日本語タイトルを考えて下さい。英単語が混ざってもかまいません。");
    sendBtn->do_callback();

    
    Fl_Text_Buffer* buffer = output->buffer();
    title = buffer->text();

    cout << "title加工前: " << title << endl;
    // 先頭の半角スペースを削除する
    title.erase(title.begin(), std::find_if(title.begin(), title.end(), [](char c) {
        return !std::isspace(c);
    }));

    if (title.length() > 0) {
        char c = title[0];
        if ((c >= 0x21 && c <= 0x2F) || (c >= 0x3A && c <= 0x40) || (c >= 0x5B && c <= 0x60) || (c >= 0x7B && c <= 0x7E)|| c == static_cast<char>(0x300C) || c == static_cast<char>(0x300D)) {
            // 先頭の文字が記号である場合
            title.erase(title.begin(), title.begin() + 3);
        }
    }

   if (title.length() > 0) {
        char c = title.back();
        if ((c >= 0x21 && c <= 0x2F) || (c >= 0x3A && c <= 0x40) || (c >= 0x5B && c <= 0x60) || (c >= 0x7B && c <= 0x7E) || c == static_cast<char>(0x300C) || c == static_cast<char>(0x300D)) {
            // 末尾の文字が記号である場合(カギ括弧を含む)
            title.erase(title.end() - 3, title.end());
        }
    }

    cout << "title加工後: " << title << endl;

    vector<string> tokens;
    string delimiter = ".";
    size_t pos = 0;
    string token;
    string jsonFileStr = string(jsonFile);
    while ((pos = jsonFileStr.find(delimiter)) != string::npos) {
        token = jsonFileStr.substr(0, pos);
        tokens.push_back(token);
        jsonFileStr.erase(0, pos + delimiter.length());
    }
    tokens.push_back(jsonFile);

    string jsonFileNew = tokens[0] + "_" + title + ".json";

    cout << "jsonFileNew: " << jsonFileNew << endl;

    int result = std::rename(jsonFile.c_str(), jsonFileNew.c_str());
    if (result == 0) {
        std::printf("File renamed successfully.\n");
    } else {
        std::printf("Error renaming file.\n");
    }

    Fl_Text_Buffer* bufferNotice = new Fl_Text_Buffer();
    string notice = "JSONファイルをリネームしました。\n" + jsonFileNew;
    bufferNotice -> text(notice.c_str());
    noticeDisplay -> buffer(bufferNotice);
}

[C++] 279 FLTK : ChatGPTアプリの製作 その8 コードをFl_Text_Displayに表示・改良

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

ChatGPTから得られた回答を一旦JSONに格納してから取り出すという手順が間違いの元でした。

回答の生データをそのままFl_Text_Displayに表示してから、JSONに格納するようにしました。

これで可読性が大幅に改善しました。

ただJSONに収まった時点でprintf(“%s\n”,str)のようなコード中の改行文字とエスケープシーケンスとしてリテラル化した改行文字が混在してしまうので完全な復元ができない、という問題を抱えたままです。

自分で簡単なパーサー(解析器)を作るしかないのかもしれません。

vector<string> getCode(string str){
    vector<string> codes;
    size_t start_pos = str.find("```");
    while (start_pos != string::npos) {
        size_t end_pos = str.find("```", start_pos + 1);
        if (end_pos != string::npos) {
            code = str.substr(start_pos + 3, end_pos - start_pos - 3);

            size_t start_pos2 = code.find("#");
            code.erase(0, start_pos2);
            codes.push_back(code);
            cout << "getCode内code:\n" << code << endl;

            str.erase(start_pos, end_pos - start_pos + 3);
            cout << "getCode内str:\n" << str << endl;
        }
        start_pos = str.find("```");
    }

    return codes;
}

[C++] 278 FLTK : ChatGPTアプリの製作 その7 コードをFl_Text_Displayに表示

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

これはかなり手こずりました。ChatGPTからは有益なヒントを得られず、自力解決でした。かなり泥臭い手法です。

ChatGPTから受け取った回答をJSON解析するため、改行文字やダブルクォートをリテラルにしているので、これらを元に戻しました。printf(\”%s\n\”, content_cstr);のような置き換える必要のないところまで改行になってしまいますが、これ位はしかたないです。

// code取り出し&表示
    vector<string> codes;
    size_t start_pos = content2.find("```");
    while (start_pos != string::npos) {
        size_t end_pos = content2.find("```", start_pos + 1);
        if (end_pos != string::npos) {
            code = content2.substr(start_pos + 3, end_pos - start_pos - 3);

            size_t start_pos2 = code.find("#");
            code.erase(0, start_pos2);
            codes.push_back(code);
            cout << "code: " << code << endl;

            content2.erase(start_pos, end_pos - start_pos + 3);
            cout << "content2: " << content2 << endl;
        }
        start_pos = content2.find("```");
    }

    Fl_Text_Buffer* bufferCode = new Fl_Text_Buffer();
    string codeStr = codes[0];

    pos = 0;
    while ((pos = codeStr.find("\\n", pos)) != string::npos) {
        codeStr.replace(pos, 2, "\n");
        pos += 2;
    }
    pos = 0;
    while ((pos = codeStr.find("\\n", pos)) != string::npos) {
        codeStr.replace(pos, 2, "\n");
        pos += 2;
    }
    pos = 0;
    while ((pos = codeStr.find('\\', pos)) != string::npos) {
        codeStr.erase(pos, 1);
        pos += 2;
    }

    bufferCode -> text(codeStr.c_str());
    codeDisplay -> buffer(bufferCode);
    codeDisplay -> wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 5);

[C++] 277 FLTK : ChatGPTアプリの製作 その6 JSONをドラッグ&ドロップ

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT Plus, NO IDE]

JSONファイルをGUIにドラッグ&ドロップできるようにしました。

ChatGPTの回答文からコードを取り出すことはできましたが、改行やインデント整形ができません(GUIの左下)。Fl_Text_Displayは文字列リテラルであればコードをきれいに並べて表示できます。文字列変数の場合はどうすればよいのか調査中です。

#pragma once
#include <FLstd.h>
#include "cppstd.h"

class Box : public Fl_Box {
    Fl_Input* input;

    public:
        Box(int, int, int, int, Fl_Input*);
    private:
        int handle(int);
};
#include "Box.h"

Box::Box(int x, int y, int width_input, int height_input, Fl_Input* input) : Fl_Box(FL_NO_BOX, x, y, width_input, height_input, "") 
{
    this->input = input;
}

int Box::handle(int event){
    switch (event) {
        case FL_DND_DRAG:
        case FL_DND_ENTER:
        case FL_DND_RELEASE:
            return 1;
        case FL_PASTE:
            input -> value(Fl::event_text()); 
            input -> textsize(12);

            return 1;
        default:
            return Fl_Box::handle(event);
    }
}