Skip to content

Commit 38fa759

Browse files
authored
Merge pull request #61 from led-mirage/feature/v1.33.0
Feature/v1.33.0
2 parents e998f53 + 33b34ed commit 38fa759

File tree

13 files changed

+219
-26
lines changed

13 files changed

+219
-26
lines changed

Readme.en.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ You can customize your own character, and one of the unique features is that it
3131
Here are the highlights of this app ✨
3232

3333
* Supports the three major AI services (OpenAI, Google Gemini, and Anthropic Claude)
34-
* Supports OpenAI API-compatible local LLMs
34+
* Supports OpenAI API-compatible local LLMs
3535
* Character customization feature
3636
* Automatic message read-aloud feature
3737
* Markdown display and TeX-format formula rendering
@@ -41,7 +41,8 @@ Here are the highlights of this app ✨
4141
* Compatible with Raspberry Pi (X11/LXDE only, Japanese input via IBus only, printing not supported)
4242
* Compatible with Linux Mint (Cinnamon/x64 only, Japanese input via IBus only, printing not supported)
4343
* Dark mode supported
44-
- Custom CSS support ✨
44+
* Custom CSS support
45+
* Image-based questions are now supported (experimental, OpenAI only) ✨
4546

4647
## 💎 Language Support
4748

Readme.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ APIを使ってAIとチャットするアプリなのだ。
3434
このアプリのアピールポイントは次のとおりなのだ✨
3535

3636
- 3大AIサービス(OpenAI、Google Gemini、Anthropic Claude)に対応
37-
- ローカルLLM(OpenAI API互換)に対応
37+
- ローカルLLM(OpenAI API互換)に対応
3838
- 柔軟なキャラクターカスタマイズ機能
3939
- メッセージ自動読み上げ機能
4040
- マークダウン表示、TeX形式の数式表示機能
@@ -44,7 +44,8 @@ APIを使ってAIとチャットするアプリなのだ。
4444
- Raspberry Pi対応(X11/LXDE、日本語入力はIBus限定、印刷機能は非対応)
4545
- Linux Mint対応(Cinnamon/x64、日本語入力はIBus限定、印刷機能は非対応)
4646
- ダークモード対応
47-
- スタイルシート(CSS)をカスタマイズ可能✨
47+
- スタイルシート(CSS)をカスタマイズ可能
48+
- 画像を使った質問が可能(実験的機能、OpenAIのみ)✨
4849

