Spaces:
Sleeping
Sleeping
Commit
·
afedd43
1
Parent(s):
9da7c31
Upd code viewer
Browse files- static/styles.css +122 -17
- utils/api/router.py +27 -13
static/styles.css
CHANGED
|
@@ -1077,67 +1077,172 @@
|
|
| 1077 |
/* Code blocks with copy button */
|
| 1078 |
.code-block-wrapper {
|
| 1079 |
position: relative;
|
| 1080 |
-
margin:
|
| 1081 |
-
border-radius:
|
| 1082 |
overflow: hidden;
|
| 1083 |
border: 1px solid var(--border);
|
|
|
|
|
|
|
| 1084 |
}
|
| 1085 |
|
| 1086 |
.code-block-header {
|
| 1087 |
display: flex;
|
| 1088 |
justify-content: space-between;
|
| 1089 |
align-items: center;
|
| 1090 |
-
padding:
|
| 1091 |
-
background: var(--bg-secondary);
|
| 1092 |
border-bottom: 1px solid var(--border);
|
| 1093 |
font-size: 12px;
|
| 1094 |
color: var(--muted);
|
|
|
|
| 1095 |
}
|
| 1096 |
|
| 1097 |
.code-block-language {
|
| 1098 |
-
font-weight:
|
| 1099 |
text-transform: uppercase;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1100 |
}
|
| 1101 |
|
| 1102 |
.copy-code-btn {
|
| 1103 |
display: flex;
|
| 1104 |
align-items: center;
|
| 1105 |
-
gap:
|
| 1106 |
-
padding:
|
| 1107 |
background: var(--card);
|
| 1108 |
border: 1px solid var(--border);
|
| 1109 |
-
border-radius:
|
| 1110 |
color: var(--text-secondary);
|
| 1111 |
font-size: 11px;
|
|
|
|
| 1112 |
cursor: pointer;
|
| 1113 |
-
transition: all 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1114 |
}
|
| 1115 |
|
| 1116 |
.copy-code-btn:hover {
|
| 1117 |
background: var(--card-hover);
|
| 1118 |
-
border-color: var(--
|
| 1119 |
color: var(--text);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1120 |
}
|
| 1121 |
|
| 1122 |
.copy-code-btn.copied {
|
| 1123 |
background: var(--success);
|
| 1124 |
color: white;
|
| 1125 |
border-color: var(--success);
|
|
|
|
| 1126 |
}
|
| 1127 |
|
| 1128 |
.copy-code-btn svg {
|
| 1129 |
-
width:
|
| 1130 |
-
height:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1131 |
}
|
| 1132 |
|
| 1133 |
.code-block-content {
|
| 1134 |
-
background:
|
| 1135 |
-
color:
|
| 1136 |
-
padding:
|
| 1137 |
overflow-x: auto;
|
| 1138 |
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
| 1139 |
font-size: 14px;
|
| 1140 |
-
line-height: 1.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1141 |
}
|
| 1142 |
|
| 1143 |
/* Download PDF button */
|
|
|
|
| 1077 |
/* Code blocks with copy button */
|
| 1078 |
.code-block-wrapper {
|
| 1079 |
position: relative;
|
| 1080 |
+
margin: 20px 0;
|
| 1081 |
+
border-radius: 12px;
|
| 1082 |
overflow: hidden;
|
| 1083 |
border: 1px solid var(--border);
|
| 1084 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
| 1085 |
+
background: var(--card);
|
| 1086 |
}
|
| 1087 |
|
| 1088 |
.code-block-header {
|
| 1089 |
display: flex;
|
| 1090 |
justify-content: space-between;
|
| 1091 |
align-items: center;
|
| 1092 |
+
padding: 12px 16px;
|
| 1093 |
+
background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--card) 100%);
|
| 1094 |
border-bottom: 1px solid var(--border);
|
| 1095 |
font-size: 12px;
|
| 1096 |
color: var(--muted);
|
| 1097 |
+
font-weight: 500;
|
| 1098 |
}
|
| 1099 |
|
| 1100 |
.code-block-language {
|
| 1101 |
+
font-weight: 700;
|
| 1102 |
text-transform: uppercase;
|
| 1103 |
+
letter-spacing: 0.5px;
|
| 1104 |
+
color: var(--accent);
|
| 1105 |
+
display: flex;
|
| 1106 |
+
align-items: center;
|
| 1107 |
+
gap: 6px;
|
| 1108 |
+
}
|
| 1109 |
+
|
| 1110 |
+
.code-block-language::before {
|
| 1111 |
+
content: '';
|
| 1112 |
+
width: 8px;
|
| 1113 |
+
height: 8px;
|
| 1114 |
+
border-radius: 50%;
|
| 1115 |
+
background: var(--accent);
|
| 1116 |
+
display: inline-block;
|
| 1117 |
}
|
| 1118 |
|
| 1119 |
.copy-code-btn {
|
| 1120 |
display: flex;
|
| 1121 |
align-items: center;
|
| 1122 |
+
gap: 6px;
|
| 1123 |
+
padding: 6px 12px;
|
| 1124 |
background: var(--card);
|
| 1125 |
border: 1px solid var(--border);
|
| 1126 |
+
border-radius: 8px;
|
| 1127 |
color: var(--text-secondary);
|
| 1128 |
font-size: 11px;
|
| 1129 |
+
font-weight: 500;
|
| 1130 |
cursor: pointer;
|
| 1131 |
+
transition: all 0.3s ease;
|
| 1132 |
+
position: relative;
|
| 1133 |
+
overflow: hidden;
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
.copy-code-btn::before {
|
| 1137 |
+
content: '';
|
| 1138 |
+
position: absolute;
|
| 1139 |
+
top: 0;
|
| 1140 |
+
left: -100%;
|
| 1141 |
+
width: 100%;
|
| 1142 |
+
height: 100%;
|
| 1143 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
| 1144 |
+
transition: left 0.5s ease;
|
| 1145 |
}
|
| 1146 |
|
| 1147 |
.copy-code-btn:hover {
|
| 1148 |
background: var(--card-hover);
|
| 1149 |
+
border-color: var(--accent);
|
| 1150 |
color: var(--text);
|
| 1151 |
+
transform: translateY(-1px);
|
| 1152 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
.copy-code-btn:hover::before {
|
| 1156 |
+
left: 100%;
|
| 1157 |
}
|
| 1158 |
|
| 1159 |
.copy-code-btn.copied {
|
| 1160 |
background: var(--success);
|
| 1161 |
color: white;
|
| 1162 |
border-color: var(--success);
|
| 1163 |
+
transform: scale(1.05);
|
| 1164 |
}
|
| 1165 |
|
| 1166 |
.copy-code-btn svg {
|
| 1167 |
+
width: 14px;
|
| 1168 |
+
height: 14px;
|
| 1169 |
+
transition: transform 0.2s ease;
|
| 1170 |
+
}
|
| 1171 |
+
|
| 1172 |
+
.copy-code-btn:hover svg {
|
| 1173 |
+
transform: scale(1.1);
|
| 1174 |
}
|
| 1175 |
|
| 1176 |
.code-block-content {
|
| 1177 |
+
background: linear-gradient(135deg, #1a1a1a 0%, #1e1e1e 100%);
|
| 1178 |
+
color: #e6e6e6;
|
| 1179 |
+
padding: 20px;
|
| 1180 |
overflow-x: auto;
|
| 1181 |
+
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
| 1182 |
font-size: 14px;
|
| 1183 |
+
line-height: 1.6;
|
| 1184 |
+
position: relative;
|
| 1185 |
+
}
|
| 1186 |
+
|
| 1187 |
+
.code-block-content::before {
|
| 1188 |
+
content: '';
|
| 1189 |
+
position: absolute;
|
| 1190 |
+
top: 0;
|
| 1191 |
+
left: 0;
|
| 1192 |
+
right: 0;
|
| 1193 |
+
height: 1px;
|
| 1194 |
+
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
| 1195 |
+
}
|
| 1196 |
+
|
| 1197 |
+
/* Enhanced syntax highlighting for code blocks */
|
| 1198 |
+
.code-block-content .hljs {
|
| 1199 |
+
background: transparent !important;
|
| 1200 |
+
color: inherit !important;
|
| 1201 |
+
}
|
| 1202 |
+
|
| 1203 |
+
.code-block-content .hljs-keyword {
|
| 1204 |
+
color: #ff7b72;
|
| 1205 |
+
font-weight: 600;
|
| 1206 |
+
}
|
| 1207 |
+
|
| 1208 |
+
.code-block-content .hljs-string {
|
| 1209 |
+
color: #a5d6ff;
|
| 1210 |
+
}
|
| 1211 |
+
|
| 1212 |
+
.code-block-content .hljs-comment {
|
| 1213 |
+
color: #8b949e;
|
| 1214 |
+
font-style: italic;
|
| 1215 |
+
}
|
| 1216 |
+
|
| 1217 |
+
.code-block-content .hljs-number {
|
| 1218 |
+
color: #79c0ff;
|
| 1219 |
+
}
|
| 1220 |
+
|
| 1221 |
+
.code-block-content .hljs-function {
|
| 1222 |
+
color: #d2a8ff;
|
| 1223 |
+
}
|
| 1224 |
+
|
| 1225 |
+
.code-block-content .hljs-variable {
|
| 1226 |
+
color: #ffa657;
|
| 1227 |
+
}
|
| 1228 |
+
|
| 1229 |
+
/* Scrollbar styling for code blocks */
|
| 1230 |
+
.code-block-content::-webkit-scrollbar {
|
| 1231 |
+
height: 8px;
|
| 1232 |
+
}
|
| 1233 |
+
|
| 1234 |
+
.code-block-content::-webkit-scrollbar-track {
|
| 1235 |
+
background: #2d2d2d;
|
| 1236 |
+
border-radius: 4px;
|
| 1237 |
+
}
|
| 1238 |
+
|
| 1239 |
+
.code-block-content::-webkit-scrollbar-thumb {
|
| 1240 |
+
background: var(--accent);
|
| 1241 |
+
border-radius: 4px;
|
| 1242 |
+
}
|
| 1243 |
+
|
| 1244 |
+
.code-block-content::-webkit-scrollbar-thumb:hover {
|
| 1245 |
+
background: var(--accent-hover);
|
| 1246 |
}
|
| 1247 |
|
| 1248 |
/* Download PDF button */
|
utils/api/router.py
CHANGED
|
@@ -94,25 +94,39 @@ async def generate_answer_with_model(selection: Dict[str, Any], system_prompt: s
|
|
| 94 |
model = selection["model"]
|
| 95 |
|
| 96 |
if provider == "gemini":
|
| 97 |
-
|
| 98 |
-
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={key}"
|
| 99 |
-
payload = {
|
| 100 |
-
"contents": [
|
| 101 |
-
{"role": "user", "parts": [{"text": f"{system_prompt}\n\n{user_prompt}"}]}
|
| 102 |
-
],
|
| 103 |
-
"generationConfig": {"temperature": 0.2}
|
| 104 |
-
}
|
| 105 |
-
headers = {"Content-Type": "application/json"}
|
| 106 |
-
data = await robust_post_json(url, headers, payload, gemini_rotator)
|
| 107 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
content = data["candidates"][0]["content"]["parts"][0]["text"]
|
| 109 |
if not content or content.strip() == "":
|
| 110 |
logger.warning(f"Empty content from Gemini model: {data}")
|
| 111 |
-
|
| 112 |
return content
|
| 113 |
except Exception as e:
|
| 114 |
-
logger.warning(f"
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
elif provider == "nvidia":
|
| 118 |
# Many NVIDIA endpoints are OpenAI-compatible. Adjust if using a different path.
|
|
|
|
| 94 |
model = selection["model"]
|
| 95 |
|
| 96 |
if provider == "gemini":
|
| 97 |
+
# Try Gemini first
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
try:
|
| 99 |
+
key = gemini_rotator.get_key() or ""
|
| 100 |
+
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={key}"
|
| 101 |
+
payload = {
|
| 102 |
+
"contents": [
|
| 103 |
+
{"role": "user", "parts": [{"text": f"{system_prompt}\n\n{user_prompt}"}]}
|
| 104 |
+
],
|
| 105 |
+
"generationConfig": {"temperature": 0.2}
|
| 106 |
+
}
|
| 107 |
+
headers = {"Content-Type": "application/json"}
|
| 108 |
+
data = await robust_post_json(url, headers, payload, gemini_rotator)
|
| 109 |
+
|
| 110 |
content = data["candidates"][0]["content"]["parts"][0]["text"]
|
| 111 |
if not content or content.strip() == "":
|
| 112 |
logger.warning(f"Empty content from Gemini model: {data}")
|
| 113 |
+
raise Exception("Empty content from Gemini")
|
| 114 |
return content
|
| 115 |
except Exception as e:
|
| 116 |
+
logger.warning(f"Gemini model {model} failed: {e}. Attempting fallback...")
|
| 117 |
+
|
| 118 |
+
# Fallback logic: GEMINI_PRO/MED → NVIDIA_LARGE, GEMINI_SMALL → NVIDIA_SMALL
|
| 119 |
+
if model in [GEMINI_PRO, GEMINI_MED]:
|
| 120 |
+
logger.info(f"Falling back from {model} to NVIDIA_LARGE")
|
| 121 |
+
fallback_selection = {"provider": "nvidia_large", "model": NVIDIA_LARGE}
|
| 122 |
+
return await generate_answer_with_model(fallback_selection, system_prompt, user_prompt, gemini_rotator, nvidia_rotator)
|
| 123 |
+
elif model == GEMINI_SMALL:
|
| 124 |
+
logger.info(f"Falling back from {model} to NVIDIA_SMALL")
|
| 125 |
+
fallback_selection = {"provider": "nvidia", "model": NVIDIA_SMALL}
|
| 126 |
+
return await generate_answer_with_model(fallback_selection, system_prompt, user_prompt, gemini_rotator, nvidia_rotator)
|
| 127 |
+
else:
|
| 128 |
+
logger.error(f"No fallback defined for Gemini model: {model}")
|
| 129 |
+
return "I couldn't parse the model response."
|
| 130 |
|
| 131 |
elif provider == "nvidia":
|
| 132 |
# Many NVIDIA endpoints are OpenAI-compatible. Adjust if using a different path.
|