tech-envision commited on
Commit
6f9e1c0
·
1 Parent(s): e575a5e

Add Gradio frontend with HuggingFace login

Browse files
Files changed (2) hide show
  1. frontend.py +200 -0
  2. requirements.txt +3 -0
frontend.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio UI for llm-backend."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Iterator, List, Optional
7
+ import typing
8
+
9
+ import gradio as gr
10
+ from huggingface_hub import HfApi, login
11
+
12
+ from src.config import UPLOAD_DIR
13
+ from src.db import list_sessions_info, reset_history
14
+ from src.team import TeamChatSession
15
+
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Authentication helpers
19
+ # ---------------------------------------------------------------------------
20
+
21
+ def hf_login(token: str) -> str:
22
+ """Login to HuggingFace and return the username."""
23
+ login(token=token, new_session=True)
24
+ info = HfApi().whoami(token=token)
25
+ return info.get("name") or info.get("email")
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # File helpers
30
+ # ---------------------------------------------------------------------------
31
+
32
+ def _vm_host_path(user: str, vm_path: str) -> Path:
33
+ rel = Path(vm_path).relative_to("/data")
34
+ base = Path(UPLOAD_DIR) / user
35
+ target = (base / rel).resolve()
36
+ if not str(target).startswith(str(base)):
37
+ raise ValueError("Invalid path")
38
+ return target
39
+
40
+
41
+ def list_directory(user: str, path: str) -> list[dict[str, str | bool]]:
42
+ target = _vm_host_path(user, path)
43
+ if not target.exists() or not target.is_dir():
44
+ raise FileNotFoundError(path)
45
+ entries: list[dict[str, str | bool]] = []
46
+ for entry in sorted(target.iterdir()):
47
+ entries.append({"name": entry.name, "is_dir": entry.is_dir()})
48
+ return entries
49
+
50
+
51
+ def read_file(user: str, path: str) -> str:
52
+ target = _vm_host_path(user, path)
53
+ if not target.exists() or target.is_dir():
54
+ raise FileNotFoundError(path)
55
+ return target.read_text()
56
+
57
+
58
+ def write_file(user: str, path: str, content: str) -> None:
59
+ target = _vm_host_path(user, path)
60
+ target.parent.mkdir(parents=True, exist_ok=True)
61
+ target.write_text(content)
62
+
63
+
64
+ def delete_path(user: str, path: str) -> None:
65
+ target = _vm_host_path(user, path)
66
+ if target.is_dir():
67
+ for child in target.iterdir():
68
+ if child.is_file():
69
+ child.unlink()
70
+ else:
71
+ delete_path(user, str(Path("/data") / child.relative_to(target)))
72
+ target.rmdir()
73
+ elif target.exists():
74
+ target.unlink()
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Chat helpers
79
+ # ---------------------------------------------------------------------------
80
+
81
+ async def chat_generator(user: str, session: str, prompt: str) -> Iterator[List[List[str]]]:
82
+ history: List[List[str]] = []
83
+ async with TeamChatSession(user=user, session=session) as chat:
84
+ history.append([prompt, ""])
85
+ resp = ""
86
+ async for part in chat.chat_stream(prompt):
87
+ resp += part
88
+ history[-1][1] = resp
89
+ yield history
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Gradio callbacks
94
+ # ---------------------------------------------------------------------------
95
+
96
+ async def send_message(user: str, session: str, message: str, history: list[list[str]] | None) -> typing.Iterator[tuple[list[list[str]], str]]:
97
+ history = history or []
98
+ async with TeamChatSession(user=user, session=session) as chat:
99
+ history.append([message, ""])
100
+ resp = ""
101
+ async for part in chat.chat_stream(message):
102
+ resp += part
103
+ history[-1][1] = resp
104
+ yield history, ""
105
+
106
+ def load_sessions(user: str) -> list[str]:
107
+ info = list_sessions_info(user)
108
+ return [s["name"] for s in info]
109
+
110
+
111
+ def remove_session(user: str, session: str) -> None:
112
+ reset_history(user, session)
113
+
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # UI construction
117
+ # ---------------------------------------------------------------------------
118
+
119
+ def build_ui() -> gr.Blocks:
120
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
121
+ user_state = gr.State(str)
122
+ session_state = gr.State("default")
123
+ history_state = gr.State([])
124
+
125
+ with gr.Column() as login_col:
126
+ gr.Markdown("## HuggingFace Login")
127
+ token_box = gr.Textbox(type="password", label="HuggingFace token")
128
+ login_btn = gr.Button("Login")
129
+ login_status = gr.Markdown()
130
+
131
+ with gr.Row(visible=False) as main_row:
132
+ with gr.Column(scale=3):
133
+ session_drop = gr.Dropdown(label="Session", interactive=True)
134
+ new_session = gr.Textbox(label="New Session Name")
135
+ create_btn = gr.Button("Create Session")
136
+ chatbox = gr.Chatbot(type="messages")
137
+ msg = gr.Textbox(label="Message")
138
+ send_btn = gr.Button("Send")
139
+
140
+ with gr.Column(scale=2):
141
+ gr.Markdown("### Files")
142
+ dir_path = gr.Textbox(value="/data", label="Path")
143
+ refresh_btn = gr.Button("List")
144
+ file_list = gr.Dataframe(headers=["Name", "Is Dir"], datatype=["str", "bool"], interactive=False)
145
+ open_path = gr.Textbox(label="File Path")
146
+ open_btn = gr.Button("Open")
147
+ file_editor = gr.Textbox(label="Content", lines=10)
148
+ save_btn = gr.Button("Save")
149
+ delete_btn = gr.Button("Delete")
150
+
151
+ def do_login(token: str):
152
+ user = hf_login(token)
153
+ sessions = load_sessions(user)
154
+ return {user_state: user, session_drop: gr.Dropdown.update(choices=sessions), login_col: gr.Column.update(visible=False), main_row: gr.Row.update(visible=True)}
155
+
156
+ login_btn.click(do_login, inputs=token_box, outputs=[user_state, session_drop, login_col, main_row])
157
+
158
+ def create_session(user: str, name: str):
159
+ if not name:
160
+ return gr.Dropdown.update()
161
+ reset_history(user, name)
162
+ sessions = load_sessions(user)
163
+ return gr.Dropdown.update(value=name, choices=sessions)
164
+
165
+ create_btn.click(create_session, inputs=[user_state, new_session], outputs=session_drop)
166
+
167
+ def refresh_files(user: str, path: str):
168
+ entries = list_directory(user, path)
169
+ data = [[e["name"], e["is_dir"]] for e in entries]
170
+ return {file_list: gr.Dataframe.update(value=data), dir_path: path}
171
+
172
+ refresh_btn.click(refresh_files, inputs=[user_state, dir_path], outputs=[file_list, dir_path])
173
+
174
+ def open_file(user: str, path: str):
175
+ content = read_file(user, path)
176
+ return {file_editor: content, open_path: path}
177
+
178
+ open_btn.click(open_file, inputs=[user_state, open_path], outputs=[file_editor, open_path])
179
+
180
+ def save_file(user: str, path: str, content: str):
181
+ write_file(user, path, content)
182
+ return gr.Textbox.update()
183
+
184
+ save_btn.click(save_file, inputs=[user_state, open_path, file_editor], outputs=file_editor)
185
+
186
+ def delete_file(user: str, path: str):
187
+ delete_path(user, path)
188
+ return gr.Textbox.update(value="")
189
+
190
+ delete_btn.click(delete_file, inputs=[user_state, open_path], outputs=open_path)
191
+
192
+ send_btn.click(send_message, inputs=[user_state, session_drop, msg, history_state], outputs=[chatbox, history_state, msg])
193
+ msg.submit(send_message, inputs=[user_state, session_drop, msg, history_state], outputs=[chatbox, history_state, msg])
194
+
195
+ return demo
196
+
197
+
198
+ if __name__ == "__main__":
199
+ ui = build_ui()
200
+ ui.launch()
requirements.txt CHANGED
@@ -8,3 +8,6 @@ fastapi
8
  uvicorn
9
  python-multipart
10
  httpx
 
 
 
 
8
  uvicorn
9
  python-multipart
10
  httpx
11
+
12
+ gradio
13
+ huggingface_hub