Полностью переработан деплой для обхода ограничений прав доступа на HuggingFace Space
Browse files- Dockerfile +12 -23
- fallback.py +156 -28
- start.sh +13 -68
Dockerfile
CHANGED
@@ -28,12 +28,11 @@ RUN wget https://golang.org/dl/go1.21.0.linux-amd64.tar.gz && \
|
|
28 |
tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz && \
|
29 |
rm go1.21.0.linux-amd64.tar.gz
|
30 |
|
31 |
-
#
|
32 |
ENV PATH=$PATH:/usr/local/go/bin
|
33 |
ENV GOPATH=/go
|
34 |
ENV PATH=$PATH:$GOPATH/bin
|
35 |
RUN mkdir -p /go && chmod 777 /go
|
36 |
-
RUN go install github.com/go-task/task/v3/cmd/task@latest
|
37 |
|
38 |
# Установка Node.js и pnpm
|
39 |
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
@@ -44,34 +43,24 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
|
44 |
WORKDIR /app
|
45 |
RUN git clone --depth 1 https://github.com/TEN-framework/TEN-Agent.git /app
|
46 |
|
47 |
-
# Создание
|
48 |
-
RUN
|
49 |
-
AGORA_APP_ID=${AGORA_APP_ID}\n\
|
50 |
-
AGORA_APP_CERTIFICATE=${AGORA_APP_CERTIFICATE}\n\
|
51 |
-
AZURE_STT_KEY=${AZURE_STT_KEY}\n\
|
52 |
-
AZURE_STT_REGION=${AZURE_STT_REGION}\n\
|
53 |
-
AZURE_TTS_KEY=${AZURE_TTS_KEY}\n\
|
54 |
-
AZURE_TTS_REGION=${AZURE_TTS_REGION}\n\
|
55 |
-
OPENAI_API_KEY=${OPENAI_API_KEY}\n\
|
56 |
-
" > /app/.env
|
57 |
|
58 |
-
#
|
59 |
-
|
60 |
-
|
61 |
-
chmod -R 777 /app && \
|
62 |
-
mkdir -p /tmp/ten_agent && \
|
63 |
-
chmod -R 777 /tmp/ten_agent
|
64 |
|
65 |
-
#
|
66 |
-
|
|
|
67 |
|
68 |
# Копируем стартовые скрипты
|
69 |
COPY start.sh /app/start.sh
|
70 |
COPY fallback.py /app/fallback.py
|
71 |
RUN chmod +x /app/start.sh /app/fallback.py
|
72 |
|
73 |
-
# Открываем порты (7860 для HF Space, 8080 для API
|
74 |
-
EXPOSE 7860 8080
|
75 |
|
76 |
-
# Запускаем TEN-Agent через
|
77 |
CMD ["/app/start.sh"]
|
|
|
28 |
tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz && \
|
29 |
rm go1.21.0.linux-amd64.tar.gz
|
30 |
|
31 |
+
# Настройка окружения Go
|
32 |
ENV PATH=$PATH:/usr/local/go/bin
|
33 |
ENV GOPATH=/go
|
34 |
ENV PATH=$PATH:$GOPATH/bin
|
35 |
RUN mkdir -p /go && chmod 777 /go
|
|
|
36 |
|
37 |
# Установка Node.js и pnpm
|
38 |
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
|
|
43 |
WORKDIR /app
|
44 |
RUN git clone --depth 1 https://github.com/TEN-framework/TEN-Agent.git /app
|
45 |
|
46 |
+
# Создание пользовательской директории для временных файлов
|
47 |
+
RUN mkdir -p /tmp/ten_user && chmod 777 /tmp/ten_user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
+
# Создание .env файла из окружения Hugging Face Space
|
50 |
+
# Используем простой формат без пробелов для предотвращения ошибок
|
51 |
+
RUN echo "AGORA_APP_ID=${AGORA_APP_ID}\nAGORA_APP_CERTIFICATE=${AGORA_APP_CERTIFICATE}\nAZURE_STT_KEY=${AZURE_STT_KEY}\nAZURE_STT_REGION=${AZURE_STT_REGION}\nAZURE_TTS_KEY=${AZURE_TTS_KEY}\nAZURE_TTS_REGION=${AZURE_TTS_REGION}\nOPENAI_API_KEY=${OPENAI_API_KEY}" > /app/.env
|
|
|
|
|
|
|
52 |
|
53 |
+
# Предварительная установка зависимостей для playground
|
54 |
+
# Делаем это отдельно, чтобы не пересобирать при каждом изменении
|
55 |
+
RUN cd /app/playground && pnpm install
|
56 |
|
57 |
# Копируем стартовые скрипты
|
58 |
COPY start.sh /app/start.sh
|
59 |
COPY fallback.py /app/fallback.py
|
60 |
RUN chmod +x /app/start.sh /app/fallback.py
|
61 |
|
62 |
+
# Открываем порты (7860 для HF Space, 8080 для API)
|
63 |
+
EXPOSE 7860 8080
|
64 |
|
65 |
+
# Запускаем TEN-Agent через fallback скрипт
|
66 |
CMD ["/app/start.sh"]
|
fallback.py
CHANGED
@@ -1,20 +1,33 @@
|
|
1 |
#!/usr/bin/env python3
|
2 |
import os
|
3 |
import json
|
4 |
-
import shutil
|
5 |
import subprocess
|
6 |
import sys
|
7 |
import time
|
|
|
8 |
from pathlib import Path
|
9 |
|
10 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
"""Создает базовые файлы конфигурации для TEN-Agent"""
|
12 |
print("Creating basic configuration files...")
|
13 |
|
14 |
-
# Создаем директории
|
15 |
-
agents_dir = Path("/app/agents")
|
16 |
-
agents_dir.mkdir(exist_ok=True)
|
17 |
-
|
18 |
# Создаем manifest.json
|
19 |
manifest = {
|
20 |
"_ten": {"version": "0.0.1"},
|
@@ -108,7 +121,7 @@ def create_basic_files():
|
|
108 |
json.dump(voice_agent, f, indent=2)
|
109 |
|
110 |
# Создаем chat_agent.json (аналогичный voice_agent.json)
|
111 |
-
chat_agent = voice_agent
|
112 |
chat_agent["nodes"][1]["data"]["properties"]["system_prompt"] = "You are a helpful chat assistant."
|
113 |
|
114 |
with open(agents_dir / "chat_agent.json", "w") as f:
|
@@ -116,46 +129,161 @@ def create_basic_files():
|
|
116 |
|
117 |
print("Basic configuration files created successfully.")
|
118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
def main():
|
120 |
try:
|
121 |
-
|
122 |
-
|
123 |
-
subprocess.run(["chmod", "-R", "777", "/app/agents"], check=True)
|
124 |
|
125 |
-
#
|
126 |
-
|
|
|
|
|
127 |
|
128 |
-
#
|
129 |
-
|
130 |
-
if not server_binary.exists():
|
131 |
-
print("Compiling API server...")
|
132 |
-
subprocess.run(["cd", "/app/server", "&&", "go", "build", "-o", "bin/api", "main.go"], check=True)
|
133 |
-
subprocess.run(["chmod", "+x", "/app/server/bin/api"], check=True)
|
134 |
|
135 |
-
#
|
136 |
-
|
137 |
|
138 |
-
#
|
139 |
-
|
|
|
|
|
|
|
|
|
|
|
140 |
os.environ["AGENT_SERVER_URL"] = "http://localhost:8080"
|
141 |
os.environ["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true"
|
142 |
os.environ["NEXT_PUBLIC_DISABLE_CAMERA"] = "true"
|
143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
playground_process = subprocess.Popen(
|
145 |
-
|
146 |
env=os.environ,
|
147 |
-
shell=True
|
|
|
|
|
148 |
)
|
149 |
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
|
152 |
-
# Ожидаем завершения процессов
|
153 |
-
|
154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
155 |
|
156 |
except Exception as e:
|
157 |
print(f"Error: {e}")
|
158 |
sys.exit(1)
|
159 |
|
160 |
if __name__ == "__main__":
|
|
|
161 |
main()
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
import os
|
3 |
import json
|
|
|
4 |
import subprocess
|
5 |
import sys
|
6 |
import time
|
7 |
+
import shutil
|
8 |
from pathlib import Path
|
9 |
|
10 |
+
def create_user_directory():
|
11 |
+
"""Создает отдельную директорию для пользовательских файлов"""
|
12 |
+
print("Creating user directory...")
|
13 |
+
|
14 |
+
# Создаем пользовательскую директорию
|
15 |
+
user_dir = Path("/tmp/ten_user")
|
16 |
+
user_dir.mkdir(exist_ok=True, parents=True)
|
17 |
+
|
18 |
+
# Создаем структуру директорий
|
19 |
+
agents_dir = user_dir / "agents"
|
20 |
+
agents_dir.mkdir(exist_ok=True, parents=True)
|
21 |
+
server_bin_dir = user_dir / "server" / "bin"
|
22 |
+
server_bin_dir.mkdir(exist_ok=True, parents=True)
|
23 |
+
|
24 |
+
print(f"Created directory structure at {user_dir}")
|
25 |
+
return user_dir, agents_dir, server_bin_dir
|
26 |
+
|
27 |
+
def create_basic_files(agents_dir):
|
28 |
"""Создает базовые файлы конфигурации для TEN-Agent"""
|
29 |
print("Creating basic configuration files...")
|
30 |
|
|
|
|
|
|
|
|
|
31 |
# Создаем manifest.json
|
32 |
manifest = {
|
33 |
"_ten": {"version": "0.0.1"},
|
|
|
121 |
json.dump(voice_agent, f, indent=2)
|
122 |
|
123 |
# Создаем chat_agent.json (аналогичный voice_agent.json)
|
124 |
+
chat_agent = dict(voice_agent)
|
125 |
chat_agent["nodes"][1]["data"]["properties"]["system_prompt"] = "You are a helpful chat assistant."
|
126 |
|
127 |
with open(agents_dir / "chat_agent.json", "w") as f:
|
|
|
129 |
|
130 |
print("Basic configuration files created successfully.")
|
131 |
|
132 |
+
def check_api_server(server_bin_dir):
|
133 |
+
"""Проверяет наличие API сервера и компилирует его при необходимости"""
|
134 |
+
api_bin = server_bin_dir / "api"
|
135 |
+
|
136 |
+
if not api_bin.exists():
|
137 |
+
print("Copying API server binary...")
|
138 |
+
# Проверяем наличие уже скомпилированного сервера в основном репозитории
|
139 |
+
original_bin = Path("/app/server/bin/api")
|
140 |
+
if original_bin.exists():
|
141 |
+
# Копируем бинарник из основного репозитория
|
142 |
+
shutil.copy(original_bin, api_bin)
|
143 |
+
api_bin.chmod(0o755) # Делаем исполняемым
|
144 |
+
print(f"API server binary copied to {api_bin}")
|
145 |
+
else:
|
146 |
+
print("Compiling API server from source...")
|
147 |
+
try:
|
148 |
+
# Компилируем сервер
|
149 |
+
result = subprocess.run(
|
150 |
+
"cd /app/server && go build -o /tmp/ten_user/server/bin/api main.go",
|
151 |
+
shell=True,
|
152 |
+
check=True,
|
153 |
+
stdout=subprocess.PIPE,
|
154 |
+
stderr=subprocess.PIPE
|
155 |
+
)
|
156 |
+
print(f"API server compiled successfully: {result.stdout.decode()}")
|
157 |
+
# Делаем исполняемым
|
158 |
+
api_bin.chmod(0o755)
|
159 |
+
except subprocess.CalledProcessError as e:
|
160 |
+
print(f"Failed to compile API server: {e.stderr.decode()}")
|
161 |
+
# Как последний вариант, используем предварительно скомпилированный бинарник
|
162 |
+
fallback_bin = Path("/app/fallback/api")
|
163 |
+
if fallback_bin.exists():
|
164 |
+
shutil.copy(fallback_bin, api_bin)
|
165 |
+
api_bin.chmod(0o755)
|
166 |
+
print(f"Using pre-compiled fallback API server binary")
|
167 |
+
else:
|
168 |
+
print("No API server binary available! Cannot continue.")
|
169 |
+
return False
|
170 |
+
|
171 |
+
return True
|
172 |
+
|
173 |
def main():
|
174 |
try:
|
175 |
+
print(f"Starting TEN-Agent in fallback mode...")
|
176 |
+
print(f"Current directory: {os.getcwd()}")
|
|
|
177 |
|
178 |
+
# Выводим информацию о среде
|
179 |
+
user = os.environ.get('USER', 'unknown')
|
180 |
+
print(f"Current user: {user}")
|
181 |
+
print(f"HOME: {os.environ.get('HOME', 'unset')}")
|
182 |
|
183 |
+
# Создаем пользовательские директории
|
184 |
+
user_dir, agents_dir, server_bin_dir = create_user_directory()
|
|
|
|
|
|
|
|
|
185 |
|
186 |
+
# Создаем базовые конфигурационные файлы
|
187 |
+
create_basic_files(agents_dir)
|
188 |
|
189 |
+
# Проверяем API сервер
|
190 |
+
if not check_api_server(server_bin_dir):
|
191 |
+
print("Critical error: Cannot prepare API server")
|
192 |
+
sys.exit(1)
|
193 |
+
|
194 |
+
# Установка переменных среды
|
195 |
+
os.environ["PORT"] = "7860" # HuggingFace использует порт 7860
|
196 |
os.environ["AGENT_SERVER_URL"] = "http://localhost:8080"
|
197 |
os.environ["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true"
|
198 |
os.environ["NEXT_PUBLIC_DISABLE_CAMERA"] = "true"
|
199 |
|
200 |
+
# Путь к бинарнику API сервера
|
201 |
+
api_bin = str(server_bin_dir / "api")
|
202 |
+
|
203 |
+
# Запускаем API сервер с указанием пути к agents директории
|
204 |
+
print(f"Starting API server with agents directory: {agents_dir}")
|
205 |
+
api_env = os.environ.copy()
|
206 |
+
api_env["TEN_AGENT_DIR"] = str(agents_dir)
|
207 |
+
|
208 |
+
api_process = subprocess.Popen(
|
209 |
+
[api_bin],
|
210 |
+
env=api_env,
|
211 |
+
stdout=subprocess.PIPE,
|
212 |
+
stderr=subprocess.PIPE
|
213 |
+
)
|
214 |
+
|
215 |
+
# Даем серверу время для запуска
|
216 |
+
time.sleep(5)
|
217 |
+
|
218 |
+
# Проверяем, запустился ли сервер
|
219 |
+
if api_process.poll() is not None:
|
220 |
+
print("API server failed to start!")
|
221 |
+
stdout, stderr = api_process.communicate()
|
222 |
+
print(f"STDOUT: {stdout.decode()}")
|
223 |
+
print(f"STDERR: {stderr.decode()}")
|
224 |
+
sys.exit(1)
|
225 |
+
|
226 |
+
print("API server started successfully")
|
227 |
+
|
228 |
+
# Запускаем playground
|
229 |
+
print("Starting Playground UI...")
|
230 |
playground_process = subprocess.Popen(
|
231 |
+
"cd /app/playground && pnpm dev",
|
232 |
env=os.environ,
|
233 |
+
shell=True,
|
234 |
+
stdout=subprocess.PIPE,
|
235 |
+
stderr=subprocess.PIPE
|
236 |
)
|
237 |
|
238 |
+
# Даем UI время для запуска
|
239 |
+
time.sleep(5)
|
240 |
+
|
241 |
+
# Проверяем, запустился ли UI
|
242 |
+
if playground_process.poll() is not None:
|
243 |
+
print("Playground UI failed to start!")
|
244 |
+
stdout, stderr = playground_process.communicate()
|
245 |
+
print(f"STDOUT: {stdout.decode()}")
|
246 |
+
print(f"STDERR: {stderr.decode()}")
|
247 |
+
|
248 |
+
# Останавливаем API сервер
|
249 |
+
api_process.terminate()
|
250 |
+
sys.exit(1)
|
251 |
+
|
252 |
+
print("TEN-Agent started successfully in fallback mode.")
|
253 |
+
print("Playground UI is available at http://localhost:7860")
|
254 |
+
|
255 |
+
# Следим за выводом процессов в реальном времени
|
256 |
+
def stream_output(process, name):
|
257 |
+
for line in iter(process.stdout.readline, b''):
|
258 |
+
print(f"[{name}] {line.decode().strip()}")
|
259 |
+
|
260 |
+
api_thread = threading.Thread(target=stream_output, args=(api_process, "API"))
|
261 |
+
ui_thread = threading.Thread(target=stream_output, args=(playground_process, "UI"))
|
262 |
+
|
263 |
+
api_thread.daemon = True
|
264 |
+
ui_thread.daemon = True
|
265 |
+
|
266 |
+
api_thread.start()
|
267 |
+
ui_thread.start()
|
268 |
|
269 |
+
# Ожидаем завершения любого из процессов
|
270 |
+
while True:
|
271 |
+
if api_process.poll() is not None:
|
272 |
+
print("API server has stopped.")
|
273 |
+
playground_process.terminate()
|
274 |
+
break
|
275 |
+
|
276 |
+
if playground_process.poll() is not None:
|
277 |
+
print("Playground UI has stopped.")
|
278 |
+
api_process.terminate()
|
279 |
+
break
|
280 |
+
|
281 |
+
time.sleep(1)
|
282 |
|
283 |
except Exception as e:
|
284 |
print(f"Error: {e}")
|
285 |
sys.exit(1)
|
286 |
|
287 |
if __name__ == "__main__":
|
288 |
+
import threading
|
289 |
main()
|
start.sh
CHANGED
@@ -1,83 +1,28 @@
|
|
1 |
#!/bin/bash
|
2 |
-
set -e
|
3 |
|
4 |
# Вывод информации о запуске
|
5 |
echo "===== Starting TEN-Agent on HuggingFace Space ====="
|
6 |
echo "$(date)"
|
7 |
echo "Current directory: $(pwd)"
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
# Проверяем наличие .env файла
|
10 |
if [ -f .env ]; then
|
11 |
echo "✅ .env file found"
|
12 |
cat .env | grep -v "KEY\|CERTIFICATE" | sed 's/=.*/=***/'
|
13 |
else
|
14 |
-
echo "
|
15 |
-
exit 1
|
16 |
-
fi
|
17 |
-
|
18 |
-
# Проверяем установку task
|
19 |
-
if command -v task &> /dev/null; then
|
20 |
-
echo "✅ task command is available"
|
21 |
-
task --version
|
22 |
-
else
|
23 |
-
echo "❌ Error: task command not found"
|
24 |
-
exit 1
|
25 |
fi
|
26 |
|
27 |
-
#
|
28 |
-
echo "=====
|
29 |
-
|
30 |
-
|
31 |
-
chmod -R 777 /app/agents
|
32 |
-
chmod -R 777 /app
|
33 |
-
chmod -R 777 /tmp/ten_agent
|
34 |
-
|
35 |
-
echo "Directory permissions:"
|
36 |
-
ls -la /app/agents
|
37 |
-
ls -la /app
|
38 |
-
ls -la /tmp/ten_agent
|
39 |
|
40 |
-
#
|
41 |
-
|
42 |
-
echo "✅ Examples directory found"
|
43 |
-
ls -la /app/agents/examples
|
44 |
-
else
|
45 |
-
echo "❌ Error: Examples directory not found"
|
46 |
-
# Создаем базовую структуру
|
47 |
-
mkdir -p /app/agents/examples/default
|
48 |
-
echo "Created directory structure"
|
49 |
-
fi
|
50 |
-
|
51 |
-
# Запускаем сборку агента с официальной командой
|
52 |
-
echo "===== Building TEN-Agent ====="
|
53 |
-
if task use; then
|
54 |
-
echo "✅ task use успешно выполнена"
|
55 |
-
|
56 |
-
# Обновляем порт для HuggingFace Space (HF использует порт 7860)
|
57 |
-
echo "===== Setting up port for HuggingFace Space ====="
|
58 |
-
export PORT=7860
|
59 |
-
|
60 |
-
# Отключаем запрос на использование камеры для HuggingFace Space
|
61 |
-
export NEXT_PUBLIC_DISABLE_CAMERA=true
|
62 |
-
|
63 |
-
# Запускаем TEN-Agent с официальной командой
|
64 |
-
echo "===== Starting TEN-Agent server ====="
|
65 |
-
exec task run
|
66 |
-
else
|
67 |
-
echo "⚠️ Warning: task use failed, trying alternative method..."
|
68 |
-
|
69 |
-
# Альтернативный метод настройки агента без использования символических ссылок
|
70 |
-
mkdir -p /app/agents/examples/default
|
71 |
-
cp -r /app/agents/examples/default/* /app/agents/ 2>/dev/null || true
|
72 |
-
|
73 |
-
echo "Manual setup completed, trying to run task run..."
|
74 |
-
|
75 |
-
if task run; then
|
76 |
-
echo "✅ task run успешно выполнена"
|
77 |
-
exit 0
|
78 |
-
else
|
79 |
-
echo "⚠️ Warning: все методы запуска через task не удались, запускаем fallback..."
|
80 |
-
chmod +x /app/fallback.py
|
81 |
-
exec python3 /app/fallback.py
|
82 |
-
fi
|
83 |
-
fi
|
|
|
1 |
#!/bin/bash
|
|
|
2 |
|
3 |
# Вывод информации о запуске
|
4 |
echo "===== Starting TEN-Agent on HuggingFace Space ====="
|
5 |
echo "$(date)"
|
6 |
echo "Current directory: $(pwd)"
|
7 |
|
8 |
+
# Вывод информации о пользователе и его правах
|
9 |
+
echo "===== Environment Information ====="
|
10 |
+
echo "User: $(whoami)"
|
11 |
+
echo "Groups: $(groups)"
|
12 |
+
echo "Home directory: $HOME"
|
13 |
+
|
14 |
# Проверяем наличие .env файла
|
15 |
if [ -f .env ]; then
|
16 |
echo "✅ .env file found"
|
17 |
cat .env | grep -v "KEY\|CERTIFICATE" | sed 's/=.*/=***/'
|
18 |
else
|
19 |
+
echo "⚠️ Warning: .env file not found, will use environment variables"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
fi
|
21 |
|
22 |
+
# Запускаем приложение напрямую через fallback скрипт
|
23 |
+
echo "===== Starting TEN-Agent via fallback script ====="
|
24 |
+
echo "Due to permission issues in Hugging Face Space, we'll use the fallback script"
|
25 |
+
echo "This will create necessary files in /tmp where we have write access"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
+
# Выполняем Python скрипт напрямую
|
28 |
+
exec python3 /app/fallback.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|