メインコンテンツまでスキップ

LM Studio用の低消費電力PCをArc Pro B60 24GBで組みたい2(GPUは買ったぞ)

· 約2分
もみじーな
個人開発者

タイトル通りArc Pro B60 24GBを1枚だけ購入しました。

本当は嫌ですが予約購入です・・・・

果たしていつ入荷されるのか

本当は2枚欲しいのですがちょっと構成を考え直す可能性を考慮してとりあえず1枚予約購入です。

ケースも買いなおすことにしました。

結局、あのケースは2スロット厚でギリギリ収まったとしてもおそらく廃熱で死にます。

ごめよHyte お前は倉庫で眠っててくれ

現在、考えているPC構成は以下の2パターンに絞りました。

電源はもしかしたら850か1000wにするかもしれないので合計は目安ですね。

まだ、電力スパイクとかもちゃんと考えてないので・・・

足りはしてるはず。

Aプラン

カテゴリー製品名価格
ケースQUBE 500 Flatpack14,000
マザーボードZ890 AERO G52,830
CPUCore Ultra 5 22528,000
メモリCMK32GX5M2B5600C4034,000
電源RM750e 202512,000
M.2CT500P310SSD8-JP7,000
GPUArc B60 24GB x2240,000

合計387,830円

Bプラン

カテゴリー製品名価格
ケースQUBE 500 Flatpack14,000
マザーボードProArt B650-CREATOR34,000
CPUryzen5 760028,000
メモリCMK32GX5M2B5600C4034,000
電源RM750e 202512,000
M.2CT500P310SSD8-JP7,000
GPUArc B60 24GB x2240,000

合計 369,000円

x8/x8対応のマザーがなさすぎてマザーボードは高くなりがちですね。

一応、Arc Pro B60 24GBをDualで運用を検討している方がいたら安いマザーボードは基本的にNGだと思っておいてください。

性能が落ちます。

また、CPUにも実は制限があります。Ryzenでの例ですが例えばGが付くモデルとかは避けるべきです。

理由はCPU自体が持っているレーン数が少ないため、2枚挿し時に制限がかかる可能性があります。(間違えている可能性はありますがリスクは捨てるべきです)

とりあえず6950xtを前のPCからもぎ取って動作テストはしたいかな。

あまりにもB60がゴミならやめて別を考えます・・・

Google AI Studioで自動4コマアプリをつくったら案外・・・

· 約2分
もみじーな
個人開発者

画像が自動圧縮されるのでwebpに一部、変換しました


タイトル通りです。

AI Studioもだいぶ良くなってますね。

正直これは感動ものです。

作成につかったプロンプトは以下だけ(こんな雑でもいけるのか)

与えられた画像の人物から漫画を生成できるようにしてお題も入力できるようにして
画像をアップロードしなくても、こういうキャラでこういう構成みたいな感じでみたいなプロンプトで作れるようにして

画面はこんな感じのできました

テスト入力 できた漫画 できた漫画2

一応、ダウンロード機能とアスペクト比がおかしかったので修正させてます。

ダウンロードした漫画です。

アスペクト比の修正前です アスペクト比修正後

人物画像からの方も問題はなかったです。

正直、バスより早く走ってたり意味が分からないですが・・・

AI 4コマでそのうちストアとか埋め尽くされそう・・・

そもそもAI Studioの画像生成って何回できるんですかね

かなりの量を生成させましたが特に制限もなさそうでした。

有料アカウントだからなのかはわからないですが結構生成できそう。

Antigravityのエージェントモードは現状はVSCodeより良いかも

· 約3分
もみじーな
個人開発者

一番下に追記してます。
私はVSCodeに返り咲きました。はい。


今年に入ってVSCodeのエージェントを使った開発を恐らく累計で数百時間やってきたきがします。

VSCodeのエージェントに開発ガイドを作成して読ませたりすると通常のAIでは作成できないものも作れるので気に入ってました。

おそらくVSCodeがオープンソースになったこともありAntigravityが登場したのでしょうがGemini3も一緒に出て使えることですごいことになってました。