4950
アプリの紹介と、もっとも手軽な導入方法を[Zennの記事](https://zenn.dev/ledmirage/articles/7650f36d3a784a)にしたので、そちらも参考にしてほしいのだ✨
5051

@@ -205,7 +206,7 @@ Windowsの場合は、Windowsの検索窓で「環境変数を編集」で検索
205206

206207
以下のリンクから ZundaGPT2.ZIP をダウンロードして、作成したフォルダに展開するのだ。
207208

208-
https://github.com/led-mirage/ZundaGPT2/releases/tag/v1.32.0
209+
https://github.com/led-mirage/ZundaGPT2/releases/tag/v1.33.0
209210

210211
#### 3. 実行
211212

@@ -353,11 +354,11 @@ CSSを知らない人はなんのことかわからないかもしれないけ
353354

354355
VirusTotalでのチェック結果はこちらなのだ。
355356

356-
- Windows版: [72個中2個のアンチウィルスエンジンで検出 :2025/10/26 v1.32.0](https://www.virustotal.com/gui/file/d285792398208711d5b16b05edca0fb2a0bde7ddcbbcdce786427c740602f6b9/detection)
357-
- Raspberry Pi版: [61個中0個のアンチウィルスエンジンで検出 :2025/10/26 v1.32.0](https://www.virustotal.com/gui/file/d2169294d8b912d0390a176176d6adb01ad8acf39147669efc54bcc326e8d3af/detection)
358-
- Linux版: [62個中0個のアンチウィルスエンジンで検出 :2025/10/26 v1.32.0](https://www.virustotal.com/gui/file/a05184974a95efb10e609f7f5abdffee093cd72f4258cfbcbfce5b106972e2ec/detection)
357+
- Windows版: [71個中1個のアンチウィルスエンジンで検出 :2025/10/31 v1.33.0](https://www.virustotal.com/gui/file/e93d31b74243520d3d202a6307723e41d1fb1517d31b585409739423913ffd76/detection)
358+
- Raspberry Pi版: [61個中0個のアンチウィルスエンジンで検出 :2025/10/31 v1.33.0](https://www.virustotal.com/gui/file/409a69577a8c8e25ee7f0ae24a0fe0a8f1376ba3d81746dd1ada6bcde98b8ce5/detection)
359+
- Linux版: [63個中0個のアンチウィルスエンジンで検出 :2025/10/31 v1.33.0](https://www.virustotal.com/gui/file/cd622f4756cc4b111da71db6bfcfe156beeee662539e91f9b85e9862cb9194ef/detection)
359360

360-
<img src="doc/images/virustotal_1.32.0.png" width="600">
361+
<img src="doc/images/virustotal_1.33.0.png" width="600">
361362

362363
### ⚡ ご利用について
363364

@@ -488,6 +489,13 @@ VirusTotalでのチェック結果はこちらなのだ。
488489

489490
## 💎 バージョン履歴
490491

492+
### 1.33.0 (2025/10/31)
493+
494+
- 画像送信機能の追加(実験的機能)
495+
- OpenAIのみ、最大送信枚数:10枚
496+
- テキストボックス内にドラッグ&ドロップするか、クリップボードから貼り付けてください
497+
- 貼り付けた画像は1回につき1度だけ送信され、チャットログには保存されません
498+
491499
### 1.32.0 (2025/10/26)
492500

493501
- OpenAI GPT-5-Codex、GPT-5 proに対応
@@ -508,15 +516,15 @@ VirusTotalでのチェック結果はこちらなのだ。
508516

509517
- fix: ウィンドウの最小化、最大化状態から元に戻す際にウィンドウサイズが固定化されるバグを修正
510518

519+
<details><summary>それ以前のバージョンアップの履歴はこちらなのだ</summary>
520+
511521
### 1.30.0 (2025/09/14)
512522

513523
- ウィンドウサイズの自動保存・復元機能を追加
514524
- Darkスタイルの修正
515525
- 言語ファイルの統一
516526
- コメント・書式の統一
517527

518-
<details><summary>それ以前のバージョンアップの履歴はこちらなのだ</summary>
519-
520528
### 1.29.0 (2025/09/07)
521529

522530
- キャラクター毎のカスタムスタイルを設定可能に。

app/api/api_router.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ def delete_current_chat(self):
5656
return self.index_service.delete_current_chat()
5757

5858
# メッセージ送信イベントハンドラ(UI)
59-
def send_message_to_chatgpt(self, text):
60-
return self.index_service.send_message_to_chatgpt(text)
59+
def send_message_to_chatgpt(self, text, images):
60+
return self.index_service.send_message_to_chatgpt(text, images)
6161

6262
# メッセージ再送信イベントハンドラ(UI)
6363
def retry_send_message_to_chatgpt(self):

app/chat/chat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,5 @@ def send_onetime_message(self, text: str) -> str:
7575
pass
7676

7777
@abstractmethod
78-
def send_message(self, text: str, listener: SendMessageListener) -> str:
78+
def send_message(self, text: str, images: list[str], listener: SendMessageListener) -> str:
7979
pass

app/chat/chat_claude.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def send_onetime_message(self, text:str):
5959
def send_message(
6060
self,
6161
text: str,
62+
images: list[str],
6263
listener: SendMessageListener) -> str:
6364

6465
try:

app/chat/chat_gemini.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def send_onetime_message(self, text:str) -> str:
5656
def send_message(
5757
self,
5858
text: str,
59+
images: list[str],
5960
listener: SendMessageListener) -> str:
6061

6162
try:

app/chat/chat_openai_base.py

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# このソースコードは MITライセンス の下でライセンスされています。
77
# ライセンスの詳細については、このプロジェクトのLICENSEファイルを参照してください。
88

9+
import copy
910
import httpx
1011
from datetime import datetime
1112
from httpx import ReadTimeout
@@ -29,36 +30,53 @@ def send_onetime_message(self, text:str):
2930
def send_message(
3031
self,
3132
text: str,
33+
images: list[str],
3234
listener: SendMessageListener) -> str:
3335

3436
try:
35-
return self.send_message_completions_streaming(text, listener)
37+
return self.send_message_completions_streaming(text, images, listener)
3638

3739
except StreamNotAllowedError:
3840
self.messages = self.messages[:-1]
39-
return self.send_message_completions_not_streaming(text, listener)
41+
return self.send_message_completions_not_streaming(text, images, listener)
4042

4143
except ResponsesApiRequiredError:
4244
self.messages = self.messages[:-1]
4345
try:
44-
return self.send_message_responses_streaming(text, listener)
46+
return self.send_message_responses_streaming(text, images, listener)
4547
except StreamNotAllowedError:
4648
self.messages = self.messages[:-1]
47-
return self.send_message_responses_not_streaming(text, listener)
49+
return self.send_message_responses_not_streaming(text, images, listener)
4850

4951
# メッセージを送信して回答を得る(Completions API・ストリーミング版)
5052
def send_message_completions_streaming(
5153
self,
5254
text: str,
55+
images: list[str],
5356
listener: SendMessageListener) -> str:
5457

5558
try:
5659
self.stop_send_event.clear()
5760

5861
self.messages.append({"role": "user", "content": text})
59-
messages = self.get_history()
62+
messages = copy.deepcopy(self.get_history())
63+
64+
if images and len(images) > 0:
65+
messages = messages[:-1]
66+
content = []
67+
if text:
68+
content.append({"type": "text", "text": text})
69+
70+
for b64 in images:
71+
if not b64.startswith("data:"):
72+
b64 = f"data:image/png;base64,{b64}"
73+
content.append({"type": "image_url", "image_url": {"url": b64}})
74+
75+
messages.append({"role": "user", "content": content})
76+
6077
if not self.model.startswith("o1") and not self.model.startswith("o3") and self.instruction:
6178
messages.insert(0, {"role": "system", "content": self.instruction})
79+
6280
stream = self.client.chat.completions.create(model=self.model, messages=messages, stream=True)
6381

6482
content = ""
@@ -135,14 +153,29 @@ def send_message_completions_streaming(
135153
def send_message_completions_not_streaming(
136154
self,
137155
text: str,
156+
images: list[str],
138157
listener: SendMessageListener) -> str:
139158

140159
try:
141160
no_exception = False
142161
listener.on_non_streaming_start()
143162

144163
self.messages.append({"role": "user", "content": text})
145-
messages = self.get_history()
164+
messages = copy.deepcopy(self.get_history())
165+
166+
if images and len(images) > 0:
167+
messages = messages[:-1]
168+
content = []
169+
if text:
170+
content.append({"type": "text", "text": text})
171+
172+
for b64 in images:
173+
if not b64.startswith("data:"):
174+
b64 = f"data:image/png;base64,{b64}"
175+
content.append({"type": "image_url", "image_url": {"url": b64}})
176+
177+
messages.append({"role": "user", "content": content})
178+
146179
if not self.model.startswith("o1") and not self.model.startswith("o3") and self.instruction:
147180
messages.insert(0, {"role": "system", "content": self.instruction})
148181

@@ -204,6 +237,7 @@ def send_message_completions_not_streaming(
204237
def send_message_responses_streaming(
205238
self,
206239
text: str,
240+
images: list[str],
207241
listener: SendMessageListener) -> str:
208242

209243
try:
@@ -232,6 +266,11 @@ def convert_message(m):
232266

233267
responses_input = [convert_message(m) for m in messages]
234268

269+
if images and len(images) > 0:
270+
last_message = responses_input[-1]
271+
for image in images:
272+
last_message["content"].append({"type": "input_image", "image_url": image})
273+
235274
content = ""
236275
sentence = ""
237276
paragraph = ""
@@ -328,6 +367,7 @@ def convert_message(m):
328367
def send_message_responses_not_streaming(
329368
self,
330369
text: str,
370+
images: list[str],
331371
listener: SendMessageListener) -> str:
332372

333373
try:
@@ -339,9 +379,32 @@ def send_message_responses_not_streaming(
339379
if not self.model.startswith("o1") and not self.model.startswith("o3") and self.instruction:
340380
messages.insert(0, {"role": "system", "content": self.instruction})
341381

382+
def convert_message(m):
383+
role = m["role"]
384+
if role == "assistant":
385+
content_type = "output_text"
386+
else:
387+
content_type = "input_text"
388+
return {
389+
"role": role,
390+
"content": [
391+
{
392+
"type": content_type,
393+
"text": m["content"],
394+
}
395+
],
396+
}
397+
398+
responses_input = [convert_message(m) for m in messages]
399+
400+
if images and len(images) > 0:
401+
last_message = responses_input[-1]
402+
for image in images:
403+
last_message["content"].append({"type": "input_image", "image_url": image})
404+
342405
response = self.client.responses.create(
343406
model=self.model,
344-
input=messages,
407+
input=responses_input,
345408
timeout=httpx.Timeout(300.0, connect=5.0)
346409
)
347410

app/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
# ライセンスの詳細については、このプロジェクトのLICENSEファイルを参照してください。
88

99
APP_NAME = "ZundaGPT2"
10-
APP_VERSION = "1.32.0"
10+
APP_VERSION = "1.33.0"
1111
COPYRIGHT = "© 2024-2025 led-mirage"

0 commit comments

Comments
 (0)