2025年2月5日水曜日

ユニバーサル・テーマ24.2の変更点を確認する

Oracle APEX 24.2のリリースに伴い、バンドルされているユニバーサル・テーマが24.2になりました。主にCSSのユーティリティ・クラスが追加されていますが、これらのCSSのクラスの解説というよりは、追加されたクラスや機能の説明の見つけ方を紹介します。

最新のユニバーサル・テーマのリファレンスは、以下より参照できます。



ユニバーサル・テーマのリファレンスは、APEXアプリケーションです。このAPEXアプリケーションはサンプル・アプリケーションとして提供されているため、ギャラリよりローカルの環境にインストールすることもできます。


apex.oracle.com上のユニバーサル・テーマのリファレンスでは、Theme Versionより過去のユニバーサル・テーマのリファレンスを参照することができます。


サンプル・アプリケーションに含まれるユニバーサル・テーマのリファレンス・アプリケーションには、そのOracle APEXのバージョンと同じバージョンのユニバーサル・テーマのリファレンスだけが含まれます。

今回の記事では、apex.oracle.comのリファレンスより、ユニバーサル・テーマ24.2と24.1の差分を確認します。

最初にChange Logを確認します。


Change Logに記載されている、新しいユーティリティ・クラスが本記事の紹介範囲です。


ユニバーサル・テーマの(CSS)ユーティリティ・クラスの説明は、Referenceに含まれています。


Color, Status, and Style ModifiersとしてShadow Utility Classが追加されています。


リージョンに影をつけるクラスです。


Layout Modifiersとして、フレックス・ボックス内での表示順序を決めるクラスが追加されています。


順序を指定するu-order-0, u-order-1といったクラスは、以前のバージョンでもありました。ユニバーサル・テーマ24.2では、viewportのサイズ(つまり画面の横幅)に依存して表示順序を指定するクラスが追加されています。


Marginを指定するクラスに変更および追加があります。

ユニバーサル・テーマ24.2以前では、マージンを指定するCSSクラス名はmarginで始まっていました。このCSSクラス名はu-marginで始まるように変わります。ユニバーサル・テーマ24.2でもmarginで始まるCSSクラスは使えるようですが、できるだけu-marginで始まるクラス名を使うべきです。

その他に以下の変更があります。
  1. Directionblockinlineが追加されました。
  2. Spacingxlが追加されました。
  3. クラスとしてu-margin-xlが追加されました。
  4. margin-autoのクラス名がu-margin-inline-autoに変更されました。

Content Modifiersとして沢山のクラスが追加されました。


Headingsを指定するクラスが追加されました。

CSSクラス名はu-text-headingで始まるか、u-text-titleまたはu-text-subtitleで始まります。


Contentとして本文の文字サイズを指定するクラスが追加されました。

CSSクラス名はu-text-bodyで始まるか、u-text-titleまたはu-text-captionで始まります。


Text Transformのクラスが追加されました。

u-textUpperと同じ効果のu-text-uppercaseu-textLowerと同じ効果のu-text-lowercaseです。

u-textStart、u-textCenter、u-textEnd、u-textInitCapについては、u-text-で始まるクラス名を割り当てていないため、u-textUpperやu-textLowerがAlignmentと混同されるのを嫌ったように思われます。


Text Styleを指定するクラスが追加されました。


一般に英字を対象とした文字スタイルを日本語の文章に適用しても、英字ほどスタイルに違いが出ません。実際に確認すると、以下のようなスタイルで文章が表示されました。


Line Clamp, Truncate & Hyphenateを指定するクラスが追加されました。

u-lineclamp-1などのクラスは以前のバージョンでもありました。今回の追加は、u-hyphenateu-hyphen)とu-truncateになります。


u-hypheateではCSSのhyphens属性としてautoが設定されています。これは日本語の文章には効きません。u-truncateでは-webkit-line-clamp属性を使わず、単一行の省略を適用します。


Colorsを指定するクラスが追加されました。


色については、もともと英文でも微妙な違いしかなかったので、日本語も同様でした。


