Commit
•
18b9041
1
Parent(s):
53d4809
Delete app.py
Browse files
app.py
DELETED
@@ -1,452 +0,0 @@
|
|
1 |
-
# -*- coding:utf-8 -*-
|
2 |
-
import os
|
3 |
-
import logging
|
4 |
-
import sys
|
5 |
-
|
6 |
-
import gradio as gr
|
7 |
-
|
8 |
-
from modules.utils import *
|
9 |
-
from modules.presets import *
|
10 |
-
from modules.overwrites import *
|
11 |
-
from modules.chat_func import *
|
12 |
-
from modules.openai_func import get_usage
|
13 |
-
|
14 |
-
logging.basicConfig(
|
15 |
-
level=logging.DEBUG,
|
16 |
-
format="%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s",
|
17 |
-
)
|
18 |
-
|
19 |
-
my_api_key = "" # 在这里输入你的 API 密钥
|
20 |
-
|
21 |
-
# if we are running in Docker
|
22 |
-
if os.environ.get("dockerrun") == "yes":
|
23 |
-
dockerflag = True
|
24 |
-
else:
|
25 |
-
dockerflag = False
|
26 |
-
|
27 |
-
authflag = False
|
28 |
-
auth_list = []
|
29 |
-
|
30 |
-
if not my_api_key:
|
31 |
-
my_api_key = os.environ.get("my_api_key")
|
32 |
-
if dockerflag:
|
33 |
-
if my_api_key == "empty":
|
34 |
-
logging.error("Please give a api key!")
|
35 |
-
sys.exit(1)
|
36 |
-
# auth
|
37 |
-
username = os.environ.get("USERNAME")
|
38 |
-
password = os.environ.get("PASSWORD")
|
39 |
-
if not (isinstance(username, type(None)) or isinstance(password, type(None))):
|
40 |
-
auth_list.append((os.environ.get("USERNAME"), os.environ.get("PASSWORD")))
|
41 |
-
authflag = True
|
42 |
-
else:
|
43 |
-
if (
|
44 |
-
not my_api_key
|
45 |
-
and os.path.exists("api_key.txt")
|
46 |
-
and os.path.getsize("api_key.txt")
|
47 |
-
):
|
48 |
-
with open("api_key.txt", "r") as f:
|
49 |
-
my_api_key = f.read().strip()
|
50 |
-
if os.path.exists("auth.json"):
|
51 |
-
authflag = True
|
52 |
-
with open("auth.json", "r", encoding='utf-8') as f:
|
53 |
-
auth = json.load(f)
|
54 |
-
for _ in auth:
|
55 |
-
if auth[_]["username"] and auth[_]["password"]:
|
56 |
-
auth_list.append((auth[_]["username"], auth[_]["password"]))
|
57 |
-
else:
|
58 |
-
logging.error("请检查auth.json文件中的用户名和密码!")
|
59 |
-
sys.exit(1)
|
60 |
-
|
61 |
-
gr.Chatbot.postprocess = postprocess
|
62 |
-
PromptHelper.compact_text_chunks = compact_text_chunks
|
63 |
-
|
64 |
-
with open("assets/custom.css", "r", encoding="utf-8") as f:
|
65 |
-
customCSS = f.read()
|
66 |
-
|
67 |
-
with gr.Blocks(css=customCSS, theme=small_and_beautiful_theme) as demo:
|
68 |
-
history = gr.State([])
|
69 |
-
token_count = gr.State([])
|
70 |
-
promptTemplates = gr.State(load_template(get_template_names(plain=True)[0], mode=2))
|
71 |
-
user_api_key = gr.State(my_api_key)
|
72 |
-
user_question = gr.State("")
|
73 |
-
outputing = gr.State(False)
|
74 |
-
topic = gr.State("未命名对话历史记录")
|
75 |
-
|
76 |
-
with gr.Row():
|
77 |
-
with gr.Column(scale=1):
|
78 |
-
gr.HTML(title)
|
79 |
-
with gr.Column(scale=4):
|
80 |
-
gr.HTML('<center><a href="https://huggingface.co/spaces/JohnSmith9982/ChuanhuChatGPT?duplicate=true"><img src="https://bit.ly/3gLdBN6" alt="Duplicate Space"></a>Duplicate the Space and run securely with your OpenAI API Key</center>')
|
81 |
-
with gr.Column(scale=4):
|
82 |
-
status_display = gr.Markdown(get_geoip(), elem_id="status_display")
|
83 |
-
|
84 |
-
with gr.Row().style(equal_height=True):
|
85 |
-
with gr.Column(scale=5):
|
86 |
-
with gr.Row():
|
87 |
-
chatbot = gr.Chatbot(elem_id="chuanhu_chatbot").style(height="100%")
|
88 |
-
with gr.Row():
|
89 |
-
with gr.Column(scale=12):
|
90 |
-
user_input = gr.Textbox(
|
91 |
-
show_label=False, placeholder="在这里输入"
|
92 |
-
).style(container=False)
|
93 |
-
with gr.Column(min_width=70, scale=1):
|
94 |
-
submitBtn = gr.Button("发送", variant="primary")
|
95 |
-
cancelBtn = gr.Button("取消", variant="secondary", visible=False)
|
96 |
-
with gr.Row():
|
97 |
-
emptyBtn = gr.Button(
|
98 |
-
"🧹 新的对话",
|
99 |
-
)
|
100 |
-
retryBtn = gr.Button("🔄 重新生成")
|
101 |
-
delFirstBtn = gr.Button("🗑️ 删除最旧对话")
|
102 |
-
delLastBtn = gr.Button("🗑️ 删除最新对话")
|
103 |
-
reduceTokenBtn = gr.Button("♻️ 总结对话")
|
104 |
-
|
105 |
-
with gr.Column():
|
106 |
-
with gr.Column(min_width=50, scale=1):
|
107 |
-
with gr.Tab(label="ChatGPT"):
|
108 |
-
keyTxt = gr.Textbox(
|
109 |
-
show_label=True,
|
110 |
-
placeholder=f"OpenAI API-key...",
|
111 |
-
value=hide_middle_chars(my_api_key),
|
112 |
-
type="password",
|
113 |
-
visible=not HIDE_MY_KEY,
|
114 |
-
label="API-Key",
|
115 |
-
)
|
116 |
-
usageTxt = gr.Markdown("**发送消息** 或 **提交key** 以显示额度", elem_id="usage_display")
|
117 |
-
model_select_dropdown = gr.Dropdown(
|
118 |
-
label="选择模型", choices=MODELS, multiselect=False, value=MODELS[0]
|
119 |
-
)
|
120 |
-
use_streaming_checkbox = gr.Checkbox(
|
121 |
-
label="实时传输回答", value=True, visible=enable_streaming_option
|
122 |
-
)
|
123 |
-
use_websearch_checkbox = gr.Checkbox(label="使用在线搜索", value=False)
|
124 |
-
language_select_dropdown = gr.Dropdown(
|
125 |
-
label="选择回复语言(针对搜索&索引功能)",
|
126 |
-
choices=REPLY_LANGUAGES,
|
127 |
-
multiselect=False,
|
128 |
-
value=REPLY_LANGUAGES[0],
|
129 |
-
)
|
130 |
-
index_files = gr.Files(label="上传索引文件", type="file", multiple=True)
|
131 |
-
|
132 |
-
with gr.Tab(label="Prompt"):
|
133 |
-
systemPromptTxt = gr.Textbox(
|
134 |
-
show_label=True,
|
135 |
-
placeholder=f"在这里输入System Prompt...",
|
136 |
-
label="System prompt",
|
137 |
-
value=initial_prompt,
|
138 |
-
lines=10,
|
139 |
-
).style(container=False)
|
140 |
-
with gr.Accordion(label="加载Prompt模板", open=True):
|
141 |
-
with gr.Column():
|
142 |
-
with gr.Row():
|
143 |
-
with gr.Column(scale=6):
|
144 |
-
templateFileSelectDropdown = gr.Dropdown(
|
145 |
-
label="选择Prompt模板集合文件",
|
146 |
-
choices=get_template_names(plain=True),
|
147 |
-
multiselect=False,
|
148 |
-
value=get_template_names(plain=True)[0],
|
149 |
-
).style(container=False)
|
150 |
-
with gr.Column(scale=1):
|
151 |
-
templateRefreshBtn = gr.Button("🔄 刷新")
|
152 |
-
with gr.Row():
|
153 |
-
with gr.Column():
|
154 |
-
templateSelectDropdown = gr.Dropdown(
|
155 |
-
label="从Prompt模板中加载",
|
156 |
-
choices=load_template(
|
157 |
-
get_template_names(plain=True)[0], mode=1
|
158 |
-
),
|
159 |
-
multiselect=False,
|
160 |
-
value=load_template(
|
161 |
-
get_template_names(plain=True)[0], mode=1
|
162 |
-
)[0],
|
163 |
-
).style(container=False)
|
164 |
-
|
165 |
-
with gr.Tab(label="保存/加载"):
|
166 |
-
with gr.Accordion(label="保存/加载对话历史记录", open=True):
|
167 |
-
with gr.Column():
|
168 |
-
with gr.Row():
|
169 |
-
with gr.Column(scale=6):
|
170 |
-
historyFileSelectDropdown = gr.Dropdown(
|
171 |
-
label="从列表中加载对话",
|
172 |
-
choices=get_history_names(plain=True),
|
173 |
-
multiselect=False,
|
174 |
-
value=get_history_names(plain=True)[0],
|
175 |
-
)
|
176 |
-
with gr.Column(scale=1):
|
177 |
-
historyRefreshBtn = gr.Button("🔄 刷新")
|
178 |
-
with gr.Row():
|
179 |
-
with gr.Column(scale=6):
|
180 |
-
saveFileName = gr.Textbox(
|
181 |
-
show_label=True,
|
182 |
-
placeholder=f"设置文件名: 默认为.json,可选为.md",
|
183 |
-
label="设置保存文件名",
|
184 |
-
value="对话历史记录",
|
185 |
-
).style(container=True)
|
186 |
-
with gr.Column(scale=1):
|
187 |
-
saveHistoryBtn = gr.Button("💾 保存对话")
|
188 |
-
exportMarkdownBtn = gr.Button("📝 导出为Markdown")
|
189 |
-
gr.Markdown("默认保存于history文件夹")
|
190 |
-
with gr.Row():
|
191 |
-
with gr.Column():
|
192 |
-
downloadFile = gr.File(interactive=True)
|
193 |
-
|
194 |
-
with gr.Tab(label="高级"):
|
195 |
-
gr.Markdown("# ⚠️ 务必谨慎更改 ⚠️\n\n如果无法使用请恢复默认设置")
|
196 |
-
default_btn = gr.Button("🔙 恢复默认设置")
|
197 |
-
|
198 |
-
with gr.Accordion("参数", open=False):
|
199 |
-
top_p = gr.Slider(
|
200 |
-
minimum=-0,
|
201 |
-
maximum=1.0,
|
202 |
-
value=1.0,
|
203 |
-
step=0.05,
|
204 |
-
interactive=True,
|
205 |
-
label="Top-p",
|
206 |
-
)
|
207 |
-
temperature = gr.Slider(
|
208 |
-
minimum=-0,
|
209 |
-
maximum=2.0,
|
210 |
-
value=1.0,
|
211 |
-
step=0.1,
|
212 |
-
interactive=True,
|
213 |
-
label="Temperature",
|
214 |
-
)
|
215 |
-
|
216 |
-
with gr.Accordion("网络设置", open=False, visible=False):
|
217 |
-
apiurlTxt = gr.Textbox(
|
218 |
-
show_label=True,
|
219 |
-
placeholder=f"在这里输入API地址...",
|
220 |
-
label="API地址",
|
221 |
-
value="https://api.openai.com/v1/chat/completions",
|
222 |
-
lines=2,
|
223 |
-
)
|
224 |
-
changeAPIURLBtn = gr.Button("🔄 切换API地址")
|
225 |
-
proxyTxt = gr.Textbox(
|
226 |
-
show_label=True,
|
227 |
-
placeholder=f"在这里输入代理地址...",
|
228 |
-
label="代理地址(示例:http://127.0.0.1:10809)",
|
229 |
-
value="",
|
230 |
-
lines=2,
|
231 |
-
)
|
232 |
-
changeProxyBtn = gr.Button("🔄 设置代理地址")
|
233 |
-
|
234 |
-
gr.Markdown(description)
|
235 |
-
gr.HTML(footer.format(versions=versions_html()), elem_id="footer")
|
236 |
-
chatgpt_predict_args = dict(
|
237 |
-
fn=predict,
|
238 |
-
inputs=[
|
239 |
-
user_api_key,
|
240 |
-
systemPromptTxt,
|
241 |
-
history,
|
242 |
-
user_question,
|
243 |
-
chatbot,
|
244 |
-
token_count,
|
245 |
-
top_p,
|
246 |
-
temperature,
|
247 |
-
use_streaming_checkbox,
|
248 |
-
model_select_dropdown,
|
249 |
-
use_websearch_checkbox,
|
250 |
-
index_files,
|
251 |
-
language_select_dropdown,
|
252 |
-
],
|
253 |
-
outputs=[chatbot, history, status_display, token_count],
|
254 |
-
show_progress=True,
|
255 |
-
)
|
256 |
-
|
257 |
-
start_outputing_args = dict(
|
258 |
-
fn=start_outputing,
|
259 |
-
inputs=[],
|
260 |
-
outputs=[submitBtn, cancelBtn],
|
261 |
-
show_progress=True,
|
262 |
-
)
|
263 |
-
|
264 |
-
end_outputing_args = dict(
|
265 |
-
fn=end_outputing, inputs=[], outputs=[submitBtn, cancelBtn]
|
266 |
-
)
|
267 |
-
|
268 |
-
reset_textbox_args = dict(
|
269 |
-
fn=reset_textbox, inputs=[], outputs=[user_input]
|
270 |
-
)
|
271 |
-
|
272 |
-
transfer_input_args = dict(
|
273 |
-
fn=transfer_input, inputs=[user_input], outputs=[user_question, user_input, submitBtn, cancelBtn], show_progress=True
|
274 |
-
)
|
275 |
-
|
276 |
-
get_usage_args = dict(
|
277 |
-
fn=get_usage, inputs=[user_api_key], outputs=[usageTxt], show_progress=False
|
278 |
-
)
|
279 |
-
|
280 |
-
|
281 |
-
# Chatbot
|
282 |
-
cancelBtn.click(cancel_outputing, [], [])
|
283 |
-
|
284 |
-
user_input.submit(**transfer_input_args).then(**chatgpt_predict_args).then(**end_outputing_args)
|
285 |
-
user_input.submit(**get_usage_args)
|
286 |
-
|
287 |
-
submitBtn.click(**transfer_input_args).then(**chatgpt_predict_args).then(**end_outputing_args)
|
288 |
-
submitBtn.click(**get_usage_args)
|
289 |
-
|
290 |
-
emptyBtn.click(
|
291 |
-
reset_state,
|
292 |
-
outputs=[chatbot, history, token_count, status_display],
|
293 |
-
show_progress=True,
|
294 |
-
)
|
295 |
-
emptyBtn.click(**reset_textbox_args)
|
296 |
-
|
297 |
-
retryBtn.click(**start_outputing_args).then(
|
298 |
-
retry,
|
299 |
-
[
|
300 |
-
user_api_key,
|
301 |
-
systemPromptTxt,
|
302 |
-
history,
|
303 |
-
chatbot,
|
304 |
-
token_count,
|
305 |
-
top_p,
|
306 |
-
temperature,
|
307 |
-
use_streaming_checkbox,
|
308 |
-
model_select_dropdown,
|
309 |
-
language_select_dropdown,
|
310 |
-
],
|
311 |
-
[chatbot, history, status_display, token_count],
|
312 |
-
show_progress=True,
|
313 |
-
).then(**end_outputing_args)
|
314 |
-
retryBtn.click(**get_usage_args)
|
315 |
-
|
316 |
-
delFirstBtn.click(
|
317 |
-
delete_first_conversation,
|
318 |
-
[history, token_count],
|
319 |
-
[history, token_count, status_display],
|
320 |
-
)
|
321 |
-
|
322 |
-
delLastBtn.click(
|
323 |
-
delete_last_conversation,
|
324 |
-
[chatbot, history, token_count],
|
325 |
-
[chatbot, history, token_count, status_display],
|
326 |
-
show_progress=True,
|
327 |
-
)
|
328 |
-
|
329 |
-
reduceTokenBtn.click(
|
330 |
-
reduce_token_size,
|
331 |
-
[
|
332 |
-
user_api_key,
|
333 |
-
systemPromptTxt,
|
334 |
-
history,
|
335 |
-
chatbot,
|
336 |
-
token_count,
|
337 |
-
top_p,
|
338 |
-
temperature,
|
339 |
-
gr.State(sum(token_count.value[-4:])),
|
340 |
-
model_select_dropdown,
|
341 |
-
language_select_dropdown,
|
342 |
-
],
|
343 |
-
[chatbot, history, status_display, token_count],
|
344 |
-
show_progress=True,
|
345 |
-
)
|
346 |
-
reduceTokenBtn.click(**get_usage_args)
|
347 |
-
|
348 |
-
# ChatGPT
|
349 |
-
keyTxt.change(submit_key, keyTxt, [user_api_key, status_display]).then(**get_usage_args)
|
350 |
-
keyTxt.submit(**get_usage_args)
|
351 |
-
|
352 |
-
# Template
|
353 |
-
templateRefreshBtn.click(get_template_names, None, [templateFileSelectDropdown])
|
354 |
-
templateFileSelectDropdown.change(
|
355 |
-
load_template,
|
356 |
-
[templateFileSelectDropdown],
|
357 |
-
[promptTemplates, templateSelectDropdown],
|
358 |
-
show_progress=True,
|
359 |
-
)
|
360 |
-
templateSelectDropdown.change(
|
361 |
-
get_template_content,
|
362 |
-
[promptTemplates, templateSelectDropdown, systemPromptTxt],
|
363 |
-
[systemPromptTxt],
|
364 |
-
show_progress=True,
|
365 |
-
)
|
366 |
-
|
367 |
-
# S&L
|
368 |
-
saveHistoryBtn.click(
|
369 |
-
save_chat_history,
|
370 |
-
[saveFileName, systemPromptTxt, history, chatbot],
|
371 |
-
downloadFile,
|
372 |
-
show_progress=True,
|
373 |
-
)
|
374 |
-
saveHistoryBtn.click(get_history_names, None, [historyFileSelectDropdown])
|
375 |
-
exportMarkdownBtn.click(
|
376 |
-
export_markdown,
|
377 |
-
[saveFileName, systemPromptTxt, history, chatbot],
|
378 |
-
downloadFile,
|
379 |
-
show_progress=True,
|
380 |
-
)
|
381 |
-
historyRefreshBtn.click(get_history_names, None, [historyFileSelectDropdown])
|
382 |
-
historyFileSelectDropdown.change(
|
383 |
-
load_chat_history,
|
384 |
-
[historyFileSelectDropdown, systemPromptTxt, history, chatbot],
|
385 |
-
[saveFileName, systemPromptTxt, history, chatbot],
|
386 |
-
show_progress=True,
|
387 |
-
)
|
388 |
-
downloadFile.change(
|
389 |
-
load_chat_history,
|
390 |
-
[downloadFile, systemPromptTxt, history, chatbot],
|
391 |
-
[saveFileName, systemPromptTxt, history, chatbot],
|
392 |
-
)
|
393 |
-
|
394 |
-
# Advanced
|
395 |
-
default_btn.click(
|
396 |
-
reset_default, [], [apiurlTxt, proxyTxt, status_display], show_progress=True
|
397 |
-
)
|
398 |
-
changeAPIURLBtn.click(
|
399 |
-
change_api_url,
|
400 |
-
[apiurlTxt],
|
401 |
-
[status_display],
|
402 |
-
show_progress=True,
|
403 |
-
)
|
404 |
-
changeProxyBtn.click(
|
405 |
-
change_proxy,
|
406 |
-
[proxyTxt],
|
407 |
-
[status_display],
|
408 |
-
show_progress=True,
|
409 |
-
)
|
410 |
-
|
411 |
-
logging.info(
|
412 |
-
colorama.Back.GREEN
|
413 |
-
+ "\n川虎的温馨提示:访问 http://localhost:7860 查看界面"
|
414 |
-
+ colorama.Style.RESET_ALL
|
415 |
-
)
|
416 |
-
# 默认开启本地服务器,默认可以直接从IP访问,默认不创建公开分享链接
|
417 |
-
demo.title = "川虎ChatGPT 🚀"
|
418 |
-
|
419 |
-
if __name__ == "__main__":
|
420 |
-
reload_javascript()
|
421 |
-
# if running in Docker
|
422 |
-
if dockerflag:
|
423 |
-
if authflag:
|
424 |
-
demo.queue(concurrency_count=CONCURRENT_COUNT).launch(
|
425 |
-
server_name="0.0.0.0",
|
426 |
-
server_port=7860,
|
427 |
-
auth=auth_list,
|
428 |
-
favicon_path="./assets/favicon.ico",
|
429 |
-
)
|
430 |
-
else:
|
431 |
-
demo.queue(concurrency_count=CONCURRENT_COUNT).launch(
|
432 |
-
server_name="0.0.0.0",
|
433 |
-
server_port=7860,
|
434 |
-
share=False,
|
435 |
-
favicon_path="./assets/favicon.ico",
|
436 |
-
)
|
437 |
-
# if not running in Docker
|
438 |
-
else:
|
439 |
-
if authflag:
|
440 |
-
demo.queue(concurrency_count=CONCURRENT_COUNT).launch(
|
441 |
-
share=False,
|
442 |
-
auth=auth_list,
|
443 |
-
favicon_path="./assets/favicon.ico",
|
444 |
-
inbrowser=True,
|
445 |
-
)
|
446 |
-
else:
|
447 |
-
demo.queue(concurrency_count=CONCURRENT_COUNT).launch(
|
448 |
-
share=False, favicon_path="./assets/favicon.ico", inbrowser=True
|
449 |
-
) # 改为 share=True 可以创建公开分享链接
|
450 |
-
# demo.queue(concurrency_count=CONCURRENT_COUNT).launch(server_name="0.0.0.0", server_port=7860, share=False) # 可自定义端口
|
451 |
-
# demo.queue(concurrency_count=CONCURRENT_COUNT).launch(server_name="0.0.0.0", server_port=7860,auth=("在这里填写用户名", "在这里填写密码")) # 可设置用户名与密码
|
452 |
-
# demo.queue(concurrency_count=CONCURRENT_COUNT).launch(auth=("在这里填写用户名", "在这里填写密码")) # 适合Nginx反向代理
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|