なにがすごいのかですかAIのコード生成もそうですがAntigravityでは開発ガイドを分割して読むのではなく一括で読んでくれてるのかとても速く高品質です。

VSCodeでエージェントにガイドを読ませると分割して読んでるのでどうしても質の低下もありました。

間違いなく最近使ってたCopilot + Claude 4.5よりも正確にプログラムを作成してくれるので課金プランが出たら間違いなく入ります。

Googleの有料プランにそのままついてきたら本当にクラウドのAIはGoogleが最強になりそうです・・・

一応,VSCodeとAntigravityのどちらもGemini3で同じプラグインを生成させるテストをしました。

左がAntigravity,右がVSCodeです(どっちもGemini3なのでどちらも似た感じです)

プロンプトはどちらも以下だけ

開発ガイドを読んでダッシュボードアプリとしてアナログ時計を開発してください
開発ガイドは最後まで読んで

これだけですがAntigravityは何度やっても1回で成功させます。

VSCodeは同じGemini3なのになぜか一度エラー処理をさせる必要がありました。

現状,Antigravityが上をいっています。

Github Copilotの課金プランどうしようかな・・・


20251126の追記です。

結局,3日くらい使いましたがVSCodeに戻しました。

自律型エージェントとしては確かにVSCodeを超えてます。

問題は私の使い方があってないのかもしれないですがAntigravityに少し複雑なアプリ作らせてたのですが最初はいいのです。

ですが追加だったりエラーだったり処理をさせればさせるほどゴミになっていきます。

結局は人力になってしまう。

あとAntigravityではガイドも一括で読んでくれてると思ったけどそうでもなさそう。

無料で使えるClaudeにすると今度はVSCodeでは処理できるエラーに対処できない。

所詮はまだテスト中のものにすぎないかもですね。

ごめんよVSCode君、Antigravityの方がいいなんていって

Antigravityはお家にお帰り、つばをぺっぺっ

まぁ、無料で使えるのでアップデート待ち

ガイドが必要ない簡単なアプリは特に問題ないです。

LM Studio用の低消費電力PCをArc Pro B60 24GBで組みたい

· 約6分
もみじーな
個人開発者

一応,書いときます(特殊な理由がないならAPI使いましょう)


現在、RTX5090をAI生成もといゲームように使ってますが24時間365日起動しとくのは微妙なのとどうしてもゲーム中に使えないので構成を検討中です。

とりあえずArc Pro B60を年内に1枚買ってみる予定です。(在庫があれば)

私が今回,推論用PCの購入に検討したもの

AMD Ryzen AI Max+ 395

AMD Ryzen AI Max+ 395を買えばいいという話ですがこれは論外です。

中華のミニPCをAIサーバー用途に30万,40万払うのはさすがに怖すぎます。

gmktecのミニPCを過去にサーバーようにフル稼働させたときに1か月もたなったことがあるのでさすがにね・・・
(確かに交換してくれますがデータが吹き飛ぶ可能性があります。)

重要なのは中華ミニPCはサーバーではありません。(通常用途で使ってあげてください)

Corsair AI Workstation 300の日本発売を待ってましたが出る気配もないのであきらめです。
(そりゃ輸入してもいいですよ?ついてるWifiとかの技適がね)

NVIDIA DGX Spark

NVIDIA DGX Sparkも検討しましたが発表時と話が違うぞ?
そのメモリ帯域速度で60万はなんかなと・・・

企業とか開発者じゃなく、個人でこれを買うならケチらずいっそメモリは少し減りますがNVIDIA RTX PRO 6000でも買ってください。

NVIDIA RTX PRO 6000

5090より確かにメモリ量が3倍です。
ただそれだけです。
私がやりたいローカルAIでの動画生成がまだ5秒や10秒でしかも音声なしの状態でこれを買うメリットは存在しません。

まだ、次の世代と生成AIの進化を待つべきです。

絶対、2027年のRubin買った方がいいですよ。

私はSora2(OpenAIのPro)に課金をおすすめします。