ユニバーサル・テーマのリファレンスにはCSS Variablesのセクションがあります。しかし、実際には、あまりユニバーサル・テーマで使用されているCSS変数を網羅していません。


ユニバーサル・テーマのCSS変数のリファレンスとして、以下のページが参照できます。Oracle APEXのインストール・メディアに含まれているファイルなので、CDN上のファイルを参照するときは、パスにパッチ番号が入ります。


CoreおよびTheme StyleごとのCSS変数を確認できます。


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

2025年2月4日火曜日

APEX 24.2の複数選択とポップアップLOVの機能追加について

Oracle APEX 24.2では、ページ・アイテムのタイプの複数選択ポップアップLOVに機能が追加されました。
  • APEX 24.2の複数選択では、選択された項目の個数が表示されるようになりました。
  • APEX 24.2のポップアップLOVでは検索の設定が変更され、1つ選択複数選択およびコンボボックスと同じ設定になりました。
APEX 24.2では以下のように動作します。APEX 24.2では国旗のアイコンが追加されているため、国の選択時に表示できます。複数選択で数値が表示されています。また、ポップアップLOVは、検索が複数選択と同じくらい速くなっています。


APEX 24.1では、同様に作成したアプリケーションでも国旗がない分、寂しい感じがします。複数選択では数値は表示されず、ポップアップLOVの検索も若干遅いです。


以下に確認に使用したアプリケーションの作成手順と、APEX 24.2で変更された設定を紹介します。

アプリケーションが使用するデータ・ソースとして、サンプル・データセットをインストールします。


複数選択ポップアップLOVで使用するLOVを、共有コンポーネントとして作成します。

新規に空のAPEXアプリケーションを作成し、共有コンポーネントLOVを開きます。


作成をクリックします。


LOVの作成最初からとします。へ進みます。


名前LOV_COUNTRIESとします。ソースとしてSQL問合せを設定する予定なので、タイプDynamicです。

へ進みます。


ソース・タイプSQL問合せを選択し、SQL SELECT文を入力に以下を記述します。列REGION_NAMEグループに使用します。列ICONは国旗のアイコンですが、APEX 24.2でのみ意味を持ちます。
select
    c.country_id
    , c.name country_name
    , c.country_code
    , c.iso_alpha2
    , c.capital capital_name
    , c.population
    , r.name region_name
    , s.name sub_region_name
    , 'fa-flag-' || lower(c.iso_alpha2) icon
from eba_countries c join eba_country_regions r on c.region_id = r.id
     join eba_country_sub_regions s on c.sub_region_id = s.id
へ進みます。


戻り列COUNTRY_ID表示列COUNTRY_NAMEです。グループアイコンは、後で追加表示列として設定する必要があります。

以上で作成します。


作成されたLOV_COUNTRIESを開きます。


列のマッピンググループREGION_NAMEアイコンICONを割り当てます。

追加表示列列の選択をクリックします。


グループアイコンなど、列のマッピングに含めた列は必ず追加表示列に含めます。今回の例ではREGION_NAMEICONです。

更新をクリックします。


REGION_NAMEICONグループアイコンとして使用するため追加表示列に含めますが、表示や検索の対象にはしません。表示可能検索可能については、いいえにします。

以上で変更の適用をクリックします。


LOVの準備ができました。


最初に複数選択の機能拡張について確認します。


APEX 24.2では、値の表示形式カンマ区切りリストのとき、選択されている数が表示されるようになりました。


数をクリックすると、選択されている要素が表示されます。


通常は、設定デフォルトの使用オンでも、この数値は表示されます。


これは、共有コンポーネントコンポーネント設定にて、複数選択のデフォルトが設定されているためです。


コンポーネント設定複数選択を開きます。


設定値の表示方式カンマ区切りリストになっていれば、デフォルトでこのAPEX 24.2の新機能が有効になります。


APEX 24.2のポップアップLOVには、プロパティに検索の項目が新設されました。


ポップアップLOVより後に追加されたタイプである、コンボボックス1つ選択および複数選択は、すべてプロパティとして検索を持っています。

以下は、複数選択の例です。


ポップアップLOVについても、検索の設定はこれらの新しいコンポーネントと同じになりました。

