File size: 7,328 Bytes
6a6db8a
d2001c1
 
b34c488
d2001c1
 
 
5f3f107
96f986b
5f3f107
139c357
d2001c1
5f3f107
d2001c1
 
96f986b
 
d2001c1
96f986b
 
 
d2001c1
 
 
b34c488
d2001c1
96f986b
d2001c1
96f986b
16c954d
 
d2001c1
16c954d
 
139c357
d2001c1
 
16c954d
 
139c357
d2001c1
16c954d
 
 
 
 
 
 
 
 
 
 
bfe7b82
 
 
 
 
16c954d
 
bfe7b82
b34c488
16c954d
 
d2001c1
16c954d
 
 
 
 
bfe7b82
16c954d
 
bfe7b82
 
16c954d
 
 
 
 
 
bfe7b82
16c954d
b34c488
 
 
16c954d
 
578098f
d2001c1
16c954d
d2001c1
139c357
 
 
16c954d
 
139c357
 
16c954d
 
 
139c357
b34c488
139c357
 
16c954d
 
 
139c357
fe0c240
16c954d
 
 
 
 
 
 
 
 
 
 
 
96f986b
 
139c357
d2001c1
139c357
16c954d
d2001c1
 
 
16c954d
 
 
 
 
 
0c5a7f8
d2001c1
 
16c954d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96f986b
16c954d
 
 
 
 
 
 
96f986b
 
d2001c1
16c954d
 
 
 
 
 
 
 
 
139c357
16c954d
 
 
 
 
 
 
d2001c1
16c954d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96f986b
d2001c1
16c954d
 
 
 
 
 
 
 
d2001c1
 
 
16c954d
 
 
 
d2001c1
96f986b
d2001c1
 
 
16c954d
 
 
 
 
d2001c1
 
139c357
d2001c1
16c954d
d2001c1
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
import gradio as gr
import time
import base64
from openai import OpenAI
import os
from io import BytesIO
from PIL import Image

# 配置
BASE_URL = "https://api.stepfun.com/v1"
# 从环境变量获取API密钥
STEP_API_KEY = os.environ.get("STEP_API_KEY", "")

# 可选模型
MODELS = ["step-3", "step-r1-v-mini"]

def image_to_base64(image):
    """将PIL图像转换为base64字符串"""
    if image is None:
        return None
    
    if isinstance(image, Image.Image):
        buffered = BytesIO()
        image.save(buffered, format="PNG")
        img_str = base64.b64encode(buffered.getvalue()).decode('utf-8')
        return img_str
    
    return None

def call_step_api_stream(message, history, model, temperature, max_tokens, image=None):
    """调用Step API进行流式对话"""
    
    if not message and not image:
        yield history
        return
    
    if not STEP_API_KEY:
        history.append([message or "图片", "❌ API密钥未配置。请在 Settings 中添加 STEP_API_KEY。"])
        yield history
        return
    
    # 构造消息历史
    messages = []
    
    # 添加历史对话
    for h in history:
        if h[0]:  # 用户消息
            messages.append({"role": "user", "content": h[0]})
        if h[1]:  # 助手回复
            messages.append({"role": "assistant", "content": h[1]})
    
    # 构造当前消息
    if image is not None:
        # 有图片的情况
        try:
            base64_image = image_to_base64(image)
            if base64_image is None:
                history.append([message or "图片", "❌ 图片处理失败"])
                yield history
                return
            
            current_content = [
                {"type": "image_url", "image_url": {"url": f"data:image/jpg;base64,{base64_image}", "detail": "high"}}
            ]
            if message:
                current_content.append({"type": "text", "text": message})
            
            messages.append({"role": "user", "content": current_content})
            display_message = f"[图片] {message}" if message else "[图片]"
        except Exception as e:
            history.append([message or "图片", f"❌ 图片处理错误: {str(e)}"])
            yield history
            return
    else:
        # 纯文本
        messages.append({"role": "user", "content": message})
        display_message = message
    
    # 添加到历史记录
    history.append([display_message, ""])
    
    # 创建客户端
    try:
        client = OpenAI(api_key=STEP_API_KEY, base_url=BASE_URL)
    except Exception as e:
        history[-1][1] = f"❌ 客户端初始化失败: {str(e)}"
        yield history
        return
    
    # 调用API
    try:
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
            max_tokens=max_tokens,
            stream=True
        )
        
        # 处理流式响应
        full_response = ""
        for chunk in response:
            if chunk.choices and len(chunk.choices) > 0:
                delta = chunk.choices[0].delta
                if hasattr(delta, 'content') and delta.content:
                    full_response += delta.content
                    history[-1][1] = full_response
                    yield history
                    
    except Exception as e:
        history[-1][1] = f"❌ API请求失败: {str(e)}"
        yield history