Nvidia DGX Station GB300かH200

Nvidia DGX Station GB300は高い高すぎる。
正直、将来的には検討の対象ですが価格以外に騒音と熱を処理できるスペースが現在、ありません。

車が買えるレベルのH200を買うにしても私がやりたいことリストをローカルAIがまだできないので暫くはAPIでいいかなというのが現状です。

買うならDELLとかのAIサーバーになりそう。

それまでに規制の少ない課金AIがでればそれで解決ですがSora2とか見てるとキャラクターとかストーリーとか制限が入りそう。

2026年発売予定のMac Studio M5 Ultra

これは来年あたり買ってみる候補になります。
おそらくメモリ帯域が1TBくらいになります。
メモリ量も1tbにならんかな

そしてArc Pro B60 24GB

今、ソフマップとかで約12万で販売が開始されてます。
安いです。
現在、売れ残りの7900xtxと確かに価格は変わらないですしメモリ帯域速度も遅いです。

しかし重要なのは消費電力とIntelということです。

IntelのGPUを買ったことがないのです。

買ってみます。

ただそれだけです。

10月に7900xtxが投げ売りされてたのですが開けてないのでがこれは飾っておきます。
私には6950xtを積んだ化石PCがまだあるのです。

検討中の構成と大失敗

現在、検討中の構成は以下です。

項目製品名
CPUUltra 5 225
Memory適当に64GBか32GB
MBATXを適当に
PSU750WでGold電源
M.2 SSD1TB
CPUファン一応、簡易水冷買うか?
PCケースHyte Y40
GPUARC B60

どこを失敗しているか

失敗したのはPCケースです。
実は何も考えずに10月にセールしていたのでB60用にY40 Songque Limited Editionを買ってしまいました。
このPCケース何が問題かわかる人にはわかります。

まず価格・・・ではないですね。

そうです、このケースはデザインを追求したPCケースである為にあることができません。

それがデュアルGPUです。

マザボにささらないのです1枚目すらライザーケーブル必須なのです・・・・・

ロープロはささりますね。

しかもこのPCケースを買った理由が既に2つフルプライスで去年買ってあったのですが1万円引きで公式で売ってたのと積みすぎてケースが取り出せないないというアホな理由です。

なんだよPCケース積みすぎって

転売ヤーではないですがそのうちPCたくさんHyteで組んで飾りたいと思い実はHYTE Y70 Silver Wolf Limited Editionもあと3台分あります。

見てくださいとてもきれいです(LEDは一部うざいので消しました)

ホタルエディションも1台ですが予約済ですよ?

仕方がないのでとりあえず1枚構成で組んでみる予定です。

Hyteさんには申し訳ないですが最悪、切断・・・はしないですがケースを変えるか考えます。

多分,箱にいれたまま積んである色んなPCケースだけでRTX PRO 6000が買えますね・・・・

床も抜けそう

Sora2用の動画管理Wordpressテーマを作りたい

· 約1分
もみじーな
個人開発者

Sora2いいですよね。

しかも来月、アダルト解禁らしいですよ?

これは作成した動画を自己管理したいと思い立ち今に至ります。

なのでSynologyで動かせるようにWordpressでSora2ようにテーマを作成中です。

テーマとプラグインで管理してます。

とりあえず動画と説明とプロンプトとかタグ付けとかできるようにしてますが12月が楽しみです。

一応、作成中のテストテーマですダウンしたりしてるかもしれないので見れなかったらあきらめてください

WordpressでSora2プロンプト管理

Sora2のほうはゴミ動画生成してて恥ずかしいので検索しないでね。

テーマぶれぶれでやばい状態です。

Exmentにお知らせ用の既読管理システム(回覧版?)プラグインを作る

· 約1分
もみじーな
個人開発者

閲覧したかどうかがグループウェアの定番としてありますがExmentにはないようなのでサンプルプラグインをAIで作成してみました。

私、個人では必要ないしデバッグもある程度しかできないのでデータ数が増えるとバグる可能性もありますがちゃんと動きはしました。