そのためAPEX 24.2以前にあった、入力時に検索は無くなりました。


代わりに、他のコンポーネントと同じく検索時にフェッチが追加されています。


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

簡単なアプケーションですが、今回作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/apex242-new-feature-select-many-popup-lov.zip

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

APEXアプリケーションからVOICEVOXエンジンを呼び出し「ずんだもん」の声で音声合成する

先日作成したDeepSeek-R1を呼び出して小説を生成するAPEXアプリケーションに、小説の読み上げ機能を追加してみました。VOICEVOXエンジンのコンテナで動作するHTTPサーバーに、Oracle APEXが動作しているデータベース・サーバーからリクエストを発行し音声合成をします。

以下の記事を参考にしています。

日経クロステック 2025年1月31日NIFTY engineering blog
社内Webアプリでもずんだもんに喋ってほしいのだ

本記事ではフロントエンドはJavaScript、バックエンドはPL/SQLでの実装になるため、これらの記事のコードを流用しているということはありませんが、VOICEVOXと「ずんだもん」の使い方について参考にさせていただきました。

Oracle APEXのアプリケーションから音声合成を呼び出す手順としては、以前に以下の2本の記事を書いています。概ね似たような実装になっています。

OpenAIのText to speech APIを呼び出して写真の説明を読み上げる

記事「APEX 24.2のデータ・モデルの生成をLM StudioとDeepSeek R1 Distill Llama 70Bで実行する」で作成したAPEXアプリのページ番号2に、以下の小説読み上げ機能を追加します。

読み上げ対象の小説から推論過程にあたる<think>...</think>を削除するスイッチ、小説に書かれている文章からWAVファイルを生成する読み上げるボタン、生成されたWAVファイルを再生するオーディオ・コントールをページに配置します。


MシリーズのMacbook Proのpodmanを使って、VOICEVOXエンジンのコンテナを実行します。

podman run --rm -p 50021:50021 voicevox/voicevox_engine:cpu-latest

VOICEVOXエンジンのコンテナを実行すると、利用規約が表示されます。コマンドを一行実行するだけで、音声合成を行うREST APIを呼び出すことができるようになります。

% podman run --rm -p 50021:50021 voicevox/voicevox_engine:cpu-latest

+ cat /opt/voicevox_engine/README.md

# VOICEVOX エンジン利用規約


## 許諾内容


1. 商用・非商用問わず利用することができます

2. アプリケーションに組み込んで再配布することができます

3. 作成された音声を利用する際は、各音声ライブラリの規約に従ってください

4. 作成された音声の利用を他者に許諾する際は、当該他者に対し本許諾内容の 3 及び 4 の遵守を義務付けてください


## 禁止事項


- 逆コンパイル・リバースエンジニアリング及びこれらの方法の公開すること

- 製作者または第三者に不利益をもたらす行為

- 公序良俗に反する行為


## 免責事項


本ソフトウェアにより生じた損害・不利益について、製作者は一切の責任を負いません。


## その他


ご利用の際は VOICEVOX を利用したことがわかるクレジット表記が必要です。


---

[中略 - 詳細は実際の出力を確認してください。]


利用規約の詳細は以下をご確認ください。

https://zonko.zone-energy.jp/guideline

+ exec gosu user /opt/python/bin/python3 ./run.py --voicelib_dir /opt/voicevox_core/ --runtime_dir /opt/onnxruntime/lib --host 0.0.0.0

Warning: cpu_num_threads is set to 0. Setting it to half of the logical cores.

reading /home/user/.local/share/voicevox-engine-dev/user.dict_csv-7bb7720c-7a5c-4341-84a6-9c9dd9a72dba.tmp ... 79

emitting double-array: 100% |###########################################| 

INFO:     Started server process [1]

INFO:     Waiting for application startup.

INFO:     Application startup complete.

INFO:     Uvicorn running on http://0.0.0.0:50021 (Press CTRL+C to quit)

Info: Loading core 0.15.7.

INFO:     10.88.0.3:37824 - "GET /docs HTTP/1.1" 200 OK

