2025年3月12日水曜日

APEXアプリケーションに入力したメッセージをOpen WebUIに送信する

以前の記事「macOSのpodmanでOpen WebUIのコンテナを実行しAPEXのアプリの非モーダル・ダイアログとして開く」にて、APEXアプリケーションに非モーダル・ダイアログのページとしてOpen WebUIを組み込んでいます。

今回は、このAPEXアプリケーションに入力したメッセージをOpen WebUIに送り、Open WebUIから生成AIを呼び出してみます。

APEXアプリケーションは以下のように動作します。


最初にAPEXアプリケーションとOpen WebUIを同一オリジンに見せるために、両方のサーバーへのアクセスをまとめるリバース・プロキシを配置します。リバース・プロキシにはnginxを使用します。

macOS上のpodmanで環境を構築します。以下のような構成になります。APEXアプリケーション、Open WebUIともに、http://localhost:8822で接続を待ち受けているnginxに接続し、nginxがOpen WebUIをhttp://host.containers.internal:3000へ、APEXアプリケーションをhttp://host.containers.internal:8181へ振り分けます。


以前の記事に従って、Open WebUIやOracle APEXのコンテナは作成済みというところから作業を始めます。

nginxのコンテナを作成します。nginxはポート8822で待ち受けするように構成するため、ポート8822をホストにマップします。

podman run -p 8822:8822 --name nginx -d nginx:alpine

% podman run -p 8822:8822 --name nginx -d nginx:alpine


Resolving "nginx" using unqualified-search registries (/etc/containers/registries.conf.d/999-podman-machine.conf)

Trying to pull docker.io/library/nginx:alpine...

Getting image source signatures

Copying blob sha256:76f8ad18306ed8a7757d73f845d89b3aee98b4774af2b7c5c9a288bcf097036c

Copying blob sha256:0be31969a6d1e1f2699d0fb5a48f1b4da62a3b2aefa8a9b0dfb548bc497bf321

Copying blob sha256:6e771e15690e2fabf2332d3a3b744495411d6e0b00b2aea64419b58b0066cf81

Copying blob sha256:83f1386059fa17276f4f0e676f45370a63667006c4d8e514298f71c6639328ea

Copying blob sha256:8ea77ffafa6ef989339c5c391fb30729e3f148240c0834f1db148158ab32f657

Copying blob sha256:9e170776f94c97f95260908e78676599218d0247c22929326f7072689b832bbb

Copying blob sha256:3c5c25f3816a63c8239c4a853ae22036406ffbd4f55b16e29e01f941f2dea356

Copying blob sha256:0a329c61f9e1dc20260b65b0ef67440ced65fbf671c1e93df0a9710fbb42d536

Copying config sha256:cedb667e1a7b4e6d843a4f74f1f2db0dac1c29b43978aa72dbae2193e3b8eea3

Writing manifest to image destination

aa331c3fdd940c53ae85b90ec874bd6bbf85aaaccf3e80474c8ad0b993a23cf6

% 


リバース・プロキシを構成します。コンテナnginxに接続します。

podman exec -it nginx sh

% podman exec -it nginx sh

/ # 


/etc/nginx/conf.d/default.confを以下の内容に置き換えます。

server {
listen 8822;
listen [::]:8822;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
#
# Redirect to Open WebUI
# Ref: https://github.com/open-webui/open-webui/discussions/1235
#
location / {
proxy_pass http://host.containers.internal:3000;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Accept-Encoding "";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# sub_filter '"/_app/' '"/openwebui/_app/';
# sub_filter '"/themes/' '"/openwebui/themes/';
# sub_filter_once off;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
#
# redirect to Oracle APEX
# Ref
# https://blog.viscosityna.com/using-nginx-as-a-reverse-proxy-for-oracle-apex-and-ords
# https://medium.com/@ppytlak.dev/reverse-proxy-with-oracle-apex-free-tier-and-nginx-on-mikr-us-858c30d1961e
# https://jmjcloudblog.hashnode.dev/access-atp-apex-using-a-reverse-proxy
# https://content.dsp.co.uk/apex/apex-behind-a-custom-domain-using-oracle-cloud-and-nginx
# https://gielis1.rssing.com/chan-7657097/article208-live.html
#
location /ords/ {
proxy_pass http://host.containers.internal:8181/ords/;
proxy_http_version 1.1;
proxy_set_header Origin "";
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
}
}
view raw default.conf hosted with ❤ by GitHub

/ # cd /etc/nginx/conf.d

/etc/nginx/conf.d # ls default.conf 

default.conf

/etc/nginx/conf.d # vi default.conf

default.confの内容を置き換える

/etc/nginx/conf.d # exit

% 


nginxのコンテナを再起動します。

% podman restart nginx

nginx

% 


以上でnginxがリバース・プロキシとして動作するようになりました。ORDSのページに接続します。

http://localhost:8822/ords/


Open WebUIには、以下のURLで接続できます。APEXとOpen WebUIが同一オリジンになっています。

http://localhost:8822/


記事の先頭のGIF動画で使用しているAPEXアプリケーションのエクスポートです。
https://github.com/ujnak/apexapps/blob/master/exports/open-webui.zip

Oracle APEXのページからOpen WebUIのiframeへメッセージを送信する実装について、以下より紹介します。

APEXのページでメッセージを入力するために、テキスト領域のページ・アイテムを作成しています。

識別名前P1_TEXTタイプテキスト領域です。ラベルTextとしています。セッション・ステートデータ型VARCHAR2ストレージリクエストごと(メモリーのみ)としています。


ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言に以下を記述し、Open WebUIが埋め込まれたページと通信するためのブロードキャスト・チャンネルsubmit-messageを作成します。
const channel = new BroadcastChannel('submit-message');

ページ・アイテムP1_TEXTに入力した文字列をブロードキャスト・チャンネルに送信するボタンSUBMITを作成します。ボタンをクリックしたときに、動的アクションとして以下のJavaScriptコードを実行します。
const chatMessage = apex.item("P1_TEXT").getValue();
channel.postMessage( { message: chatMessage } );

以上でメッセージを送信する実装は完了です。

Open WebUIをiframeとして埋め込んでいるページに移ります。

まず、Open WebUIをiframeとして埋め込んでいるリージョンの属性を開き、設定URLhttp://localhost:8822に変更します。埋め込むURLをリバース・プロキシに変更することにより、Open WebUIがAPEXアプリケーションと同一オリジンでiframeに埋め込まれます。


ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言で、APEXアプリケーションとの通信に使用するブロードキャスト・チャンネルsubmit-messageを作成します。
const channel = new BroadcastChannel('submit-message');
ページ・ロード時に実行に以下を記述し、ブロードキャスト・チャンネルより受信したメッセージをiframe内のOpen WebUIのメッセージを入力する領域にコピーします。その後1秒待って、ボタンsend-message-buttonをクリックします。ボタンsend-message-buttonはメッセージ領域にテキストを入力した後に生成されるようで、テキスト入力後に少し待機する必要があります。
channel.addEventListener("message", (event) => {
    console.log(event.data.message);
    // iframe
    const iframe = document.querySelector('iframe');
    const input = iframe.contentDocument.getElementById("chat-input");
    input.innerText = event.data.message;

    // send-message-buttonが表示されるまで待つ。
    setTimeout(() => {
        const button = iframe.contentDocument.getElementById("send-message-button");
        button.click();
    }, 1000);
});

以上で、メッセージの送信側と受信側の実装は完了です。アプリケーションを実行すると、記事の先頭のGIF動画のように操作できます。

今回の記事は以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。