Add files via upload

This commit is contained in:
FURK4NGG
2026-03-13 15:19:31 +03:00
committed by GitHub
parent c39e1e0c3f
commit e0fb1aeeb4
2 changed files with 530 additions and 113 deletions
+227 -66
View File
@@ -301,6 +301,148 @@ def fix_mojibake(s: str) -> str:
except Exception:
return s
def _message_to_plain_text(content) -> str:
if isinstance(content, str):
return content.strip()
if isinstance(content, list):
parts = []
for item in content:
if not isinstance(item, dict):
continue
if item.get("type") == "text":
txt = item.get("text")
if isinstance(txt, str) and txt.strip():
parts.append(txt.strip())
return "\n".join(parts).strip()
return ""
def _extract_images_from_message(msg: dict) -> dict:
"""
OpenRouter / farklı provider cevaplarından image url veya base64 çıkar.
UI'nin finalize_ai_response() fonksiyonunun anlayacağı formatı döndürür.
"""
out = {
"type": "text",
"content": "",
}
if not isinstance(msg, dict):
return out
content = msg.get("content")
text_parts = []
image_urls = []
image_b64 = []
# 1) content string ise
if isinstance(content, str):
if content.strip():
text_parts.append(content.strip())
# 2) content liste ise
elif isinstance(content, list):
for item in content:
if not isinstance(item, dict):
continue
itype = str(item.get("type") or "").strip().lower()
if itype == "text":
txt = item.get("text")
if isinstance(txt, str) and txt.strip():
text_parts.append(txt.strip())
elif itype in ("image_url", "output_image"):
iu = item.get("image_url")
if isinstance(iu, str) and iu.strip():
image_urls.append(iu.strip())
elif isinstance(iu, dict):
u = iu.get("url")
if isinstance(u, str) and u.strip():
image_urls.append(u.strip())
b64 = item.get("b64_json")
if isinstance(b64, str) and b64.strip():
image_b64.append(b64.strip())
data = item.get("image")
if isinstance(data, str) and data.strip():
image_b64.append(data.strip())
# 3) message level fallback alanlar
for key in ("image", "url"):
val = msg.get(key)
if isinstance(val, str) and val.strip():
image_urls.append(val.strip())
one_b64 = msg.get("image_base64")
if isinstance(one_b64, str) and one_b64.strip():
image_b64.append(one_b64.strip())
imgs = msg.get("images")
if isinstance(imgs, list):
for item in imgs:
if isinstance(item, str) and item.strip():
image_urls.append(item.strip())
elif isinstance(item, dict):
u = item.get("url")
if isinstance(u, str) and u.strip():
image_urls.append(u.strip())
iu = item.get("image_url")
if isinstance(iu, str) and iu.strip():
image_urls.append(iu.strip())
elif isinstance(iu, dict):
uu = iu.get("url")
if isinstance(uu, str) and uu.strip():
image_urls.append(uu.strip())
b64 = item.get("b64_json")
if isinstance(b64, str) and b64.strip():
image_b64.append(b64.strip())
# uniq
def _uniq(seq):
seen = set()
out2 = []
for x in seq:
if x not in seen:
seen.add(x)
out2.append(x)
return out2
image_urls = _uniq(image_urls)
image_b64 = _uniq(image_b64)
out["content"] = "\n".join(text_parts).strip()
if image_urls:
out["type"] = "image"
out["images"] = image_urls
if image_b64:
out["type"] = "image"
out["images_base64"] = image_b64
if image_urls and image_b64:
out["type"] = "image"
return out
def _is_image_generation_model(model_name: str) -> bool:
m = str(model_name or "").strip().lower()
return any(x in m for x in [
"image",
"gpt-image",
"imagen",
"flux",
"recraft",
"stable-diffusion"
])
def _pick_model(cfg: dict, chat_file: Path, cli_model: str | None) -> str:
"""
@@ -548,16 +690,17 @@ def main():
final_messages.append({"role": role, "content": last.get("content", "")})
# Payload
is_image_model = _is_image_generation_model(model_name)
payload = {
"model": model_name,
"messages": final_messages,
"max_tokens": 5120,
"stream": True
"stream": False if is_image_model else True
}
# Request (STREAMING)
reply_parts = []
usage_data = None
reply = ""
usage_obj = None
try:
response = requests.post(
@@ -570,64 +713,105 @@ def main():
},
json=payload,
timeout=120,
stream=True
stream=not is_image_model
)
if response.status_code != 200:
sys.stderr.write(f"OpenRouter API ERROR ({response.status_code}): {response.text}\n")
sys.exit(1)
# SSE'yi satır satır oku (en stabil yöntem)
for raw_line in response.iter_lines(decode_unicode=False, chunk_size=1024):
if not raw_line:
continue
if is_image_model:
data = response.json()
line = raw_line.strip()
if not line.startswith(b"data:"):
continue
usage_data = data.get("usage")
if usage_data:
prompt_t = int(usage_data.get("prompt_tokens", 0) or 0)
comp_t = int(usage_data.get("completion_tokens", 0) or 0)
total_t = int(usage_data.get("total_tokens", 0) or 0)
data_str = line[len(b"data:"):].strip()
if data_str == b"[DONE]":
break
usage_obj = {
"prompt_tokens": prompt_t,
"completion_tokens": comp_t,
"total_tokens": total_t
}
msg = {}
try:
evt = json.loads(data_str.decode("utf-8", errors="replace"))
if "usage" in evt:
usage_data = evt["usage"]
msg = data["choices"][0]["message"]
except Exception:
continue
msg = {}
result_obj = _extract_images_from_message(msg)
# hiç image yoksa düz text fallback
if not result_obj.get("content") and not result_obj.get("images") and not result_obj.get("images_base64"):
txt = _message_to_plain_text(msg.get("content"))
result_obj = {
"type": "text",
"content": fix_mojibake(txt)
}
if usage_obj is not None:
result_obj["usage"] = usage_obj
reply = json.dumps(result_obj, ensure_ascii=False)
sys.stdout.write(reply)
sys.stdout.flush()
else:
reply_parts = []
usage_data = None
for raw_line in response.iter_lines(decode_unicode=False, chunk_size=1024):
if not raw_line:
continue
line = raw_line.strip()
if not line.startswith(b"data:"):
continue
data_str = line[len(b"data:"):].strip()
if data_str == b"[DONE]":
break
try:
evt = json.loads(data_str.decode("utf-8", errors="replace"))
if "usage" in evt:
usage_data = evt["usage"]
except Exception:
continue
delta = None
try:
delta = evt["choices"][0]["delta"].get("content")
except Exception:
delta = None
try:
delta = evt["choices"][0]["delta"].get("content")
except Exception:
delta = None
if delta:
delta = fix_mojibake(delta)
reply_parts.append(delta)
sys.stdout.write(delta)
sys.stdout.flush()
if delta:
delta = fix_mojibake(delta)
reply_parts.append(delta)
sys.stdout.write(delta)
sys.stdout.flush()
reply = "".join(reply_parts).strip()
# token usage yazdır
usage_obj = None
reply = "".join(reply_parts).strip()
if usage_data:
prompt_t = int(usage_data.get("prompt_tokens", 0) or 0)
comp_t = int(usage_data.get("completion_tokens", 0) or 0)
total_t = int(usage_data.get("total_tokens", 0) or 0)
if usage_data:
prompt_t = int(usage_data.get("prompt_tokens", 0) or 0)
comp_t = int(usage_data.get("completion_tokens", 0) or 0)
total_t = int(usage_data.get("total_tokens", 0) or 0)
usage_obj = {
"prompt_tokens": prompt_t,
"completion_tokens": comp_t,
"total_tokens": total_t
}
usage_obj = {
"prompt_tokens": prompt_t,
"completion_tokens": comp_t,
"total_tokens": total_t
}
reply = fix_mojibake(reply)
if not reply:
sys.stderr.write("OpenRouter boş stream döndürdü.\n")
sys.exit(1)
reply = fix_mojibake(reply)
if not reply:
sys.stderr.write("OpenRouter boş stream döndürdü.\n")
sys.exit(1)
except requests.exceptions.Timeout:
sys.stderr.write("İstek zaman aşımına uğradı (timeout).\n")
@@ -640,30 +824,7 @@ def main():
except Exception as e:
sys.stderr.write(f"AI ERROR: {str(e)}\n")
sys.exit(1)
# Save bot reply
# UI tarafı streaming için placeholder bot mesajı eklediyse, ona yaz.
if messages and isinstance(messages[-1], dict) and messages[-1].get("role") in ("bot", "assistant") and messages[-1].get("streaming"):
messages[-1]["role"] = "bot"
messages[-1]["content"] = reply
messages[-1].pop("streaming", None)
# usage JSON'a kaydet
if usage_obj is not None:
messages[-1]["usage"] = usage_obj
else:
bot_msg: Dict[str, Any] = {"role": "bot", "content": reply}
if usage_obj is not None:
bot_msg["usage"] = usage_obj
messages.append(bot_msg)
try:
chat_file.write_text(json.dumps(messages, ensure_ascii=False, indent=2), encoding="utf-8")
except Exception as e:
sys.stderr.write(f"Chat yazılamadı: {e}\n")
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()