INFO:     10.88.0.3:37824 - "GET /openapi.json HTTP/1.1" 200 OK


上記のコマンドではコンテナのポート50021をホスト・ポート50021にマッピングしているため、REST APIのエンドポイントはhttp://localhost:50021がベースURLになります。OpenAPIによるREST APIのドキュメントは以下より参照できます。


VOICEVOXエンジンの準備は以上で完了です。

以下よりAPEXアプリケーション側での作業を紹介します。

アプリケーション定義置換に、いくつかAPEXアプリケーションが参照する値を置換文字列として設定します。

置換文字列G_VOICEVOX_BASE_URLとして、VOICEVOXエンジンのREST APIのベースURLを設定します。今回の構成はVOICEVOXエンジンを呼び出すOracle Databaseもローカルのコンテナで実行しているため、G_VOICEVOX_BASE_URLとしてhttp://host.containers.internal:50021を設定しています。

置換文字列G_SPEAKERには、REST APIのaudio_queryやsynthesisに引数speakerとして渡す値を設定します。http://localhost:50021/speakersより、番号と音声の対応を参照できます。選択する音声ごとに音声ライブラリ利用規約が設定されているので、確認が必要です。

置換文字列G_VOICEVOX_PROXY_URLに、Oracle REST Data Servicesで実装したREST APIのエンドポイントを設定します。APEXアプリケーションはVOICEVOXエンジンを直接呼び出す代わりに、一旦Oracle REST Data ServicesのREST APIを呼び出し、そこからVOICEVOXエンジンのREST APIを呼び出します。このようにした理由のひとつは、CORS対策です。APEXが生成したページからJavaScriptで直接VOICEVOXエンジンを呼ぼうとすると、VOICEVOXにリクエストが拒否されます。VOICEVOXのコンテナに手を入れたくなかったので、ORDSでリクエストを中継することによりCORS有効化を不要にしています。

置換文字列G_VOICEVOX_PROXY_URL/ords/apexdev/voicevox/audio_query_and_synthesisを設定しています。この値は、ORDS別名(通常はAPEXワークスペース名に同じ)に依存して、環境ごとに変わります。


ページ・デザイナで、読み上げ機能を追加するページ番号のページを開きます。

非表示のページ・アイテムP2_BASE_URLを作成し、置換文字列G_VOICEVOX_BASE_URLの値を、このページで実行するJavaScriptから参照できるようにします。

ソースタイプアイテムアイテムとしてG_VOICEVOX_BASE_URLを設定します。セッション・ステートストレージリクエストごと(メモリーのみ)を選択します。


同様にページ・アイテムP2_SPEAKERから、置換文字列G_SPEAKERの値を参照できるようにします。


ページ・アイテムP2_PROXY_URLから、置換文字列G_VOICEVOX_PROXY_URLの値を参照できるようにします。


読み上げに関するアイテムやボタンを含めるリージョンを作成します。識別名前Readout Containerタイプ静的コンテンツです。外観テンプレートに装飾の無いBlank with Attributesを選択します。

このリージョンにAPEXアクションを保持するコンテキストを作成するため、静的IDとしてREADOUTを設定します。


DeepSeek-R1の出力には、<think>...</think>で囲まれた推論過程が含まれます。小説の読み上げ時には不要な文章なので、小説から除くための切替えとして、ページ・アイテムP2_EXCLUDE_THINKを作成します。ラベルThinkを除くとします。

設定デフォルトの使用オンとします。この場合、切替えがオンでページ・アイテムの値はYオフNになります。レイアウト行CSS クラスu-flex u-align-items-centerを設定し、切替えやボタンの縦方向の位置を中央揃えにします。列スパンを設定し、この切替えの横幅に(横方向に12分割された列の内の)2列を割り当てます。

検証必須の値オン、デフォルト静的値Nとしています。この値は動的コンテンツのリージョン小説のPL/SQLコードから参照するため、セッション・ステートストレージセッションごと(永続)を選択し、セッション・ステートに保存します。


リージョン小説ソースCLOBを返すPL/SQLファンクション本体を、以下のコードに置き換えます。ページ・アイテムP2_EXCLUDE_THINKYであれば、表示する文章から<think>...</think>で囲まれた文字列を取り除きます。


