[C++] 309 ファイルの生成日時を文字列にする

[M1 Mac, Monterey 12.6.3, clang 13.0.0, ChatGPT API(gpt-3.5-turbo), NO IDE]

ChatGPTに作ってもらいました。何度かトライしてようやく成功です。

最初の回答を修正させてハマったので、全く別の方法を考えさせました。

一見単純なコードですが、stat構造体を知らないと書けません。

ところでAIの情報を毎日チェックしていますが、ChatGPTの優秀さが際立つばかりといった印象です。

AI開発停止運動とやらは、OpenAI始め先行開発群に追いつけそうもない焦りの現れでしょうか。

MDファイルをJSONに変換して生成日時をファイル名に追加
#include <sys/stat.h>

string getCreationTime(const char* filePath) {
    struct stat attr;
    if (stat(filePath, &attr) == -1) {
        return "";
    }

    char buffer[16];
    strftime(buffer, sizeof(buffer), "%y%m%d_%H%M%S", localtime(&attr.st_birthtimespec.tv_sec));

    return buffer;
}

[C++] 308 FLTK : ChatGPTアプリの製作 その35 md→json変換 その2

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

昨日ChatGPTに書いてもらったコードを叩き台にして手を入れ主要な部分を作成しました。

細かいところで色々ややこしかったので、coutデバッグの痕跡を残したままアップしておきます。前半のwhile文はこちらの意図が伝わってなかったのか大分修正しました。後半のfor文が完璧だったのはさすがです。

あとはmdファイルの生成日時をファイル名の先頭に付けたら完成になります。

これでiPadやスマートフォンでのやりとりもChatGPTアプリで閲覧できるようになります。

変換jsonファイルの内容確認
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <nlohmann/json.hpp>

using namespace std;
using json = nlohmann::json;

int main() {
    ifstream ifs("test.md");
    string line;
    vector<string> user_messages;
    vector<string> assistant_messages;
    bool is_user = false;
    string assistant_msg;
    string user_msg;

    cout << "初期化通過" << endl;

    while (getline(ifs, line)) {
        cout << "while通過" << endl;

        if (line.empty()) {
            if (is_user) {
                is_user = false;
            }
            cout << "empty通過" << endl;
        } else if (line[0] == '>') { // user
            if (assistant_msg != ""){
                assistant_messages.push_back(assistant_msg);
                cout << "assistantベクター追加" << endl;
                assistant_msg = "";
            }

            is_user = true;
            cout << "user : " << line.substr(2) << endl;
            string user_msg = line.substr(2) + "\n";
            user_messages.push_back(user_msg);
        } else {
            if (is_user) {
                cout << "user2行目以降 : " << line << endl;
                user_msg += line + "\n";
                cout << "user文字列追加" << endl;

            } else {
                if (user_msg != ""){
                    user_messages.push_back(user_msg);
                    cout << "userベクター追加" << endl;
                    user_msg = "";
                }

                cout << "assistant : " << line << endl;
                assistant_msg += line + "\n";
                cout << "assistant文字列追加" << endl;
            }
        }
    }
    assistant_messages.push_back(assistant_msg);
    cout << "assistantベクター最終追加" << endl;

    cout << "getline通過" << endl;

    json root;
    json messages = json::array();
    for (int i = 0; i < user_messages.size(); i++) {
        json message;
        message["content"] = user_messages[i];
        message["role"] = "user";
        messages.push_back(message);
        message["content"] = assistant_messages[i];
        message["role"] = "assistant";
        messages.push_back(message);
    }
    root["messages"] = messages;
    root["model"] = "gpt-3.5-turbo";
    root["temperature"] = 0.0;

    ofstream ofs("output.json");
    ofs << root.dump(4);

    return 0;
}

[C++] 307 FLTK : ChatGPTアプリの製作 その34 md→json変換

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

iPadでChatGPTを使う場合は公式Webを使います。そこでのやりとりはWebラッパーでmdファイルとしてダウンロードします。

そのmdファイルを自製ChatGPTアプリでjsonファイルに変換できるよう開発を進めています。

