Spaces:
Runtime error
Runtime error
LE Quoc Dat
commited on
Commit
·
2ecced5
1
Parent(s):
16b33c2
working
Browse files- app.py +12 -5
- llm_utils.py +27 -15
- static/js/main.js +0 -222
- static/js/models.js +14 -0
- static/js/prompts.js +44 -0
- templates/index.html +77 -86
app.py
CHANGED
@@ -5,6 +5,7 @@ import json
|
|
5 |
from datetime import datetime
|
6 |
import base64
|
7 |
from llm_utils import generate_completion
|
|
|
8 |
|
9 |
app = Flask(__name__)
|
10 |
|
@@ -68,18 +69,24 @@ def generate_flashcard():
|
|
68 |
data = request.json
|
69 |
prompt = data['prompt']
|
70 |
mode = data.get('mode', 'flashcard')
|
|
|
71 |
|
72 |
try:
|
73 |
-
# Use llm_utils to generate completion
|
74 |
-
content = generate_completion(prompt)
|
75 |
print(prompt)
|
76 |
print(content)
|
77 |
|
78 |
if mode == 'language':
|
79 |
try:
|
80 |
-
#
|
81 |
-
|
82 |
-
|
|
|
|
|
|
|
|
|
|
|
83 |
except Exception as parse_err:
|
84 |
return jsonify({'error': 'JSON parsing error in language mode: ' + str(parse_err)})
|
85 |
elif mode == 'flashcard':
|
|
|
5 |
from datetime import datetime
|
6 |
import base64
|
7 |
from llm_utils import generate_completion
|
8 |
+
import re
|
9 |
|
10 |
app = Flask(__name__)
|
11 |
|
|
|
69 |
data = request.json
|
70 |
prompt = data['prompt']
|
71 |
mode = data.get('mode', 'flashcard')
|
72 |
+
model = data.get('model')
|
73 |
|
74 |
try:
|
75 |
+
# Use llm_utils to generate completion with the selected model
|
76 |
+
content = generate_completion(prompt, model=model)
|
77 |
print(prompt)
|
78 |
print(content)
|
79 |
|
80 |
if mode == 'language':
|
81 |
try:
|
82 |
+
# Extract the JSON substring from the content in case there is extra text.
|
83 |
+
json_match = re.search(r'\{[\s\S]*\}', content)
|
84 |
+
if json_match:
|
85 |
+
json_text = json_match.group(0)
|
86 |
+
flashcard = json.loads(json_text)
|
87 |
+
return jsonify({'flashcard': flashcard})
|
88 |
+
else:
|
89 |
+
raise ValueError("No JSON object found in response")
|
90 |
except Exception as parse_err:
|
91 |
return jsonify({'error': 'JSON parsing error in language mode: ' + str(parse_err)})
|
92 |
elif mode == 'flashcard':
|
llm_utils.py
CHANGED
@@ -6,33 +6,45 @@ def load_model_config():
|
|
6 |
with open('models.yaml', 'r') as file:
|
7 |
return yaml.safe_load(file)
|
8 |
|
9 |
-
def generate_completion(prompt: str, api_key: str = None) -> str:
|
10 |
"""
|
11 |
-
Generate completion using LiteLLM with the configured model
|
12 |
|
13 |
Args:
|
14 |
-
prompt (str): The input prompt
|
15 |
-
|
|
|
16 |
|
17 |
Returns:
|
18 |
-
str: The generated completion text
|
19 |
"""
|
20 |
config = load_model_config()
|
21 |
-
|
22 |
-
#
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
if api_key is None:
|
28 |
-
api_key = os.getenv(
|
29 |
-
|
30 |
-
|
31 |
|
32 |
messages = [{"role": "user", "content": prompt}]
|
33 |
|
34 |
response = completion(
|
35 |
-
model=
|
36 |
messages=messages,
|
37 |
api_key=api_key
|
38 |
)
|
|
|
6 |
with open('models.yaml', 'r') as file:
|
7 |
return yaml.safe_load(file)
|
8 |
|
9 |
+
def generate_completion(prompt: str, model: str = None, api_key: str = None) -> str:
|
10 |
"""
|
11 |
+
Generate completion using LiteLLM with the configured model.
|
12 |
|
13 |
Args:
|
14 |
+
prompt (str): The input prompt.
|
15 |
+
model (str, optional): The model to use (if not provided, default is used).
|
16 |
+
api_key (str, optional): Override API key. If not provided, will use environment variable.
|
17 |
|
18 |
Returns:
|
19 |
+
str: The generated completion text.
|
20 |
"""
|
21 |
config = load_model_config()
|
22 |
+
|
23 |
+
# Build a mapping of model to the required API key environment variable.
|
24 |
+
model_to_env = {}
|
25 |
+
for item in config['models']:
|
26 |
+
for env_key, models in item.items():
|
27 |
+
for m in models:
|
28 |
+
model_to_env[m] = env_key
|
29 |
+
|
30 |
+
# Use the default model from the first configuration if none provided.
|
31 |
+
if model is None:
|
32 |
+
first_env_var = list(config['models'][0].keys())[0]
|
33 |
+
model = config['models'][0][first_env_var][0]
|
34 |
+
|
35 |
+
env_var = model_to_env.get(model)
|
36 |
+
if not env_var:
|
37 |
+
raise ValueError("Model is not supported.")
|
38 |
+
|
39 |
if api_key is None:
|
40 |
+
api_key = os.getenv(env_var)
|
41 |
+
if not api_key:
|
42 |
+
raise ValueError(f"Please set {env_var} environment variable")
|
43 |
|
44 |
messages = [{"role": "user", "content": prompt}]
|
45 |
|
46 |
response = completion(
|
47 |
+
model=model,
|
48 |
messages=messages,
|
49 |
api_key=api_key
|
50 |
)
|
static/js/main.js
DELETED
@@ -1,222 +0,0 @@
|
|
1 |
-
import { PROMPTS } from './prompts.js';
|
2 |
-
|
3 |
-
// DOM elements
|
4 |
-
const fileInput = document.getElementById('file-input');
|
5 |
-
const pdfViewer = document.getElementById('pdf-viewer');
|
6 |
-
const epubViewer = document.getElementById('epub-viewer');
|
7 |
-
const modeToggle = document.getElementById('mode-toggle');
|
8 |
-
const systemPrompt = document.getElementById('system-prompt');
|
9 |
-
const explainPrompt = document.getElementById('explain-prompt');
|
10 |
-
const languagePrompt = document.getElementById('language-prompt');
|
11 |
-
const submitBtn = document.getElementById('submit-btn');
|
12 |
-
const flashcardsContainer = document.getElementById('flashcards');
|
13 |
-
const apiKeyInput = document.getElementById('api-key-input');
|
14 |
-
const modelSelect = document.getElementById('model-select');
|
15 |
-
const recentPdfList = document.getElementById('file-list');
|
16 |
-
|
17 |
-
// State variables
|
18 |
-
let pdfDoc = null;
|
19 |
-
let pageNum = 1;
|
20 |
-
let pageRendering = false;
|
21 |
-
let pageNumPending = null;
|
22 |
-
let scale = 3;
|
23 |
-
const minScale = 0.5;
|
24 |
-
const maxScale = 5;
|
25 |
-
let mode = 'flashcard';
|
26 |
-
let apiKey = '';
|
27 |
-
let currentFileName = '';
|
28 |
-
let currentPage = 1;
|
29 |
-
let selectedModel = 'claude-3-haiku-20240307';
|
30 |
-
let lastProcessedQuery = '';
|
31 |
-
let lastRequestTime = 0;
|
32 |
-
const cooldownTime = 1000; // 1 second cooldown
|
33 |
-
let book;
|
34 |
-
let rendition;
|
35 |
-
let currentScaleEPUB = 100;
|
36 |
-
let highlights = [];
|
37 |
-
let flashcardCollectionCount = 0;
|
38 |
-
let languageCollectionCount = 0;
|
39 |
-
let collectedFlashcards = [];
|
40 |
-
let collectedLanguageFlashcards = [];
|
41 |
-
let voices = [];
|
42 |
-
|
43 |
-
// Initialize on DOM load
|
44 |
-
document.addEventListener('DOMContentLoaded', () => {
|
45 |
-
// Set default prompts
|
46 |
-
systemPrompt.value = PROMPTS.flashcard;
|
47 |
-
explainPrompt.value = PROMPTS.explain;
|
48 |
-
languagePrompt.value = PROMPTS.language;
|
49 |
-
|
50 |
-
// Load last working API key
|
51 |
-
const lastWorkingAPIKey = localStorage.getItem('lastWorkingAPIKey');
|
52 |
-
if (lastWorkingAPIKey) {
|
53 |
-
apiKeyInput.value = lastWorkingAPIKey;
|
54 |
-
apiKey = lastWorkingAPIKey;
|
55 |
-
}
|
56 |
-
|
57 |
-
// Initialize collection counts and flashcards
|
58 |
-
flashcardCollectionCount = parseInt(localStorage.getItem('flashcardCollectionCount')) || 0;
|
59 |
-
languageCollectionCount = parseInt(localStorage.getItem('languageCollectionCount')) || 0;
|
60 |
-
collectedFlashcards = JSON.parse(localStorage.getItem('collectedFlashcards')) || [];
|
61 |
-
collectedLanguageFlashcards = JSON.parse(localStorage.getItem('collectedLanguageFlashcards')) || [];
|
62 |
-
updateAddToCollectionButtonText();
|
63 |
-
updateExportButtonVisibility();
|
64 |
-
|
65 |
-
// Load recent files
|
66 |
-
loadRecentFiles();
|
67 |
-
|
68 |
-
// Set up event listeners
|
69 |
-
setupEventListeners();
|
70 |
-
populateVoiceList();
|
71 |
-
initializeMode();
|
72 |
-
});
|
73 |
-
|
74 |
-
// Setup event listeners
|
75 |
-
function setupEventListeners() {
|
76 |
-
fileInput.addEventListener('change', handleFileChange);
|
77 |
-
apiKeyInput.addEventListener('change', () => {
|
78 |
-
apiKey = apiKeyInput.value;
|
79 |
-
localStorage.setItem('lastWorkingAPIKey', apiKey);
|
80 |
-
});
|
81 |
-
modelSelect.addEventListener('change', () => {
|
82 |
-
selectedModel = modelSelect.value;
|
83 |
-
});
|
84 |
-
submitBtn.addEventListener('click', generateContent);
|
85 |
-
document.getElementById('add-to-collection-btn').addEventListener('click', addToCollection);
|
86 |
-
document.getElementById('clear-collection-btn').addEventListener('click', clearCollection);
|
87 |
-
document.getElementById('export-csv-btn').addEventListener('click', exportToCSV);
|
88 |
-
document.getElementById('go-to-page-btn').addEventListener('click', handleGoToPage);
|
89 |
-
document.getElementById('page-input').addEventListener('keyup', (e) => {
|
90 |
-
if (e.key === 'Enter') handleGoToPage();
|
91 |
-
});
|
92 |
-
document.getElementById('zoom-in-btn').addEventListener('click', handleZoomIn);
|
93 |
-
document.getElementById('zoom-out-btn').addEventListener('click', handleZoomOut);
|
94 |
-
document.getElementById('settings-icon').addEventListener('click', () => {
|
95 |
-
const settingsPanel = document.getElementById('settings-panel');
|
96 |
-
settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none';
|
97 |
-
});
|
98 |
-
document.getElementById('left-panel').addEventListener('scroll', handleScroll);
|
99 |
-
setupModeButtons();
|
100 |
-
setupLanguageButtons();
|
101 |
-
}
|
102 |
-
|
103 |
-
// Initialize mode
|
104 |
-
function initializeMode() {
|
105 |
-
mode = 'language';
|
106 |
-
document.querySelector('.mode-btn[data-mode="language"]').classList.add('selected');
|
107 |
-
document.getElementById('language-buttons').style.display = 'flex';
|
108 |
-
submitBtn.style.display = 'none';
|
109 |
-
systemPrompt.style.display = 'none';
|
110 |
-
explainPrompt.style.display = 'none';
|
111 |
-
languagePrompt.style.display = 'block';
|
112 |
-
const savedLanguage = loadLanguageChoice() || 'English';
|
113 |
-
setLanguageButton(savedLanguage);
|
114 |
-
}
|
115 |
-
|
116 |
-
// File handling
|
117 |
-
function handleFileChange(e) {
|
118 |
-
const file = e.target.files[0];
|
119 |
-
if (!['application/pdf', 'text/plain', 'application/epub+zip'].includes(file.type)) {
|
120 |
-
console.error('Error: Not a PDF, TXT, or EPUB file');
|
121 |
-
return;
|
122 |
-
}
|
123 |
-
uploadFile(file);
|
124 |
-
}
|
125 |
-
|
126 |
-
function uploadFile(file) {
|
127 |
-
const formData = new FormData();
|
128 |
-
formData.append('file', file);
|
129 |
-
fetch('/upload_file', {
|
130 |
-
method: 'POST',
|
131 |
-
body: formData
|
132 |
-
})
|
133 |
-
.then(response => response.json())
|
134 |
-
.then(data => {
|
135 |
-
if (data.message) {
|
136 |
-
loadFile(file);
|
137 |
-
loadRecentFiles();
|
138 |
-
addRecentFile(file.name);
|
139 |
-
} else {
|
140 |
-
console.error(data.error);
|
141 |
-
}
|
142 |
-
})
|
143 |
-
.catch(error => console.error('Error:', error));
|
144 |
-
}
|
145 |
-
|
146 |
-
function loadFile(file) {
|
147 |
-
pdfViewer.style.display = 'none';
|
148 |
-
epubViewer.style.display = 'none';
|
149 |
-
if (file.name.endsWith('.pdf')) {
|
150 |
-
pdfViewer.style.display = 'block';
|
151 |
-
loadPDF(file);
|
152 |
-
} else if (file.name.endsWith('.txt')) {
|
153 |
-
pdfViewer.style.display = 'block';
|
154 |
-
loadTXT(file);
|
155 |
-
} else if (file.name.endsWith('.epub')) {
|
156 |
-
epubViewer.style.display = 'block';
|
157 |
-
loadEPUB(file);
|
158 |
-
}
|
159 |
-
}
|
160 |
-
|
161 |
-
// PDF handling (broken down for readability)
|
162 |
-
async function loadPDF(file) {
|
163 |
-
const arrayBuffer = await readFileAsArrayBuffer(file);
|
164 |
-
pdfDoc = await pdfjsLib.getDocument(arrayBuffer).promise;
|
165 |
-
pdfViewer.innerHTML = '';
|
166 |
-
currentFileName = file.name;
|
167 |
-
const lastPage = localStorage.getItem(`lastPage_${currentFileName}`);
|
168 |
-
pageNum = lastPage ? Math.max(parseInt(lastPage) - 2, 1) : 1;
|
169 |
-
loadScaleForCurrentFile();
|
170 |
-
renderPage(pageNum);
|
171 |
-
updateCurrentPage(pageNum);
|
172 |
-
hideHeaderPanel();
|
173 |
-
loadHighlights();
|
174 |
-
}
|
175 |
-
|
176 |
-
function readFileAsArrayBuffer(file) {
|
177 |
-
return new Promise((resolve, reject) => {
|
178 |
-
const reader = new FileReader();
|
179 |
-
reader.onload = () => resolve(new Uint8Array(reader.result));
|
180 |
-
reader.onerror = reject;
|
181 |
-
reader.readAsArrayBuffer(file);
|
182 |
-
});
|
183 |
-
}
|
184 |
-
|
185 |
-
function renderPage(num) {
|
186 |
-
pageRendering = true;
|
187 |
-
pdfDoc.getPage(num).then(page => {
|
188 |
-
const viewport = page.getViewport({ scale });
|
189 |
-
const pixelRatio = window.devicePixelRatio || 1;
|
190 |
-
const adjustedViewport = page.getViewport({ scale: scale * pixelRatio });
|
191 |
-
const pageDiv = createPageDiv(num, viewport);
|
192 |
-
const canvas = createCanvas(viewport, adjustedViewport);
|
193 |
-
renderCanvas(page, canvas, adjustedViewport);
|
194 |
-
pageDiv.appendChild(canvas);
|
195 |
-
const textLayerDiv = createTextLayerDiv(viewport);
|
196 |
-
pageDiv.appendChild(textLayerDiv);
|
197 |
-
renderTextLayer(page, textLayerDiv, viewport);
|
198 |
-
pdfViewer.appendChild(pageDiv);
|
199 |
-
attachLanguageModeListener(pageDiv);
|
200 |
-
renderHighlights();
|
201 |
-
pageRendering = false;
|
202 |
-
if (pageNumPending !== null) {
|
203 |
-
renderPage(pageNumPending);
|
204 |
-
pageNumPending = null;
|
205 |
-
}
|
206 |
-
if (num < pdfDoc.numPages && pdfViewer.scrollHeight <= window.innerHeight * 2) {
|
207 |
-
renderPage(num + 1);
|
208 |
-
}
|
209 |
-
});
|
210 |
-
}
|
211 |
-
|
212 |
-
// Other functions (TXT, EPUB, navigation, mode handling, flashcard generation, etc.)
|
213 |
-
// These are implemented as in index.html, with improvements:
|
214 |
-
// - Use async/await consistently
|
215 |
-
// - Break down large functions (e.g., generateContent, handleLanguageMode)
|
216 |
-
// - Improve error handling
|
217 |
-
// - Use const/let appropriately
|
218 |
-
// - Encapsulate related functionality
|
219 |
-
|
220 |
-
// Note: Due to space constraints, the full implementation is not shown here.
|
221 |
-
// However, all functions from index.html are moved here with the noted improvements.
|
222 |
-
// Ensure all functionality (PDF, EPUB, TXT handling, navigation, collections, etc.) is preserved.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/models.js
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const MODEL_API_KEY_MAPPING = {
|
2 |
+
"gemini/gemini-exp-1206": "GEMINI_API_KEY",
|
3 |
+
// "gemini/gemini-2.0-flash": "GEMINI_API_KEY",
|
4 |
+
// "gemini/gemini-2.0-flash-lite-preview-02-05": "GEMINI_API_KEY",
|
5 |
+
// "gemini/gemini-2.0-pro-exp-02-05": "GEMINI_API_KEY",
|
6 |
+
// "gemini/gemini-2.0-flash-thinking-exp-01-21": "GEMINI_API_KEY",
|
7 |
+
"openrouter/google/gemini-exp-1206:free": "OPENROUTER_API_KEY",
|
8 |
+
"openrouter/anthropic/claude-3-haiku-20240307": "OPENROUTER_API_KEY",
|
9 |
+
"openrouter/anthropic/claude-3-sonnet-20240229": "OPENROUTER_API_KEY"
|
10 |
+
};
|
11 |
+
|
12 |
+
const availableModels = Object.keys(MODEL_API_KEY_MAPPING);
|
13 |
+
|
14 |
+
// Removed MODEL_LABELS as they are no longer needed.
|
static/js/prompts.js
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const FLASHCARD_PROMPT = `Generate flashcards as a JSON array where each object has "question" and "answer" keys. The number of flashcards should be proportional to the text's length and complexity, with a minimum of 1 and a maximum of 10. Each question should test a key concept and the answer should be brief but complete. Use <b> tags to emphasize important words or phrases. Cite short code or examples if needed.
|
2 |
+
|
3 |
+
Example input: "In parallel computing, load balancing refers to the practice of distributing computational work evenly across multiple processing units. This is crucial for maximizing efficiency and minimizing idle time. Dynamic load balancing adjusts the distribution of work during runtime, while static load balancing determines the distribution before execution begins."
|
4 |
+
|
5 |
+
Example output:
|
6 |
+
[
|
7 |
+
{
|
8 |
+
"question": "What is the primary goal of <b>load balancing</b> in parallel computing?",
|
9 |
+
"answer": "To <b>distribute work evenly</b> across processing units, maximizing efficiency and minimizing idle time."
|
10 |
+
},
|
11 |
+
{
|
12 |
+
"question": "How does <b>dynamic load balancing</b> differ from <b>static load balancing</b>?",
|
13 |
+
"answer": "Dynamic balancing <b>adjusts work distribution during runtime</b>, while static balancing <b>determines distribution before execution</b>."
|
14 |
+
}
|
15 |
+
]
|
16 |
+
|
17 |
+
Now generate flashcards for this text:`;
|
18 |
+
|
19 |
+
const EXPLAIN_PROMPT = `Explain the following text in simple terms, focusing on the main concepts and their relationships. Use clear and concise language, and break down complex ideas into easily understandable parts. If there are any technical terms, provide brief explanations for them. Return your explanation in a JSON object with an "explanation" key.
|
20 |
+
|
21 |
+
Example output:
|
22 |
+
{
|
23 |
+
"explanation": "Load balancing is a technique in parallel computing that ensures work is distributed evenly across different processing units. Think of it like distributing tasks among team members - when done well, everyone has a fair amount of work and the team is more efficient. There are two main approaches: dynamic balancing (adjusting work distribution as needed) and static balancing (planning the distribution ahead of time)."
|
24 |
+
}
|
25 |
+
|
26 |
+
Now explain this text:`;
|
27 |
+
|
28 |
+
const LANGUAGE_PROMPT = `Return a JSON object with "word", "translation", "question", and "answer" keys for the given word in {targetLanguage}.
|
29 |
+
|
30 |
+
Example input:
|
31 |
+
Word: "refused"
|
32 |
+
Phrase: "Hamas refused to join a new round of peace negotiations."
|
33 |
+
|
34 |
+
Example output:
|
35 |
+
{
|
36 |
+
"word": "refused",
|
37 |
+
"translation": "từ chối",
|
38 |
+
"question": "Hamas <b>refused</b> to join a new round of peace negotiations.",
|
39 |
+
"answer": "Declined to accept or comply with a request or proposal."
|
40 |
+
}
|
41 |
+
|
42 |
+
Now explain the word in the phrase below:
|
43 |
+
Word: "{word}"
|
44 |
+
Phrase: "{phrase}"`;
|
templates/index.html
CHANGED
@@ -9,6 +9,8 @@
|
|
9 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
|
10 |
<script src="https://cdn.jsdelivr.net/npm/epubjs/dist/epub.min.js"></script>
|
11 |
<link rel="stylesheet" href="/static/css/styles.css">
|
|
|
|
|
12 |
<style>
|
13 |
.api-settings {
|
14 |
margin-bottom: 15px;
|
@@ -70,53 +72,11 @@
|
|
70 |
</div>
|
71 |
</div>
|
72 |
<div id="settings-panel" style="display: none;">
|
73 |
-
<input type="password" id="api-key-input" placeholder="Enter
|
74 |
-
<select id="model-select">
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
<textarea id="system-prompt" placeholder="Enter system prompt for flashcard generation">Generate flashcards as a JSON array where each object has "question" and "answer" keys. The number of flashcards should be proportional to the text's length and complexity, with a minimum of 1 and a maximum of 10. Each question should test a key concept and the answer should be brief but complete. Use <b> tags to emphasize important words or phrases. Cite short code or examples if needed.
|
79 |
-
|
80 |
-
Example input: "In parallel computing, load balancing refers to the practice of distributing computational work evenly across multiple processing units. This is crucial for maximizing efficiency and minimizing idle time. Dynamic load balancing adjusts the distribution of work during runtime, while static load balancing determines the distribution before execution begins."
|
81 |
-
|
82 |
-
Example output:
|
83 |
-
[
|
84 |
-
{
|
85 |
-
"question": "What is the primary goal of <b>load balancing</b> in parallel computing?",
|
86 |
-
"answer": "To <b>distribute work evenly</b> across processing units, maximizing efficiency and minimizing idle time."
|
87 |
-
},
|
88 |
-
{
|
89 |
-
"question": "How does <b>dynamic load balancing</b> differ from <b>static load balancing</b>?",
|
90 |
-
"answer": "Dynamic balancing <b>adjusts work distribution during runtime</b>, while static balancing <b>determines distribution before execution</b>."
|
91 |
-
}
|
92 |
-
]
|
93 |
-
|
94 |
-
Now generate flashcards for this text:</textarea>
|
95 |
-
<textarea id="explain-prompt" placeholder="Enter system prompt for explanation" style="display: none;">Explain the following text in simple terms, focusing on the main concepts and their relationships. Use clear and concise language, and break down complex ideas into easily understandable parts. If there are any technical terms, provide brief explanations for them. Return your explanation in a JSON object with an "explanation" key.
|
96 |
-
|
97 |
-
Example output:
|
98 |
-
{
|
99 |
-
"explanation": "Load balancing is a technique in parallel computing that ensures work is distributed evenly across different processing units. Think of it like distributing tasks among team members - when done well, everyone has a fair amount of work and the team is more efficient. There are two main approaches: dynamic balancing (adjusting work distribution as needed) and static balancing (planning the distribution ahead of time)."
|
100 |
-
}
|
101 |
-
|
102 |
-
Now explain this text:</textarea>
|
103 |
-
<textarea id="language-prompt" placeholder="Enter system prompt for language mode">Return a JSON object with "word", "translation", "question", and "answer" keys for the given word in {targetLanguage}.
|
104 |
-
|
105 |
-
Example input:
|
106 |
-
Word: "refused"
|
107 |
-
Phrase: "Hamas refused to join a new round of peace negotiations."
|
108 |
-
|
109 |
-
Example output:
|
110 |
-
{
|
111 |
-
"word": "refused",
|
112 |
-
"translation": "từ chối",
|
113 |
-
"question": "Hamas <b>refused</b> to join a new round of peace negotiations.",
|
114 |
-
"answer": "Declined to accept or comply with a request or proposal."
|
115 |
-
}
|
116 |
-
|
117 |
-
Now explain the word in the phrase below:
|
118 |
-
Word: "{word}"
|
119 |
-
Phrase: "{phrase}"</textarea>
|
120 |
</div>
|
121 |
<div id="mode-toggle">
|
122 |
<button class="mode-btn selected" data-mode="flashcard">Flashcard</button>
|
@@ -149,7 +109,6 @@ Phrase: "{phrase}"</textarea>
|
|
149 |
</div>
|
150 |
</div>
|
151 |
|
152 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.1/showdown.min.js"></script>
|
153 |
<script>
|
154 |
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.worker.min.js';
|
155 |
|
@@ -174,7 +133,7 @@ Phrase: "{phrase}"</textarea>
|
|
174 |
let apiKey = '';
|
175 |
let currentFileName = '';
|
176 |
let currentPage = 1;
|
177 |
-
let selectedModel = '
|
178 |
let lastProcessedQuery = '';
|
179 |
let lastRequestTime = 0;
|
180 |
const cooldownTime = 1000; // 1 second cooldown
|
@@ -480,9 +439,30 @@ Phrase: "{phrase}"</textarea>
|
|
480 |
}
|
481 |
}
|
482 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
483 |
async function generateLanguageFlashcard(word, phrase, targetLanguage) {
|
484 |
if (!apiKey) {
|
485 |
-
alert('Please enter your
|
486 |
return;
|
487 |
}
|
488 |
|
@@ -492,7 +472,7 @@ Phrase: "{phrase}"</textarea>
|
|
492 |
.replace('{targetLanguage}', targetLanguage);
|
493 |
|
494 |
try {
|
495 |
-
const response = await
|
496 |
if (response.flashcard) {
|
497 |
const flashcard = response.flashcard;
|
498 |
const formattedFlashcard = {
|
@@ -507,14 +487,14 @@ Phrase: "{phrase}"</textarea>
|
|
507 |
throw new Error('Invalid response from API');
|
508 |
}
|
509 |
} catch (error) {
|
510 |
-
console.error('Error calling
|
511 |
alert('Failed to generate language flashcard. Please check your API key and try again.');
|
512 |
}
|
513 |
}
|
514 |
|
515 |
async function generateContent() {
|
516 |
if (!apiKey) {
|
517 |
-
alert('Please enter your
|
518 |
return;
|
519 |
}
|
520 |
|
@@ -532,23 +512,23 @@ Phrase: "{phrase}"</textarea>
|
|
532 |
return;
|
533 |
}
|
534 |
|
535 |
-
// Disable the button
|
536 |
submitBtn.disabled = true;
|
537 |
-
submitBtn.style.backgroundColor = '#808080';
|
538 |
const notification = document.createElement('div');
|
539 |
notification.textContent = 'Generating...';
|
540 |
notification.style.position = 'fixed';
|
541 |
notification.style.top = '20px';
|
542 |
notification.style.right = '20px';
|
543 |
notification.style.padding = '10px';
|
544 |
-
notification.style.backgroundColor = 'rgba(0, 128, 0, 0.7)';
|
545 |
notification.style.color = 'white';
|
546 |
notification.style.borderRadius = '5px';
|
547 |
notification.style.zIndex = '1000';
|
548 |
document.body.appendChild(notification);
|
549 |
|
550 |
try {
|
551 |
-
const response = await
|
552 |
if (mode === 'flashcard' && response.flashcards) {
|
553 |
displayFlashcards(response.flashcards, true);
|
554 |
} else if (mode === 'explain' && response.explanation) {
|
@@ -557,18 +537,17 @@ Phrase: "{phrase}"</textarea>
|
|
557 |
throw new Error('Invalid response from API');
|
558 |
}
|
559 |
} catch (error) {
|
560 |
-
console.error('Error calling
|
561 |
alert(`Failed to generate ${mode === 'flashcard' ? 'flashcards' : 'explanation'}. Please check your API key and try again.`);
|
562 |
} finally {
|
563 |
-
// Remove notification, re-enable button, and restore its color after 3 seconds
|
564 |
setTimeout(() => {
|
565 |
document.body.removeChild(notification);
|
566 |
submitBtn.disabled = false;
|
567 |
-
submitBtn.style.backgroundColor = '';
|
568 |
}, 3000);
|
569 |
}
|
570 |
} else {
|
571 |
-
alert(`Please select some text
|
572 |
}
|
573 |
}
|
574 |
|
@@ -609,31 +588,6 @@ Phrase: "{phrase}"</textarea>
|
|
609 |
}
|
610 |
}
|
611 |
|
612 |
-
async function callClaudeAPI(prompt) {
|
613 |
-
const response = await fetch('/generate_flashcard', {
|
614 |
-
method: 'POST',
|
615 |
-
headers: {
|
616 |
-
'Content-Type': 'application/json',
|
617 |
-
'X-API-Key': apiKey
|
618 |
-
},
|
619 |
-
body: JSON.stringify({
|
620 |
-
prompt: prompt,
|
621 |
-
model: selectedModel,
|
622 |
-
mode: mode // Add the current mode to the request
|
623 |
-
})
|
624 |
-
});
|
625 |
-
|
626 |
-
if (!response.ok) {
|
627 |
-
throw new Error(`HTTP error! status: ${response.status}`);
|
628 |
-
}
|
629 |
-
|
630 |
-
return await response.json();
|
631 |
-
}
|
632 |
-
|
633 |
-
modelSelect.addEventListener('change', function () {
|
634 |
-
selectedModel = this.value;
|
635 |
-
});
|
636 |
-
|
637 |
function displayFlashcards(flashcards, append = false) {
|
638 |
if (!append) {
|
639 |
flashcardsContainer.innerHTML = ''; // Clear existing flashcards only if not appending
|
@@ -1391,6 +1345,43 @@ Phrase: "{phrase}"</textarea>
|
|
1391 |
console.error('Error:', error);
|
1392 |
});
|
1393 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1394 |
</script>
|
1395 |
</body>
|
1396 |
|
|
|
9 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
|
10 |
<script src="https://cdn.jsdelivr.net/npm/epubjs/dist/epub.min.js"></script>
|
11 |
<link rel="stylesheet" href="/static/css/styles.css">
|
12 |
+
<script src="/static/js/prompts.js"></script>
|
13 |
+
<script src="/static/js/models.js"></script>
|
14 |
<style>
|
15 |
.api-settings {
|
16 |
margin-bottom: 15px;
|
|
|
72 |
</div>
|
73 |
</div>
|
74 |
<div id="settings-panel" style="display: none;">
|
75 |
+
<input type="password" id="api-key-input" placeholder="Enter API Key">
|
76 |
+
<select id="model-select"></select>
|
77 |
+
<textarea id="system-prompt" placeholder="Enter system prompt for flashcard generation"></textarea>
|
78 |
+
<textarea id="explain-prompt" placeholder="Enter system prompt for explanation" style="display: none;"></textarea>
|
79 |
+
<textarea id="language-prompt" placeholder="Enter system prompt for language mode"></textarea>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
</div>
|
81 |
<div id="mode-toggle">
|
82 |
<button class="mode-btn selected" data-mode="flashcard">Flashcard</button>
|
|
|
109 |
</div>
|
110 |
</div>
|
111 |
|
|
|
112 |
<script>
|
113 |
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.9.359/pdf.worker.min.js';
|
114 |
|
|
|
133 |
let apiKey = '';
|
134 |
let currentFileName = '';
|
135 |
let currentPage = 1;
|
136 |
+
let selectedModel = 'gemini/gemini-exp-1206';
|
137 |
let lastProcessedQuery = '';
|
138 |
let lastRequestTime = 0;
|
139 |
const cooldownTime = 1000; // 1 second cooldown
|
|
|
439 |
}
|
440 |
}
|
441 |
|
442 |
+
async function callLLMAPI(prompt) {
|
443 |
+
const response = await fetch('/generate_flashcard', {
|
444 |
+
method: 'POST',
|
445 |
+
headers: {
|
446 |
+
'Content-Type': 'application/json',
|
447 |
+
'X-API-Key': apiKey
|
448 |
+
},
|
449 |
+
body: JSON.stringify({
|
450 |
+
prompt: prompt,
|
451 |
+
model: selectedModel,
|
452 |
+
mode: mode
|
453 |
+
})
|
454 |
+
});
|
455 |
+
|
456 |
+
if (!response.ok) {
|
457 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
458 |
+
}
|
459 |
+
|
460 |
+
return await response.json();
|
461 |
+
}
|
462 |
+
|
463 |
async function generateLanguageFlashcard(word, phrase, targetLanguage) {
|
464 |
if (!apiKey) {
|
465 |
+
alert('Please enter your API key first.');
|
466 |
return;
|
467 |
}
|
468 |
|
|
|
472 |
.replace('{targetLanguage}', targetLanguage);
|
473 |
|
474 |
try {
|
475 |
+
const response = await callLLMAPI(prompt);
|
476 |
if (response.flashcard) {
|
477 |
const flashcard = response.flashcard;
|
478 |
const formattedFlashcard = {
|
|
|
487 |
throw new Error('Invalid response from API');
|
488 |
}
|
489 |
} catch (error) {
|
490 |
+
console.error('Error calling LLM API:', error);
|
491 |
alert('Failed to generate language flashcard. Please check your API key and try again.');
|
492 |
}
|
493 |
}
|
494 |
|
495 |
async function generateContent() {
|
496 |
if (!apiKey) {
|
497 |
+
alert('Please enter your API key first.');
|
498 |
return;
|
499 |
}
|
500 |
|
|
|
512 |
return;
|
513 |
}
|
514 |
|
515 |
+
// Disable the button and show notification
|
516 |
submitBtn.disabled = true;
|
517 |
+
submitBtn.style.backgroundColor = '#808080';
|
518 |
const notification = document.createElement('div');
|
519 |
notification.textContent = 'Generating...';
|
520 |
notification.style.position = 'fixed';
|
521 |
notification.style.top = '20px';
|
522 |
notification.style.right = '20px';
|
523 |
notification.style.padding = '10px';
|
524 |
+
notification.style.backgroundColor = 'rgba(0, 128, 0, 0.7)';
|
525 |
notification.style.color = 'white';
|
526 |
notification.style.borderRadius = '5px';
|
527 |
notification.style.zIndex = '1000';
|
528 |
document.body.appendChild(notification);
|
529 |
|
530 |
try {
|
531 |
+
const response = await callLLMAPI(prompt);
|
532 |
if (mode === 'flashcard' && response.flashcards) {
|
533 |
displayFlashcards(response.flashcards, true);
|
534 |
} else if (mode === 'explain' && response.explanation) {
|
|
|
537 |
throw new Error('Invalid response from API');
|
538 |
}
|
539 |
} catch (error) {
|
540 |
+
console.error('Error calling LLM API:', error);
|
541 |
alert(`Failed to generate ${mode === 'flashcard' ? 'flashcards' : 'explanation'}. Please check your API key and try again.`);
|
542 |
} finally {
|
|
|
543 |
setTimeout(() => {
|
544 |
document.body.removeChild(notification);
|
545 |
submitBtn.disabled = false;
|
546 |
+
submitBtn.style.backgroundColor = '';
|
547 |
}, 3000);
|
548 |
}
|
549 |
} else {
|
550 |
+
alert(`Please select some text to generate ${mode === 'flashcard' ? 'flashcards' : 'an explanation'}.`);
|
551 |
}
|
552 |
}
|
553 |
|
|
|
588 |
}
|
589 |
}
|
590 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
591 |
function displayFlashcards(flashcards, append = false) {
|
592 |
if (!append) {
|
593 |
flashcardsContainer.innerHTML = ''; // Clear existing flashcards only if not appending
|
|
|
1345 |
console.error('Error:', error);
|
1346 |
});
|
1347 |
}
|
1348 |
+
|
1349 |
+
document.addEventListener("DOMContentLoaded", () => {
|
1350 |
+
// Ensure the variables are defined from /static/js/prompts.js
|
1351 |
+
if (typeof FLASHCARD_PROMPT !== 'undefined') {
|
1352 |
+
document.getElementById('system-prompt').value = FLASHCARD_PROMPT;
|
1353 |
+
}
|
1354 |
+
if (typeof EXPLAIN_PROMPT !== 'undefined') {
|
1355 |
+
document.getElementById('explain-prompt').value = EXPLAIN_PROMPT;
|
1356 |
+
}
|
1357 |
+
if (typeof LANGUAGE_PROMPT !== 'undefined') {
|
1358 |
+
document.getElementById('language-prompt').value = LANGUAGE_PROMPT;
|
1359 |
+
}
|
1360 |
+
|
1361 |
+
// Populate the model select options
|
1362 |
+
const modelSelect = document.getElementById('model-select');
|
1363 |
+
availableModels.forEach(model => {
|
1364 |
+
const option = document.createElement('option');
|
1365 |
+
option.value = model;
|
1366 |
+
option.textContent = model;
|
1367 |
+
modelSelect.appendChild(option);
|
1368 |
+
});
|
1369 |
+
|
1370 |
+
// Set default model to Gemini
|
1371 |
+
modelSelect.value = "gemini/gemini-exp-1206";
|
1372 |
+
selectedModel = "gemini/gemini-exp-1206";
|
1373 |
+
|
1374 |
+
// Update API key placeholder based on selected model
|
1375 |
+
modelSelect.addEventListener('change', function() {
|
1376 |
+
selectedModel = this.value;
|
1377 |
+
const requiredKey = MODEL_API_KEY_MAPPING[selectedModel];
|
1378 |
+
apiKeyInput.placeholder = `Enter ${requiredKey}`;
|
1379 |
+
});
|
1380 |
+
|
1381 |
+
// Set initial API key placeholder
|
1382 |
+
const initialKey = MODEL_API_KEY_MAPPING["gemini/gemini-exp-1206"];
|
1383 |
+
apiKeyInput.placeholder = `Enter ${initialKey}`;
|
1384 |
+
});
|
1385 |
</script>
|
1386 |
</body>
|
1387 |
|