安否確認とかも定番なので試しに作らせるかもしれないですがデバッグが不完全な安否確認システムって怖すぎですよね。

まぁ、試しにだけそのうち作ります。

開発の方とGithubに入れておくので既読管理システム(回覧版?)は自己責任で使用してください。

なんならちゃんとしたのに誰か作り変えて公開してください・・・

Exmentで座席管理システム(座席管理)をつくりたかった

· 約2分
もみじーな
個人開発者

タイトル通りですがふとExmentに座席管理みたいなものがそういえばないなと思い作れる試してみました。

左上がバグってますがまぁ・・・

座席表はExcelフォーマットで配布されてるやつを名前とかだけ消してテストで使ってます。

結構無理やり作っててこのプラグイン1つになんと3テーブルもつかってます。

フロアユーザーのテーブルにExmentのユーザーテーブルを指定すれば減りますが特に何も考えてなったですね。

AIでこねくり回しながらつくってたのでなんかユーザー取得方法を変えるのもめんどくさくなって放置中です。

AIの生成したReadmeが感動するほど雑で意味がわからないのでReadmeの修正も含めると・・・

Githubにプライベートで上げてますがテーブル多すぎて整理ができないので・・・

あと放置理由はSora2というおもちゃにはまってしまっててプロンプトどう変えるか等の研究中です。

途中からAIにしかできないゴミ動画を作るのが楽しくなってますが何度も作っていると1日30回じゃ足りないのでプランに入るか検討してますが何故かBANされそうな動画が生成されることがあったので入ってなかったですが修正されたようなのでどっかとりあえず時間があるとき加入します。

Stripeなんですね支払い

ならComfyUIも安心して契約できそうですね。

Qwen-Image-Editを使った動画のフレーム置き換え

· 約2分
もみじーな
個人開発者

Wan2.2 Animateだとアニメベースの動画だったりだと置き換えが不安定なのでテストしてみました。

やり方

怒られそうなのでだいぶ簡単に書きます。
一応、自動化してますが1フレームごとにOpenCVで切り取りながら同じ画像じゃないかだけ判定してQwen-Image-Editに飛ばしてry
さらにキャラを固定させるためにプロンプトをある程度細かく長くつくりLoraも強めに当ててます。

テスト動画1(初回テスト)

Youtubeの【未確認で進行形】というアニメOPを360Pで5秒くらいだけ切り取り生成した動画です。

怒られそうなので動画は調べてください。

2期まだか?

ステップ数4で各フレーム生成してます。

このテストの失敗点は360Pで切り取ったという解像度の低さとフレーム取得の不安定とLoraとプロンプト,ステップ数,etc(きりがないですね)

テスト動画2(微調整テスト)

みんな大好きLonely Lonelyのトレース + Lora適用です。

だいぶよくなったのではないかと思いますがフレーム数,解像度,ステップ数,書き出しがバグって解像度がおかしくなった,etc・・・

ステップ数8で各フレーム生成してます。

まぁ、だいぶいいですが私のテストはここで終わります。

結論

やろうと思えばアニメのトレースがいけそう

そして色んな所から怒られそう。

ここでやめておきます。

Wan2.2をRTX5090で試してみる

· 約3分
もみじーな
個人開発者

Wan2.2がでてから放置してましたがWan2.2のAnimeteを5090で試す為に今回,色々ダウンロードしたのでその内容です。
Wan2.2 Animeteは360Pだと粗すぎるし720Pだと結構時間かかる割に微妙だったのでやめました。

Wan2.2 14B T2V テキストtoビデオワークフローを使ってます。
設定しないと場合によってページング ファイルが小さすぎるとかいうエラーがでますがまぁ動くようになりました。

使い方とワークフロー

使い方について公式wikiと公式ワークフロー使ってますのでそちらを確認してください。
公式Wiki

RTX5090での生成時間とか

裏でゲームとか動いてたので正確ではないですが640x640で50-70秒です
720Pで3分かからないくらいです。

プロンプトも一応,日本語対応してるんですね 生成時間とか参考図