最初はRustで製作されたWebラッパーを改変してjsonとしてダウンロードできるようにしようかとも考えたんですが、Rustで書くとJavaのヌルポ攻勢と同様の例外処理対応に追われるのが目に見えているので、やめておくことにしました。

取りあえずChatGPTに叩き台のコードを書いてもらいました。

ところでGPT-4のwaitlistに登録して2週間が経とうとしていますが、まだAPIで使えません。やはり重課金ユーザー優先ということでしょう。当方かなり使ったつもりでもまだ供与された18ドルに遠く及ばない2.8ドルです。

RWKVなど有望なLLMが話題に上がっていて、いよいよローカルChat Botの実現が見えてきました。今のところRTX3080位ないと厳しいらしく、どうするか考えています。

ただTwitterを見る限りキャッチアップしているのは画像生成AI界隈の方ばかりで、その分野に全く興味がない者としてそろそろ潮時とも思えます。プログラマ系の方々は水面下に潜った感がありますね。そりゃTwitterばかり見てたらコードもろくに書けません。

ChatGPTアプリ製作開始から26日経過し、平常モードに戻るタイミングかもしれません。

Q:
以下のコードをC++で書いて下さい。
mdファイルをjsonファイルに変換する。
1.mdファイル
不等号の行はrole:userのcontent
改行コードが2回続き、次の行はrole:assistantのcontent
2.jsonファイル
keyはmessages, model, temperatureの3つ。
modelのvalueはgpt-3.5-turbo
temperatureのvalueは0.0
jsonの例は以下の通り
{
“messages”: [
{
“content”: “”,
“role”: “system”
},
{
“content”: “FLTKのプログラムがFl_Buttonを自動クリックするコードを書いて下さい”,
“role”: “user”
},
{
“content”: “以下は、FLTKのプログラムでFl_Buttonを自動クリックするコードの例です。\n\nc++\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n#include <FL/Fl_Button.H>\n\nint main(int argc, char **argv) {\n Fl_Window *window = new Fl_Window(200, 200, \"Auto Click\");\n Fl_Button *button = new Fl_Button(50, 50, 100, 50, \"Click Me\");\n\n // 自動クリックする\n button->do_callback();\n\n window->end();\n window->show(argc, argv);\n return Fl::run();\n}\n\n\nこの例では、Fl_Buttonオブジェクトのdo_callback()メソッドを呼び出すことで、ボタンを自動的にクリックしています。このメソッドは、ボタンがクリックされたときに呼び出されるコールバック関数を実行します。”,
“role”: “assistant”
},
{
“content”: “これまでの質問回答の内容から15文字程度で日本語タイトルを考えて下さい。英単語が混ざってもかまいません。”,
“role”: “user”
},
{
“content”: “FLTKで自動クリックする方法”,
“role”: “assistant”
}
],
“model”: “gpt-3.5-turbo”,
“temperature”: 0.0
}

A:
以下がC++でのコード例になります。

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main() {
    // mdファイルを読み込む
    std::ifstream ifs("1.md");
    std::string md((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());

    // mdファイルを改行コードで分割する
    std::vector<std::string> lines;
    std::string line;
    std::istringstream iss(md);
    while (std::getline(iss, line)) {
        lines.push_back(line);
    }

    // jsonオブジェクトを作成する
    json j;
    j["messages"] = json::array();
    j["model"] = "gpt-3.5-turbo";
    j["temperature"] = 0.0;

    // mdファイルの内容をjsonオブジェクトに追加する
    std::string role = "user";
    for (const auto& line : lines) {
        if (line.empty()) {
            role = "assistant";
            continue;
        }
        j["messages"].push_back({
            {"content", line},
            {"role", role}
        });
    }

    // jsonファイルに書き込む
    std::ofstream ofs("2.json");
    ofs << j.dump(4) << std::endl;

    return 0;
}

このコードでは、まずmdファイルを読み込み、改行コードで分割して各行を処理します。次に、jsonオブジェクトを作成し、mdファイルの内容をjsonオブジェクトに追加します。最後に、jsonファイルに書き込みます。

[C++] 306 FLTK : 文字列をユニコードに変換 漢字変換修正 UTF-16

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

前回の記事で漢字変換がおかしかった所を修正しました。

UTF-8のままではマルチバイト文字を1つのユニコードとして認識できないため、UTF-16に変換する必要があります。

wstring_convertクラスを使ってUTF-8からUTF-16に変換しています。

#include <iostream>
#include <string>
#include <codecvt>

void convertCB(Fl_Widget*, void*) {
    std::stringstream unicode;
    const char* chr = convertInput -> value();

    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
    std::wstring unicode_str = converter.from_bytes(chr);
    for (wchar_t c : unicode_str) {
        unicode << std::hex << std::uppercase << static_cast<int>(c) << " ";
    }

    browser->add((unicode.str()).c_str());
}

[C++] 305 FLTK : 文字列をユニコードに変換 

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

自製アプリに文字列をユニコードに変換する機能を実装しました。

下図のようにファイル名に改行コードLFが混入していることが分かります。

漢字の変換がおかしいですが、後日修正します。

void convertCB(Fl_Widget*, void*) {
    std::stringstream unicode;
    const char* chr = convertInput -> value();
    for (int i = 0; chr[i] != '\0'; i++) {
        unicode << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)chr[i];
    }

    browser->add((unicode.str()).c_str());
}

