File size: 6,481 Bytes
6abc72b
9e973d6
6abc72b
9e973d6
 
 
ea7a010
9e973d6
 
6abc72b
028a160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5ca8b8
9e973d6
 
ea7a010
6abc72b
 
179e141
 
6abc72b
9e973d6
179e141
9e973d6
 
ea7a010
9b2b791
 
 
252ceca
a272337
 
9b2b791
 
 
 
028a160
 
 
 
 
 
252ceca
 
9b2b791
252ceca
9b2b791
 
 
 
9e973d6
252ceca
9e973d6
 
 
ea7a010
 
9b2b791
d09ee43
028a160
 
 
 
 
3c8be11
028a160
 
252ceca
9b2b791
028a160
 
 
 
 
 
 
 
 
 
 
 
 
 
252ceca
028a160
252ceca
a272337
d09ee43
252ceca
9e973d6
 
 
 
252ceca
 
 
 
 
 
028a160
252ceca
028a160
252ceca
 
d09ee43
252ceca
 
 
6abc72b
36a16e0
9e973d6
 
6abc72b
 
 
252ceca
 
 
3c8be11
d09ee43
252ceca
 
36a16e0
ea7a010
a5ca8b8
 
028a160
 
9b2b791
a5ca8b8
6abc72b
 
16da1b8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import os
import base64
import gradio as gr
from openai import OpenAI

def encode_image(image_path):
    """将本地图片文件转换为 Base64 编码"""
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

def detect_language(text, history):
    """
    智能检测用户语言:
    1. 优先从当前提问检测
    2. 若当前无文字,从最近一轮用户历史中推断
    3. 默认返回中文
    """
    # 1. 检查当前提问
    if text:
        clean_text = str(text).strip()
        if clean_text:
            # 计算中文字符和英文字母比例
            chinese_chars = sum(1 for char in clean_text if '\u4e00' <= char <= '\u9fff')
            alpha_chars = sum(1 for char in clean_text if char.isalpha())
            
            if alpha_chars > 0:
                # 中文字符占比 > 30% 判定为中文
                if chinese_chars / alpha_chars > 0.3:
                    return "zh"
                else:
                    return "en"
            else:
                # 没有字母(纯数字/符号),默认中文
                return "zh"
    
    # 2. 当前无文字,从历史中推断
    for msg in reversed(history[-6:]):
        if msg.get("role") == "user":
            content = msg.get("content", "")
            if isinstance(content, dict):
                content = content.get("text", "")
            if content:
                clean_content = str(content).strip()
                chinese_chars = sum(1 for char in clean_content if '\u4e00' <= char <= '\u9fff')
                alpha_chars = sum(1 for char in clean_content if char.isalpha())
                if alpha_chars > 0 and chinese_chars / alpha_chars > 0.3:
                    return "zh"
                elif alpha_chars > 0:
                    return "en"
    
    # 3. 默认中文
    return "zh"

def predict(message, history):
    api_key = os.getenv("SILICONFLOW_API_KEY", "")
    if not api_key:
        yield "❌ 未检测到 API Key!请先在 Space Settings -> Secrets 中添加 SILICONFLOW_API_KEY!"
        return

    api_key = api_key.strip()

    try:
        client = OpenAI(
            api_key=api_key,
            base_url="https://api.siliconflow.cn/v1"
        )

        text_content = ""
        files = []

        # Gradio 多模态输入解析
        if isinstance(message, dict):
            text_content = message.get("text", "")
            files = message.get("files", [])
        else:
            text_content = str(message)

        # 智能检测语言(含历史记忆)
        user_lang = detect_language(text_content, history)
        
        # 🔥 调试输出(可删除)
        print(f"检测到语言: {user_lang}, 用户输入: {text_content[:50]}")

        # 构建当前请求的 Message Payload
        current_content = []

        # 1. 解析当前上传的图片
        for f in files:
            file_path = f.get("path") if isinstance(f, dict) else f
            if file_path:
                try:
                    base64_img = encode_image(file_path)
                    current_content.append({
                        "type": "image_url",
                        "image_url": {"url": f"data:image/jpeg;base64,{base64_img}"}
                    })
                except Exception as img_err:
                    print(f"图片读取失败: {img_err}")

        # 2. 解析文本说明
        if not text_content or not str(text_content).strip():
            if user_lang == "en":
                prompt_text = "Please observe this image carefully and tell me what plant this is and its key features."
            else:
                prompt_text = "请仔细观察这张图片,告诉我这是什么植物?有哪些特征?"
        else:
            prompt_text = str(text_content).strip()

        current_content.append({"type": "text", "text": prompt_text})

        # 3. 根据用户语言设置 System Prompt
        if user_lang == "en":
            system_prompt = (
                "You are a professional botanist and nature expert. "
                "Identify the plant in the image and provide its scientific name, "
                "family, genus, and key features in **English**. "
                "Be concise and accurate."
            )
        else:
            system_prompt = (
                "你是一位专业的植物学家和自然科普专家。"
                "请精准识别图片中的植物,并用简洁明了的中文回答其名称、科属及特点。"
            )

        messages = [
            {"role": "system", "content": system_prompt}
        ]

        # 仅保留最近 2 轮纯文本历史
        for msg in history[-4:]:
            role = "user" if msg.get("role") == "user" else "assistant"
            content_val = msg.get("content", "")
            if isinstance(content_val, dict):
                content_val = content_val.get("text", "")
            if content_val and not str(content_val).startswith("data:image"):
                messages.append({"role": role, "content": str(content_val)})

        # 放入最新一轮包含图片的请求
        messages.append({"role": "user", "content": current_content})

        # 4. 调用视觉模型
        response = client.chat.completions.create(
            model="Qwen/Qwen3-VL-32B-Instruct",
            messages=messages,
            stream=True,
            temperature=0.2
        )

        # 5. 流式输出
        partial_text = ""
        for chunk in response:
            if chunk.choices and chunk.choices[0].delta.content:
                partial_text += chunk.choices[0].delta.content
                yield partial_text

    except Exception as e:
        err_msg = str(e)
        if "30001" in err_msg or "insufficient" in err_msg.lower():
            yield "⚠️ **请求失败:硅基流动账号余额不足**"
        elif "30003" in err_msg or "Model disabled" in err_msg:
            yield "⚠️ **模型不可用,请检查模型名称是否正确**"
        else:
            yield f"❌ 请求失败: {err_msg}"

# 创建 Gradio 界面
demo = gr.ChatInterface(
    fn=predict,
    title="🌱 AI 植物识别专家 (智能双语)",
    description="🌍 智能识别你的提问语言:中文问→中文答,英文问→英文答。连续对话自动记忆语言偏好!",
    multimodal=True
)

if __name__ == "__main__":
    demo.launch(ssr_mode=False)