生成してみた動画とか

上の画像で生成したのがこんな感じの動画です。(ローカルで作ったと思えば凄いです)
元画像はQwenImageで生成してます。(崩壊3rdのヴィタちゃんと初音ミクです)

一応,初期設定での640x640です

次のバージョンへの期待とComfyCloud(A100 40GBらしい)

Wan2.2はローカルとしてはかなりいいです。

2.5はプレビューでてますがローカル用に公開されるのか?
そもそも5090程度で動作するのか?

今回,Comfyから公式に出たクラウドサービスのComfyCloudもベータ初期にAnimeteを何となく試してましたがWanを使うなら5090より速い場合(要検証)が多かったのでサブスクを検討してますが支払方法がStripeかlinkしかないので迷ってます。

どっちも使ったことがないのでクレジットカード入力は怖いです。

ただ月20ドルで1日,8時間も動かせる?らしいので画像生成サービスとしては最強じゃないですかね。

問題はまだモデルがプリインストールのみらしいです。(Loraもまだダメそう)

Comfy Cloudの価格詳細

C# WPFでQwen3を使った完全ローカル翻訳システムをGithub Copilotと作ってみた

· 約7分
もみじーな
個人開発者

今回のプログラム(LocalTranslatorWPF)
Readmeはないです。

LLamaSharpの使い勝手がいいので完全ローカルの翻訳システムが作れないかと思いVisual Studio 2022で作成してみました。(作らせた)
本当にコードをゼロから作ることがなくなりました。(修正は必要)
モデルもLM Studioから引っこ抜いて使ってます。

ローカルでさらにAPI不要なので業務でもExcelアドインとかCADアドインとしても使えそうでした。

LLamaSharpを使用

前回と同様にLLamaSharpとLLamaSharp.Backend.CpuとCudaをNugetでインストール

2GB以上のモデルは今回、強制的にGPUのCudaを使うようにしてます。

さすがCudaって感じでした。前回はなしで作りましたがボタン押したら秒です。
毎回モデルを読み込むのがネックなくらいです。

動作確認モデル

Qwen3とGemma3で動作テストしてます。(思考モードあってもなくてもいい様にしてるはず)

基本的に4bモデル以上なら動作は安定してました。

小さいモデルは翻訳レベルが低くまともに使えなかったのと思考モードがループに入ることがあったので使えなかったです。

翻訳レベルと翻訳結果

翻訳に使った文章

英文と日本語(Gemini Pro2.5で生成しましたので翻訳の品質が分からない)

英文: Regular exercise is important for maintaining good health. 
訳: 定期的な運動は、健康を維持するために重要です。
英文: Technological advancements, particularly in artificial intelligence, are rapidly transforming our daily lives. While they offer unprecedented convenience, they also present new ethical challenges that society must thoughtfully address.
訳: 技術の進歩、特に人工知能は、私たちの日常生活を急速に変革しています。それらは前例のない利便性を提供する一方で、社会が慎重に対処すべき新たな倫理的課題も提示しています。

Qwen3 4b

Qwen3 8b

翻訳精度(わからない)

どうなんですかね。
元の文章もAIで生成していることもあって翻訳精度がまったくわからないです。
あとは翻訳をどんな感じにさせるかのプロンプトしだいですかね。

コード内容

Copilot君に作ってもらったのを修正してます。
TranslationServiceがメイン処理なのでとりあえずここだけ
残りはGithubで公開しました。
LLamaModelsにあるモデルを自動で読み込みます。

TranslationService.cs[クリックして展開]
using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using LLama;
using LLama.Common;
using LLama.Sampling;
using LocalTranslatorWPF.Models;