[C++] 304 FLTK : ChatGPTアプリの製作 その33 Finderの異常表示原因判明

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

前回の続きです。

Finderのファイル名に?が紛れ込む原因は改行コードの混入でした。

Commander Oneでファイル情報を見てみるとファイル名のところに改行が入っていました。

ChatGPTアプリで返信された回答をGUIに表示する際、コピペがしやすいように文末に改行コードを追加したことが原因でした。

Finderを悪者扱いしてしまい反省です。ChatGPTからも特殊文字が混ざっているとその部分は?になるという指摘がありましたが、正にその通りでした。ファイル名をユニコードU+16進数(U+XXXX)表記させればすぐに分かっていたでしょう。

ただCommander Oneのinfoでようやく分かったように、Finderの方でも改行コードが何らかの形で反映されるように改良していただきたいものです。

この件は解決した一方、.DS_Storeファイルによるmake妨害の件については現在とある検証を実施中です。妨害回避に成功したら報告します。

Commander Oneのinfo : ファイル名が.jsonの手前で改行されている
// 最新回答contentをoutputに表示
Fl_Text_Buffer* buffer = new Fl_Text_Buffer();
output -> buffer(buffer);
buffer -> append(content0.c_str());
buffer -> append("\n");             // ここが余計だった
output -> wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 5);

[C++] 303 FLTK : ChatGPTアプリの製作 その32 Finderの不具合発覚?

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

ChatGPTアプリでファイル名を色々加工していると?や^Jのような意図しない文字列が紛れ込むようになりました。

C++はUTF-8の扱いが苦手なのでまたかと思いつつコードを修正したものの、どうやっても直りません。

どうやらFinderの不具合のようです。バグまみれだとは思っていましたが、まさかここまでひどいとは。

普段からSpotlight用の.DS_Storeファイルにmakeコマンドの邪魔をされていることも併せてさすがに腹が立ちました。一応ChatGPTを通して弁明を聞いておきました。

ファイル名の最後に?が付くようになった
Commander Oneでは正常に表示される
ChatGPTの意見

[C++] 302 FLTK : ChatGPTアプリの製作 その31 ChatGPTを使って関数作成

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

ChatGPTにファイル名に任意の文字列を含むものを移動させる関数や削除する関数の中身を作ってもらいました。たった20秒で答えが返ってきます。引数はこちらで考えて完成させました。

汎用性の高い関数を量産することが可能になりました。ネットのレファレンスや逆引き辞典を使うよりもChatGPTに作らせる方が断然速いです。

namespace fs = std::filesystem;

void moveFile(fs::path srcDir, fs::path destDir, const char* str){
    try {
        // 移動元のディレクトリ内のファイルを順に処理
        for (const auto& entry : fs::directory_iterator(srcDir)) {
            // ファイル名にstrを含む場合は移動
            if (entry.path().filename().string().find(str) != std::string::npos) {
                fs::rename(entry.path(), destDir / entry.path().filename());
                std::cout << entry.path() << "を移動しました。" << std::endl;
            }
        }
    } catch (const std::filesystem::filesystem_error& e) {
        std::cerr << "ファイルの移動に失敗しました。" << std::endl;
        std::cerr << e.what() << std::endl;
        return;
    }
    
}