def user_input(message, history, image):
    """处理用户输入"""
    if message or image:
        return "", history, None
    return message, history, image

def clear_history():
    """清空对话历史"""
    return [], None, ""

# 创建Gradio界面
with gr.Blocks(title="Step-3", theme=gr.themes.Soft()) as demo:
    gr.Markdown("""
    # 🤖 Step-3
    Hello, I am Step-3! 
    """)
    
    with gr.Row():
        with gr.Column(scale=3):
            # 对话界面
            chatbot = gr.Chatbot(
                height=500,
                show_label=False,
                elem_id="chatbot",
                bubble_full_width=False
            )
            
            with gr.Row():
                with gr.Column(scale=8):
                    msg = gr.Textbox(
                        label="输入消息",
                        placeholder="输入你的问题...",
                        lines=1,
                        max_lines=5,
                        show_label=False,
                        elem_id="msg",
                        container=False
                    )
                with gr.Column(scale=1, min_width=100):
                    submit_btn = gr.Button("发送", variant="primary")
                with gr.Column(scale=1, min_width=100):
                    clear_btn = gr.Button("清空对话")
            
            # 图片上传
            with gr.Row():
                image_input = gr.Image(
                    label="上传图片(可选)",
                    type="pil",
                    height=150,
                    scale=1
                )
        
        with gr.Column(scale=1):
            # 设置面板
            gr.Markdown("### ⚙️ 设置")
            
            model_select = gr.Dropdown(
                choices=MODELS,
                value=MODELS[0],
                label="模型选择",
                interactive=True
            )
            
            temperature_slider = gr.Slider(
                minimum=0,
                maximum=1,
                value=0.7,
                step=0.1,
                label="Temperature",
                interactive=True
            )
            
            max_tokens_slider = gr.Slider(
                minimum=100,
                maximum=4000,
                value=2000,
                step=100,
                label="最大输出长度",
                interactive=True
            )
            
            gr.Markdown("""
            ### 📝 使用说明
            - 支持多轮对话
            - 可上传图片进行分析
            - 支持纯文本对话
            - 历史记录会保留上下文
            """)
    
    # 事件处理
    msg.submit(
        user_input,
        [msg, chatbot, image_input],
        [msg, chatbot, image_input],
        queue=False
    ).then(
        call_step_api_stream,
        [msg, chatbot, model_select, temperature_slider, max_tokens_slider, image_input],
        chatbot
    )
    
    submit_btn.click(
        user_input,
        [msg, chatbot, image_input],
        [msg, chatbot, image_input],
        queue=False
    ).then(
        call_step_api_stream,
        [msg, chatbot, model_select, temperature_slider, max_tokens_slider, image_input],
        chatbot
    )
    
    clear_btn.click(
        clear_history,
        None,
        [chatbot, image_input, msg],
        queue=False
    )
    
    # 页脚
    gr.Markdown("""
    ---
    <div style="text-align: center;">
        <img src="https://huggingface.co/stepfun-ai/step3/resolve/main/figures/stepfun-logo.png" alt="StepFun Logo" style="height: 40px; margin: 10px;">
        <br>
        Powered by <a href="https://www.stepfun.com/" target="_blank">StepFun</a>
    </div>
    """)

# 启动应用
if __name__ == "__main__":
    demo.queue()
    demo.launch()