namespace LocalTranslatorWPF.Services
{
public class TranslationService : IDisposable
{
private LLamaWeights _model;
private LLamaContext _context;
private bool _isInitialized;

public async Task<bool> InitializeModel(ModelInfo modelInfo, bool useGPU, Action<string> logCallback = null)
{
try
{
Cleanup();

logCallback?.Invoke($"モデルを読み込んでいます: {modelInfo.Name}...");

var parameters = new ModelParams(modelInfo.Path)
{
ContextSize = 2048,
GpuLayerCount = useGPU ? 35 : 0,
UseMemorymap = true,
UseMemoryLock = false
};

_model = await Task.Run(() => LLamaWeights.LoadFromFile(parameters));
_context = _model.CreateContext(parameters);
_isInitialized = true;

logCallback?.Invoke($"モデルの読み込みが完了しました。GPU使用: {useGPU}");
return true;
}
catch (Exception ex)
{
logCallback?.Invoke($"エラー: {ex.Message}");
_isInitialized = false;
return false;
}
}

public async Task<string> TranslateAsync(TranslationRequest request, Action<string> progressCallback = null, CancellationToken cancellationToken = default)
{
if (!_isInitialized)
throw new InvalidOperationException("モデルが初期化されていません。");

// 同じ言語かどうかをチェック
var detectedLanguage = DetectLanguage(request.SourceText);
var targetLanguage = request.TargetLanguage;

// 日本語→日本語、英語→英語の場合はスキップ
if ((detectedLanguage == "Japanese" && targetLanguage == "Japanese") ||
(detectedLanguage == "English" && targetLanguage == "English"))
{
var message = detectedLanguage == "Japanese"
? "日本語から日本語への翻訳のため、翻訳しませんでした。"
: "英語から英語への翻訳のため、翻訳しませんでした。";

progressCallback?.Invoke(message);
return message;
}

var prompt = CreateTranslationPrompt(request);
progressCallback?.Invoke("翻訳中...");

var inferenceParams = new InferenceParams
{
MaxTokens = 2048,
SamplingPipeline = new DefaultSamplingPipeline
{
Temperature = 0.6f,
TopP = 0.95f,
TopK = 20,
MinP = 0
},
AntiPrompts = new[] {
"<<<END>>>",
"\n\n\n",
"###",
"\n---"
}
};

var executor = new InteractiveExecutor(_context);
var result = new StringBuilder();

await foreach (var text in executor.InferAsync(prompt, inferenceParams, cancellationToken))
{
result.Append(text);
var currentText = result.ToString();

// 終了デリミタを検出
if (currentText.Contains("<<<END>>>"))
{
break;
}

// 進捗表示用に翻訳結果を抽出
var extractedTranslation = ExtractTranslationFromDelimiters(currentText);
if (!string.IsNullOrEmpty(extractedTranslation))
{
// 元のテキストと同じ場合は表示しない(翻訳失敗の可能性)
if (!IsSameContent(extractedTranslation, request.SourceText))
{
progressCallback?.Invoke(extractedTranslation);
}
}
}

// 最終的な翻訳結果を抽出
var finalResult = ExtractTranslationFromDelimiters(result.ToString());

if (string.IsNullOrEmpty(finalResult))
{
// デリミタ抽出失敗時はテキストクリーニングにフォールバック
finalResult = CleanTranslationResult(result.ToString(), request.SourceLanguage);
}

// 元のテキストと同じ場合はエラーメッセージ
if (IsSameContent(finalResult, request.SourceText))
{
var errorMessage = "翻訳に失敗しました。モデルが指示を理解できませんでした。";
progressCallback?.Invoke(errorMessage);
return errorMessage;
}

progressCallback?.Invoke(finalResult);
return finalResult.Trim();
}

private bool IsSameContent(string text1, string text2)
{
if (string.IsNullOrWhiteSpace(text1) || string.IsNullOrWhiteSpace(text2))
return false;

// 空白と句読点を除去して比較
var normalized1 = Regex.Replace(text1.ToLower(), @"[\s\p{P}]", "");
var normalized2 = Regex.Replace(text2.ToLower(), @"[\s\p{P}]", "");

// 完全一致または90%以上一致していれば同じとみなす
if (normalized1 == normalized2)
return true;

// レーベンシュタイン距離で類似度チェック
int distance = LevenshteinDistance(normalized1, normalized2);
int maxLength = Math.Max(normalized1.Length, normalized2.Length);
double similarity = 1.0 - (double)distance / maxLength;

return similarity > 0.9;
}

private int LevenshteinDistance(string s1, string s2)
{
int[,] d = new int[s1.Length + 1, s2.Length + 1];

for (int i = 0; i <= s1.Length; i++)
d[i, 0] = i;
for (int j = 0; j <= s2.Length; j++)
d[0, j] = j;

for (int j = 1; j <= s2.Length; j++)
{
for (int i = 1; i <= s1.Length; i++)
{
int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
}
}

return d[s1.Length, s2.Length];
}

private string ExtractTranslationFromDelimiters(string text)
{
if (string.IsNullOrWhiteSpace(text))
return string.Empty;

// <<<START>>> と <<<END>>> の間を抽出
var match = Regex.Match(text, @"<<<START>>>(.*?)(?:<<<END>>>|$)", RegexOptions.Singleline);
if (match.Success && match.Groups.Count > 1)
{
return match.Groups[1].Value.Trim();
}

return string.Empty;
}

private string DetectLanguage(string text)
{
if (string.IsNullOrWhiteSpace(text))
return "Unknown";

// 日本語文字(ひらがな、カタカナ、漢字)の割合をチェック
int japaneseCharCount = 0;
int totalChars = 0;

foreach (char c in text)
{
if (char.IsWhiteSpace(c) || char.IsPunctuation(c))
continue;

totalChars++;

if ((c >= 0x3040 && c <= 0x309F) || // ひらがな
(c >= 0x30A0 && c <= 0x30FF) || // カタカナ
(c >= 0x4E00 && c <= 0x9FAF)) // 漢字
{
japaneseCharCount++;
}
}

if (totalChars == 0)
return "Unknown";

double japaneseRatio = (double)japaneseCharCount / totalChars;
return japaneseRatio >= 0.3 ? "Japanese" : "English";
}

private string CleanTranslationResult(string result, string sourceLanguage)
{
// デリミタを削除
result = result.Replace("<<<START>>>", "").Replace("<<<END>>>", "");

// 改行が2つ以上続く場合、最初の部分だけを取得
var parts = result.Split(new[] { "\n\n" }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 0)
{
result = parts[0];
}

// 不要な接頭辞を削除
var prefixesToRemove = new[]
{
"英語: ",
"日本語: ",
"English: ",
"Japanese: ",
"翻訳: ",
"Translation: ",
"訳: "
};

foreach (var prefix in prefixesToRemove)
{
if (result.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
result = result.Substring(prefix.Length);
}
}

return result.Trim();
}

private string CreateTranslationPrompt(TranslationRequest request)
{
if (request.SourceLanguage == "Japanese")
{
// 日本語→英語(Few-shot付き)
return $@"あなたは翻訳者です。日本語を英語に翻訳してください。

例1:
日本語: こんにちは、元気ですか?
翻訳: <<<START>>>Hello, how are you?<<<END>>>

例2:
日本語: 今日は良い天気ですね。明日も晴れるそうです。
翻訳: <<<START>>>It's nice weather today.It's supposed to be sunny again tomorrow.<<<END>>>

実際のタスク:
日本語: {request.SourceText}
翻訳: <<<START>>>";
}
else
{
// 英語→日本語(Few-shot付き)
return $@"あなたは翻訳者です。英語を日本語に翻訳してください。必ず日本語で答えてください。

例1:
English: Hello, how are you?
翻訳: <<<START>>>こんにちは、元気ですか?<<<END>>>

例2:
English: It's nice weather today.It's supposed to be sunny again tomorrow.
翻訳: <<<START>>>今日は良い天気ですね。明日も晴れるそうです。<<<END>>>

実際のタスク:
English: {request.SourceText}
翻訳: <<<START>>>";
}
}

private void Cleanup()
{
_context?.Dispose();
_model?.Dispose();
_context = null;
_model = null;
_isInitialized = false;
}

public void Dispose()
{
Cleanup();
}
}
}