import gradio as gr import requests import json import base64 from io import BytesIO from PIL import Image import os API_BASE = "https://www.gpt4novel.com/api/xiaoshuoai/ext/v1" def get_models(api_key): try: r = requests.get(f"{API_BASE}/models", headers={"Authorization": f"Bearer {api_key}"}) models = [m["id"] for m in r.json()["data"]] return sorted(models, reverse=True) except: return ["nalang-xl-0826-10k"] def extract_card_from_png(file_obj): if not file_obj.name.endswith(".png"): return None img = Image.open(file_obj.name) data = img.info.get("chara") if data: decoded = base64.b64decode(data).decode() return json.loads(decoded) return None def chat(message, history, model, api_key, system_prompt): messages = [{"role": "system", "content": system_prompt or "你是一个有帮助的AI助手。"}] for h in history: messages.append({"role": "user", "content": h[0]}) if h[1]: messages.append({"role": "assistant", "content": h[1]}) messages.append({"role": "user", "content": message}) resp = requests.post( f"{API_BASE}/chat/completions", json={"model": model, "messages": messages, "stream": True, "temperature": 0.7, "max_tokens": 800}, headers={"Authorization": f"Bearer {api_key}"}, stream=True, timeout=60 ) resp.raise_for_status() for line in resp.iter_lines(): if line and line.startswith(b"data: "): try: data = json.loads(line[6:].decode()) if token := data["choices"][0]["delta"].get("content"): yield token except: continue with gr.Blocks() as demo: gr.HTML(open("index.html").read()) chatbot = gr.Chatbot(height="70vh") with gr.Row(): msg = gr.Textbox(scale=8, placeholder="发送消息...", container=False) send = gr.Button("发送", scale=1) model = gr.Dropdown(choices=[], label="模型") api_key = gr.Textbox(label="API Key", type="password") card_file = gr.File(label="导入角色卡 (.json 或 .png)", file_types=[".json", ".png"]) system_prompt = gr.Textbox(label="系统提示词(角色卡自动填充)", lines=4, value="你是一个有帮助的AI助手。") def load_card(file): if not file: return "", [] if str(file).endswith(".png"): card = extract_card_from_png(file) else: card = json.load(open(file.name, encoding="utf-8")) if card: prompt = card.get("data", card.get("system_prompt", card.get("description", ""))) name = card.get("name", card.get("data", {}).get("name", "角色")) return prompt, [[None, f"已加载角色卡:{name}"]] return "", [] card_file.change(load_card, card_file, [system_prompt, chatbot]) def connect_key(key): models = get_models(key) return gr.Dropdown(choices=models, value=models[0] if models else None) api_key.submit(connect_key, api_key, model) send.click(chat, [msg, chatbot, model, api_key, system_prompt], chatbot).then(lambda: "", None, msg) msg.submit(chat, [msg, chatbot, model, api_key, system_prompt], chatbot).then(lambda: "", None, msg) demo.queue(max_size=20).launch(server_name="0.0.0.0", server_port=7860)