void removeFile(fs::path dirPath, string str){
     try {
        // ディレクトリ内のファイルを順に処理
        for (const auto& entry : fs::directory_iterator(dirPath)) {
            // ファイル名にstrを含む場合は削除
            if (entry.path().filename().string().find(str) != std::string::npos) {
                fs::remove(entry.path());
                std::cout << entry.path() << "を削除しました。" << std::endl;
            }
        }
    } catch (const std::filesystem::filesystem_error& e) {
        std::cerr << "ファイルの削除に失敗しました。" << std::endl;
        std::cerr << e.what() << std::endl;
        return;
    }
}

[C++] 301 FLTK : ChatGPTアプリの製作 その30 CSVデータのグラフ化改良2

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

ChatGPT APIの回答時間推移グラフにY軸の補助線とX2軸、Y2軸を追加しました。

過去1週間のデータをグラフ化しています。Y軸補助線は24時間刻みです。

これでグラフの形式は完成といったところでしょうか。

2Dグラフィックスライブラリでグラフを描くのが徐々に楽しくなってきました。

FLTKにもFl_Chartというグラフ描画クラスがあるのですが、xyプロットの出来ない簡易版でした。

cairo以外の2Dライブラリを聞いてみたが、QtとSDLは既知だった。
// データを描画する関数
void draw_data(cairo_t* cr, vector<vector<double>> data, double x_min, double x_max, double y_min, double y_max, double width, double height) {
    // 背景色を白に設定
    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_paint(cr);

    // 折れ線の幅を設定
    cairo_set_line_width(cr, 2);

    // グラフの周囲に20pixelの余白を設ける
    double x_margin = 20;
    double y_margin = 20;

    // 7*24時間より前のデータを削除
    time_t current_time = time(NULL);
    time_t threshold_time = current_time - 7 * 24 * 3600;
    vector<vector<double>> filtered_data;
    for (int i = 0; i < data.size(); i++) {
        if (data[i][0] >= threshold_time) {
            filtered_data.push_back(data[i]);
        }
    }
    data = filtered_data;

    // x_minをx_maxの7*24時間前とする
    x_min = x_max - 7 * 24 * 3600;

    // x軸とy軸のスケールを計算
    double x_scale = (width - 2 * x_margin) / (x_max - x_min);
    double y_scale = (height - 2 * y_margin) / 30;

    // x軸の描画と目盛り
    cairo_set_source_rgb(cr, 0, 0, 0); // 黒色に設定
    cairo_move_to(cr, x_margin, height - y_margin);
    cairo_line_to(cr, width - x_margin, height - y_margin);
    cairo_stroke(cr);

    cairo_select_font_face(cr, "Arial", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size(cr, 10);

    int num_ticks = 7; // 1目盛り24時間
    double x_tick_interval = (x_max - x_min) / num_ticks;
    for (int i = 0; i <= num_ticks; i++) {
        double x = x_margin + i * x_tick_interval * x_scale;
        cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); // 灰色に設定
        cairo_move_to(cr, x, height - y_margin + 5);
        cairo_line_to(cr, x, y_margin);
        cairo_set_line_width(cr, 0.5);
        cairo_stroke(cr);

        time_t t = x_min + i * x_tick_interval;
        struct tm* timeinfo = localtime(&t);
        char buffer[80];
        strftime(buffer, 80, "%H:%M", timeinfo); // hh:mm形式に変更
        cairo_text_extents_t extents;
        cairo_text_extents(cr, buffer, &extents);
        cairo_move_to(cr, x - extents.width / 2, height - y_margin + 15);
        cairo_set_source_rgb(cr, 0, 0, 0); // 黒色に設定
        cairo_show_text(cr, buffer);
    }

    // y軸の描画と目盛り
    int num_ticksY = 6;
    cairo_set_source_rgb(cr, 0, 0, 0); // 描画色を黒色に設定します
    cairo_move_to(cr, x_margin, height - y_margin); // 描画開始位置を指定します
    cairo_line_to(cr, x_margin, y_margin); // 描画終了位置を指定します
    cairo_set_line_width(cr, 1);
    cairo_stroke(cr); // 描画を行います

    // y軸の目盛りに関する設定を行います
    double y_tick_interval = 5; // 目盛りの間隔を指定します
    for (int i = 0; i <= num_ticksY; i++) { // 目盛りの数だけループします
        double y = height - y_margin - i * y_tick_interval * y_scale; // 目盛りの位置を計算します
        cairo_move_to(cr, x_margin, y); // 描画開始位置を指定します
        cairo_line_to(cr, x_margin - 5, y); // 描画終了位置を指定します
        cairo_set_line_width(cr, 0.5);
        cairo_stroke(cr); // 描画を行います

        char buffer[80]; // 文字列を格納するためのバッファを用意します
        sprintf(buffer, "%d", i * 5); // 目盛りの値を文字列に変換します
        cairo_text_extents_t extents; // テキストの描画範囲を格納するための変数を用意します
        cairo_text_extents(cr, buffer, &extents); // テキストの描画範囲を計算します
        cairo_move_to(cr, x_margin - extents.width - 10, y + extents.height / 2); // テキストの描画位置を指定します
        cairo_show_text(cr, buffer); // テキストを描画します
    }

    // X2軸の描画
    cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); // 灰色に設定
    cairo_set_line_width(cr, 0.5);
    cairo_move_to(cr, x_margin, y_margin);
    cairo_line_to(cr, width - x_margin, y_margin);
    cairo_stroke(cr);

    // Y2軸の描画
    cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); // 灰色に設定
    cairo_set_line_width(cr, 0.5);
    cairo_move_to(cr, width - x_margin, height - y_margin);
    cairo_line_to(cr, width - x_margin, y_margin);
    cairo_stroke(cr);

    // 折れ線を描画
    cairo_set_source_rgb(cr, 1, 0, 1); // マゼンタに設定
    cairo_set_line_width(cr, 1);
    cairo_move_to(cr, x_margin, height - y_margin - (data[0][1]) * y_scale);
    for (int i = 1; i < data.size(); i++) {
        double x = x_margin + (data[i][0] - x_min) * x_scale;
        double y = height - y_margin - (data[i][1]) * y_scale;
        cairo_line_to(cr, x, y);
    }
    cairo_stroke(cr);

    // yの最大値と最小値の表示
    cairo_select_font_face(cr, "Arial", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size(cr, 12);
    char buffer[80];
    sprintf(buffer, "y_min: %.2f", y_min);
    cairo_text_extents_t extents;
    cairo_text_extents(cr, buffer, &extents);
    cairo_move_to(cr, x_margin + 5, height - y_margin - y_min * y_scale);
    cairo_set_source_rgb(cr, 0, 0.5, 0); // 濃緑に設定
    cairo_show_text(cr, buffer);

    sprintf(buffer, "y_max: %.2f", y_max);
    cairo_text_extents(cr, buffer, &extents);
    cairo_move_to(cr, x_margin + 5, height - y_margin - y_max * y_scale);
    cairo_set_source_rgb(cr, 0, 0.5, 0); // 濃緑に設定
    cairo_show_text(cr, buffer);
}

[C++] 300 FLTK : ChatGPTアプリの製作 その29 タイムアウト設定

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 1.3.8, ChatGPT API(gpt-3.5-turbo), NO IDE]

一昨日日曜あたりからChatGPT APIに質問しても反応しないケースがかなり増えました。

curl sessionにおいてタイムアウト時間を120秒に設定して対応しました。

いよいよパンク状態に差し掛かっているものと思われます。

// Set up a curl session
    CURL* curl = curl_easy_init();
    if (curl) {
        // Set the request options
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestData.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);

        // Set timeout options
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L); // 120秒でタイムアウト
        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 120L);

        // Send the request
        CURLcode res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << endl;

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

            return {};
        }

        // Clean up
        curl_easy_cleanup(curl);

    } else {
        cerr << "curl_easy_init() failed" << endl;

        const char* error = "curl_easy_init() failed";
        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 {};
    }