ボタンREADOUTを作成します。ラベル読み上げるとします。このボタンをクリックして、リージョン小説に表示されている文章からWAVファイルを生成します。生成したWAVファイルは隣に表示されているオーディオ・コントールのソースに設定します。

Thinkの切替えの右隣りに配置するため、レイアウト新規行の開始オフ列スパンとします。

このボタンを押したときに実行される処理は、APEXアクションREADOUTとして実装します。動作アクションとして動的アクションで定義詳細カスタム属性data-action="READOUT"を設定します。


ボタンREADOUTの右隣りにオーディオ・コントロールを配置します。

静的コンテンツのリージョンを作成し、ソースHTMLコードとして以下を記述します。

<audio controls id="story-audio"></audio>


以上で画面上のコンポーネントの配置は完了です。

静的アプリケーション・ファイルとして、このページで実行するJavaScriptのファイルを作成します。フォルダ名js以下に、ファイルreadout.jsとして以下の内容を記述します。

リージョン小説に表示されている文字列を取り出して、それをORDSのREST APIに渡しています。ORDSのREST APIはWAVファイルを返すので、受信したWAVファイルをオーディオ・コントールのsrcに設定しています。



ページ・プロパティJavaScriptファイルURLに以下を記述し、readout.jsをロードするようにします。

[module,defer]#APP_FILES#js/readout#MIN#.js


VOICEVOXエンジンは、Oracle REST Data Servicesのサービスから呼び出します。長いコードをORDSのRESTハンドラに記述すると開発が面倒になるため、音声合成の呼び出しはパッケージEBAJ_VOICEVOX_PROXYにまとめることにしました。

パッケージ定義部です。

RESTハンドラから呼び出すのはaudio_query_and_synthesisのみです。


パッケージ本体です。APEXのページから送信された小説本文を改行で区切り、一行ごとに音声合成を呼び出します。変換されたWAVファイルをすべて連結して、呼び出し元のAPEXページに戻します。

WAVファイルを連結するプロシージャは、ChatGPTで最近使えるようになったo3-mini-highで生成しました。以下のプロンプトでほとんど動くコードが生成されたのは、結構な驚きです。
Please write a procedure to merge 2 wav files into one by PL/SQL of Oracle?

最初に生成されたコードはリトルエンディアンの4バイト表現にCHAR型を使っている点で問題があったのですが、それも以下のプロンプトで修正されたコードが生成されました。

以下をCHARではなくRAWで実装してください。
-----
   FUNCTION int_to_le_raw(p_int IN INTEGER) RETURN RAW IS
      v_byte1 CHAR(1);
      v_byte2 CHAR(1);
      v_byte3 CHAR(1);
      v_byte4 CHAR(1);
   BEGIN
      v_byte1 := CHR(MOD(p_int, 256));
      v_byte2 := CHR(MOD(TRUNC(p_int/256), 256));
      v_byte3 := CHR(MOD(TRUNC(p_int/256/256), 256));
      v_byte4 := CHR(MOD(TRUNC(p_int/256/256/256), 256));
      RETURN UTL_RAW.CAST_TO_RAW(v_byte1 || v_byte2 || v_byte3 || v_byte4);
   END;
ChatGPTがPL/SQLではかなり難しいバイナリ・データを操作するコードを生成してくれたので、とても助かりました。

後は、ebaj_voicevox_proxy.audio_query_and_synthesisを呼びだすORDSのハンドラを作成します。

モジュールとしてvoicevoxテンプレートとしてaudio_query_and_synthesisを作成し、POSTハンドラのソースに以下を記述します。



作成したRESTサービスはAPEXのセッションで保護できます。今回は実装しませんが、以前のOpenAIやAzureでの記事では、API呼び出しが有料なためAPEXセッションによる保護を実装しています。

以上でアプリケーションは完成です。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。APEX 24.2で作成しているため、それ以前のバージョンではインポートできません。
https://github.com/ujnak/apexapps/blob/master/exports/novel-generator-synthesis.zip

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