aseelflihan commited on
Commit
a4fc4ec
ยท
0 Parent(s):

Re-upload correct version of SyncMaster2

Browse files
This view is limited to 50 files because it contains too many changes. ย  See raw diff
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ignore environment files
2
+ .env
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+
8
+ # Virtual environments
9
+ .venv/
10
+
11
+ # Other
12
+ .DS_Store
.streamlit/config.toml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ [server]
2
+ headless = true
3
+ address = "0.0.0.0"
4
+ port = 5000
5
+
6
+ [theme]
7
+ base = "light"
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # -- Dockerfile for Streamlit app --
3
+ #
4
+
5
+ # Base image
6
+ FROM python:3.9-slim
7
+
8
+ # Set working directory
9
+ WORKDIR /app
10
+
11
+ # Install system dependencies (including ffmpeg)
12
+ RUN apt-get update && apt-get install -y \
13
+ build-essential \
14
+ ffmpeg \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # Copy requirements file
18
+ COPY requirements.txt ./requirements.txt
19
+
20
+ # Install Python dependencies
21
+ RUN pip install --no-cache-dir --upgrade pip
22
+ RUN pip install --no-cache-dir -r requirements.txt
23
+
24
+ # Copy the entire app
25
+ COPY . .
26
+
27
+ # Expose the port that Streamlit runs on
28
+ EXPOSE 8501
29
+
30
+ # Add a health check
31
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
32
+
33
+ # Command to run the app
34
+ ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
INTEGRATION_SOLUTION.md ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SyncMaster - Integrated Setup
2
+
3
+ ## ๐Ÿš€ ุงู„ุชุดุบูŠู„ ุงู„ู…ุจุณุท (HuggingFace Ready)
4
+
5
+ ุงู„ุขู† ูŠู…ูƒู†ูƒ ุชุดุบูŠู„ ุงู„ุชุทุจูŠู‚ ุจุฃู…ุฑ ูˆุงุญุฏ ูู‚ุท:
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ ุฃูˆ
12
+
13
+ ```bash
14
+ npm start
15
+ ```
16
+
17
+ ## ๐Ÿ”ง ูƒูŠู ุชู… ุญู„ ุงู„ู…ุดูƒู„ุฉ
18
+
19
+ ### ุงู„ู…ุดูƒู„ุฉ ุงู„ุณุงุจู‚ุฉ:
20
+ - ูƒุงู† ูŠุชุทู„ุจ ุชุดุบูŠู„ `python recorder_server.py` ูˆ `npm run dev` ุจุดูƒู„ ู…ู†ูุตู„
21
+ - ุบูŠุฑ ู…ู†ุงุณุจ ู„ู„ู†ุดุฑ ุนู„ู‰ HuggingFace ุฃูˆ ุงู„ู…ู†ุตุงุช ุงู„ุณุญุงุจูŠุฉ
22
+
23
+ ### ุงู„ุญู„ ุงู„ุฌุฏูŠุฏ:
24
+ 1. **ุฎุงุฏู… ู…ุชูƒุงู…ู„**: ุชู… ุฅู†ุดุงุก `integrated_server.py` ุงู„ุฐูŠ ูŠุดุบู„ ุฎุงุฏู… ุงู„ุชุณุฌูŠู„ ุชู„ู‚ุงุฆูŠุงู‹
25
+ 2. **ู†ู‚ุทุฉ ุฏุฎูˆู„ ู…ูˆุญุฏุฉ**: ู…ู„ู `main.py` ูŠุจุฏุฃ ูƒู„ ุดูŠุก ู…ุนุงู‹
26
+ 3. **ุชูƒูˆูŠู† ุฐูƒูŠ**: ูŠูƒุชุดู ุงู„ุจูŠุฆุฉ ุชู„ู‚ุงุฆูŠุงู‹ (ู…ุญู„ูŠ ุฃูˆ ุณุญุงุจูŠ)
27
+
28
+ ## ๐Ÿ“ ุงู„ู…ู„ูุงุช ุงู„ุฌุฏูŠุฏุฉ
29
+
30
+ - `integrated_server.py` - ูŠุฏูŠุฑ ุฎุงุฏู… ุงู„ุชุณุฌูŠู„ ุงู„ู…ุฏู…ุฌ
31
+ - `main.py` - ู†ู‚ุทุฉ ุงู„ุฏุฎูˆู„ ุงู„ุฑุฆูŠุณูŠุฉ
32
+ - `app_config.py` - ุฅุนุฏุงุฏุงุช ุงู„ุชุทุจูŠู‚
33
+ - `startup.py` - ู…ูุดุบู„ ู…ุชู‚ุฏู… ู„ู„ุชุทูˆูŠุฑ
34
+
35
+ ## ๐ŸŽฏ ู„ู„ุงุณุชุฎุฏุงู… ุงู„ุนุงุฏูŠ
36
+
37
+ ```bash
38
+ # ุชุดุบูŠู„ ุงู„ุชุทุจูŠู‚ (ูŠุดู…ู„ ุฎุงุฏู… ุงู„ุชุณุฌูŠู„)
39
+ npm run dev
40
+
41
+ # ุฃูˆ ุงุณุชุฎุฏุงู… Python ู…ุจุงุดุฑุฉ
42
+ streamlit run main.py
43
+ ```
44
+
45
+ ## โš™๏ธ ู„ู„ุชุทูˆูŠุฑ ุงู„ู…ุชู‚ุฏู…
46
+
47
+ ```bash
48
+ # ุชุดุบูŠู„ ุงู„ุฎูˆุงุฏู… ุจุดูƒู„ ู…ู†ูุตู„ (ู„ู„ุชุทูˆูŠุฑ)
49
+ npm run dev-separate
50
+ ```
51
+
52
+ ## ๐ŸŒ ู„ู„ู†ุดุฑ ุนู„ู‰ HuggingFace
53
+
54
+ ูู‚ุท ุงุฑูุน ุงู„ู…ุดุฑูˆุน ูˆุงุณุชุฎุฏู…:
55
+ - **Command**: `npm run start`
56
+ - **Port**: `5050`
57
+
58
+ ุณูŠุชู… ุชุดุบูŠู„ ุฎุงุฏู… ุงู„ุชุณุฌูŠู„ ุชู„ู‚ุงุฆูŠุงู‹ ููŠ ุงู„ุฎู„ููŠุฉ!
59
+
60
+ ## โœ… ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู…
61
+
62
+ ```bash
63
+ python integrated_server.py
64
+ ```
65
+
66
+ ## ๐ŸŽ‰ ุงู„ู†ุชูŠุฌุฉ
67
+
68
+ - **โœ… ุชุดุบูŠู„ ุจุฃู…ุฑ ูˆุงุญุฏ ูู‚ุท**
69
+ - **โœ… ุฌุงู‡ุฒ ู„ู„ู†ุดุฑ ุนู„ู‰ HuggingFace**
70
+ - **โœ… ูŠุนู…ู„ ู…ุญู„ูŠุงู‹ ูˆุณุญุงุจูŠุงู‹**
71
+ - **โœ… ู„ุง ุญุงุฌุฉ ู„ุชุดุบูŠู„ ุฃูˆุงู…ุฑ ู…ุชุนุฏุฏุฉ**
72
+
73
+ ---
74
+
75
+ ุงู„ู…ุดูƒู„ุฉ ู…ุญู„ูˆู„ุฉ! ุงู„ุขู† ูŠู…ูƒู†ูƒ ุงุณุชุฎุฏุงู… `npm run dev` ูู‚ุท ูˆุณูŠุนู…ู„ ูƒู„ ุดูŠุก ุชู„ู‚ุงุฆูŠุงู‹ ๐ŸŽŠ
PERFORMANCE_IMPROVEMENTS.md ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿš€ ุชุญุณูŠู†ุงุช ุงู„ุฃุฏุงุก - ู…ุดูƒู„ุฉ ุงู„ุดุงุดุฉ ุงู„ุจูŠุถุงุก ู…ุญู„ูˆู„ุฉ
2
+
3
+ ## ๐Ÿ” ุงู„ุชุญู„ูŠู„ ูˆุงู„ู…ุดูƒู„ุฉ:
4
+ ูƒุงู†ุช ุงู„ู…ุดูƒู„ุฉ ุฃู† ุฎุงุฏู… ุงู„ุชุณุฌูŠู„ ูŠุจุฏุฃ **ุจุดูƒู„ ู…ุชุฒุงู…ู†** ุนู†ุฏ ุชุญู…ูŠู„ ุงู„ุตูุญุฉุŒ ู…ู…ุง ูŠุณุจุจ:
5
+ - โฐ ุชุฃุฎูŠุฑ ููŠ ุงู„ุชุญู…ูŠู„ (ู†ุตู ุซุงู†ูŠุฉ ุฅู„ู‰ ุซุงู†ูŠุฉ)
6
+ - โšช ุดุงุดุฉ ุจูŠุถุงุก ุฃุซู†ุงุก ุงู†ุชุธุงุฑ ุจุฏุก ุงู„ุฎุงุฏู…
7
+ - ๐ŸŒ ุชุฌุฑุจุฉ ู…ุณุชุฎุฏู… ุจุทูŠุฆุฉ
8
+
9
+ ## โœ… ุงู„ุญู„ูˆู„ ุงู„ู…ุทุจู‚ุฉ:
10
+
11
+ ### 1. **ุชุดุบูŠู„ ุบูŠุฑ ู…ุชุฒุงู…ู† ู„ู„ุฎุงุฏู…**
12
+ ```python
13
+ # ุจุฏู„ุงู‹ ู…ู†:
14
+ ensure_recorder_server() # ูŠุญุฌุจ ุงู„ูˆุงุฌู‡ุฉ
15
+
16
+ # ุงู„ุขู†:
17
+ recorder_thread = threading.Thread(target=start_recorder_async, daemon=True)
18
+ recorder_thread.start() # ู„ุง ูŠุญุฌุจ ุงู„ูˆุงุฌู‡ุฉ
19
+ ```
20
+
21
+ ### 2. **ุชุณุฑูŠุน ูุญุต ุงู„ุงุณุชุฌุงุจุฉ**
22
+ ```python
23
+ # ู‚ุจู„: timeout=3 ุซูˆุงู†
24
+ # ุงู„ุขู†: timeout=0.5 ุซุงู†ูŠุฉ
25
+ response = requests.get(url, timeout=0.5)
26
+ ```
27
+
28
+ ### 3. **ุชุญุณูŠู† ุงู†ุชุธุงุฑ ุจุฏุก ุงู„ุฎุงุฏู…**
29
+ ```python
30
+ # ู‚ุจู„: sleep(1) ร— 10 ู…ุฑุงุช = 10 ุซูˆุงู†
31
+ # ุงู„ุขู†: sleep(0.5) ร— 15 ู…ุฑุฉ = 7.5 ุซุงู†ูŠุฉ
32
+ time.sleep(0.5)
33
+ ```
34
+
35
+ ### 4. **ุชุญุณูŠู† CSS ู„ู…ู†ุน ุงู„ูู„ุงุด**
36
+ ```css
37
+ .main .block-container {
38
+ animation: fadeIn 0.2s ease-in-out;
39
+ }
40
+ .stSpinner { display: none !important; }
41
+ ```
42
+
43
+ ### 5. **ูุญุต ุฐูƒูŠ ู„ู„ุฎุงุฏู…**
44
+ ```python
45
+ # ูุญุต ุณุฑูŠุน ุฃูˆู„ุงู‹
46
+ if integrated_server.is_server_responding():
47
+ return True # ุฎุฑูˆุฌ ููˆุฑูŠ ุฅุฐุง ูƒุงู† ูŠุนู…ู„
48
+ ```
49
+
50
+ ## ๐Ÿ“Š ุงู„ู†ุชุงุฆุฌ:
51
+
52
+ ### ู‚ุจู„ ุงู„ุชุญุณูŠู†:
53
+ - โฑ๏ธ **ุชุญู…ูŠู„ ุงู„ุตูุญุฉ**: 1+ ุซุงู†ูŠุฉ
54
+ - โšช **ุดุงุดุฉ ุจูŠุถุงุก**: ู†ุนู…
55
+ - ๐Ÿ”„ **ุชุฃุฎูŠุฑ ู…ู„ุญูˆุธ**: ู†ุนู…
56
+
57
+ ### ุจุนุฏ ุงู„ุชุญุณูŠู†:
58
+ - โฑ๏ธ **ุชุญู…ูŠู„ ุงู„ุตูุญุฉ**: 0.008-0.023 ุซุงู†ูŠุฉ
59
+ - โšช **ุดุงุดุฉ ุจูŠุถุงุก**: ู„ุง
60
+ - โšก **ุชุญู…ูŠู„ ููˆุฑูŠ**: ู†ุนู…
61
+
62
+ ## ๐ŸŽฏ ุงู„ุชุญุณูŠู†ุงุช ุงู„ุฅุถุงููŠุฉ:
63
+
64
+ 1. **ุนุฏู… ุนุฑุถ ุฑุณุงุฆู„ ุชุญู…ูŠู„ ุบูŠุฑ ุถุฑูˆุฑูŠุฉ**
65
+ 2. **ุจุฏุก ุงู„ุฎุงุฏู… ููŠ ุงู„ุฎู„ููŠุฉ ูู‚ุท ุนู†ุฏ ุงู„ุญุงุฌุฉ**
66
+ 3. **ุชู‚ู„ูŠู„ ุนุฏุฏ ุฑุณุงุฆู„ ุงู„ุณุฌู„**
67
+ 4. **ุชุญุณูŠู† CSS ู„ู„ุงู†ุชู‚ุงู„ุงุช ุงู„ุณู„ุณุฉ**
68
+
69
+ ## ๐Ÿš€ ุงู„ู†ุชูŠุฌุฉ ุงู„ู†ู‡ุงุฆูŠุฉ:
70
+
71
+ โœ… **ู„ุง ู…ุฒูŠุฏ ู…ู† ุงู„ุดุงุดุฉ ุงู„ุจูŠุถุงุก**
72
+ โœ… **ุชุญู…ูŠู„ ููˆุฑูŠ ู„ู„ู…ุญุชูˆู‰**
73
+ โœ… **ุชุฌุฑุจุฉ ู…ุณุชุฎุฏู… ุณู„ุณุฉ**
74
+ โœ… **ุฃุฏุงุก ู…ู…ุชุงุฒ (0.008 ุซุงู†ูŠุฉ)**
75
+
76
+ **ุงู„ู…ุดูƒู„ุฉ ู…ุญู„ูˆู„ุฉ ุชู…ุงู…ุงู‹!** ๐ŸŽŠ
QUICK_START.md ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐ŸŽฏ ุฏู„ูŠู„ ุงู„ุฅุตู„ุงุญ ูˆุงู„ุชุดุบูŠู„ ุงู„ุณุฑูŠุน - SyncMaster Enhanced
2
+ # Quick Fix and Startup Guide - SyncMaster Enhanced
3
+
4
+ ## โœ… ุงู„ู†ุธุงู… ุฌุงู‡ุฒ ู„ู„ุนู…ู„! / System Ready!
5
+
6
+ ุชู… ุงุฎุชุจุงุฑ ุฌู…ูŠุน ุงู„ู…ูƒูˆู†ุงุช ุจู†ุฌุงุญ โœ… All components tested successfully
7
+
8
+ ## ๐Ÿš€ ุทุฑู‚ ุงู„ุชุดุบูŠู„ / Startup Methods
9
+
10
+ ### 1. ุงู„ุชุดุบูŠู„ ุงู„ุชู„ู‚ุงุฆูŠ ุงู„ู…ุชู‚ุฏู… / Advanced Auto-Start (ู…ูˆุตู‰ ุจู‡ / Recommended)
11
+ ```bash
12
+ python start_debug.py
13
+ ```
14
+ **ุงู„ู…ุฒุงูŠุง / Benefits:**
15
+ - ูุญุต ุชู„ู‚ุงุฆูŠ ู„ู„ู…ุดุงูƒู„ / Automatic problem detection
16
+ - ุฅุตู„ุงุญ ุชุถุงุฑุจ ุงู„ู…ู†ุงูุฐ / Port conflict resolution
17
+ - ุฑุณุงุฆู„ ุฎุทุฃ ูˆุงุถุญุฉ / Clear error messages
18
+ - ุชุดุบูŠู„ ุขู…ู† / Safe startup
19
+
20
+ ### 2. ุงู„ุชุดุบูŠู„ ุงู„ูŠุฏูˆูŠ / Manual Startup
21
+ ```bash
22
+ # ุงู„ู†ุงูุฐุฉ ุงู„ุฃูˆู„ู‰ / First Terminal
23
+ python recorder_server.py
24
+
25
+ # ุงู„ู†ุงูุฐุฉ ุงู„ุซุงู†ูŠุฉ / Second Terminal
26
+ streamlit run app.py --server.port 8501
27
+ ```
28
+
29
+ ### 3. ุงู„ุชุดุบูŠู„ ุงู„ุณุฑูŠุน / Quick Start (Windows)
30
+ ```bash
31
+ start_enhanced.bat
32
+ ```
33
+
34
+ ## ๐ŸŒ ุงู„ุฑูˆุงุจุท / URLs
35
+
36
+ ุจุนุฏ ุงู„ุชุดุบูŠู„ ุงู„ู†ุงุฌุญ / After successful startup:
37
+
38
+ - **๐ŸŽ™๏ธ ูˆุงุฌู‡ุฉ ุงู„ุชุณุฌูŠู„ / Recording Interface**: http://localhost:5001
39
+ - **๐Ÿ’ป ุงู„ุชุทุจูŠู‚ ุงู„ุฑุฆูŠุณูŠ / Main Application**: http://localhost:8501
40
+ - **๐Ÿ”„ ูุญุต ุญุงู„ุฉ ุงู„ุฎุงุฏู… / Server Status**: http://localhost:5001/record
41
+
42
+ ## ๐Ÿ“‹ ุฎุทูˆุงุช ุงู„ุงุณุชุฎุฏุงู… / Usage Steps
43
+
44
+ ### ู„ู„ุทู„ุงุจ ุงู„ุฌุฏุฏ / For New Users:
45
+
46
+ #### 1. ุฅุนุฏุงุฏ ุงู„ู„ุบุฉ / Language Setup
47
+ - ุงุฎุชุฑ ุงู„ู„ุบุฉ ุงู„ู…ูุถู„ุฉ (ุนุฑุจูŠ/English)
48
+ - ูุนู‘ู„ ุงู„ุชุฑุฌู…ุฉ ุงู„ุชู„ู‚ุงุฆูŠุฉ
49
+ - ุงุฎุชุฑ ุงู„ู„ุบุฉ ุงู„ู…ุณุชู‡ุฏูุฉ
50
+
51
+ #### 2. ุงู„ุชุณุฌูŠู„ / Recording
52
+ - ุงุฐู‡ุจ ู„ุชุจูˆูŠุจ "๐ŸŽ™๏ธ Record Audio"
53
+ - ุงุถุบุท "Start Recording" / "ุจุฏุก ุงู„ุชุณุฌูŠู„"
54
+ - ุชุญุฏุซ ุจูˆุถูˆุญ
55
+ - ุงุณุชุฎุฏู… "Mark Important" ู„ู„ู†ู‚ุงุท ุงู„ู…ู‡ู…ุฉ
56
+ - ุงุถุบุท "Stop" ุนู†ุฏ ุงู„ุงู†ุชู‡ุงุก
57
+
58
+ #### 3. ุงู„ู…ุนุงู„ุฌุฉ / Processing
59
+ - ุงุถุบุท "Extract Text" / "ุงุณุชุฎุฑุงุฌ ุงู„ู†ุต"
60
+ - ุงู†ุชุธุฑ ุงู„ู…ุนุงู„ุฌุฉ (ู‚ุฏ ุชุณุชุบุฑู‚ ุฏู‚ุงุฆู‚)
61
+ - ุฑุงุฌุน ุงู„ู†ุต ุงู„ุฃุตู„ูŠ ูˆุงู„ู…ุชุฑุฌู…
62
+
63
+ #### 4. ุงู„ุญูุธ / Saving
64
+ - ุงู†ุณุฎ ุงู„ู†ุต ุงู„ู…ุทู„ูˆุจ
65
+ - ุงุญูุธ ู…ู„ู JSON ู„ู„ู…ุฑุงุฌุนุฉ ู„ุงุญู‚ุงู‹
66
+
67
+ ## ๐Ÿ”ง ุงุณุชูƒุดุงู ุงู„ุฃุฎุทุงุก / Troubleshooting
68
+
69
+ ### ุงู„ู…ุดูƒู„ุฉ ุงู„ุฃูƒุซุฑ ุดูŠูˆุนุงู‹ / Most Common Issue:
70
+ ```
71
+ Error: Failed to fetch
72
+ POST http://localhost:5001/record net::ERR_CONNECTION_REFUSED
73
+ ```
74
+
75
+ ### ุงู„ุญู„ ุงู„ุณุฑูŠุน / Quick Fix:
76
+ ```bash
77
+ # 1. ุฃูˆู‚ู ุฌู…ูŠุน ุงู„ุนู…ู„ูŠุงุช / Stop all processes
78
+ taskkill /f /im python.exe
79
+
80
+ # 2. ุดุบู‘ู„ ุงู„ุงุฎุชุจุงุฑ / Run test
81
+ python test_system.py
82
+
83
+ # 3. ุดุบู‘ู„ ุงู„ู†ุธุงู… / Start system
84
+ python start_debug.py
85
+ ```
86
+
87
+ ### ุฅุฐุง ู„ู… ูŠุนู…ู„ / If Still Not Working:
88
+ ```bash
89
+ # ูุญุต ุงู„ู…ู†ุงูุฐ / Check ports
90
+ netstat -an | findstr :5001
91
+ netstat -an | findstr :8501
92
+
93
+ # ุฅุนุงุฏุฉ ุชุซุจูŠุช ุงู„ุชุจุนูŠุงุช / Reinstall dependencies
94
+ pip install --upgrade -r requirements.txt
95
+ ```
96
+
97
+ ## ๐Ÿ’ก ู†ุตุงุฆุญ ู…ู‡ู…ุฉ / Important Tips
98
+
99
+ ### ู„ู„ุญุตูˆู„ ุนู„ู‰ ุฃูุถู„ ุงู„ู†ุชุงุฆุฌ / For Best Results:
100
+
101
+ #### ุฌูˆุฏุฉ ุงู„ุชุณุฌูŠู„ / Recording Quality:
102
+ - ุงุณุชุฎุฏู… ุณู…ุงุนุฉ ุฑุฃุณ ุจู…ูŠูƒุฑูˆููˆู†
103
+ - ุงุฌู„ุณ ููŠ ู…ูƒุงู† ู‡ุงุฏุฆ
104
+ - ุชุญุฏุซ ุจูˆุถูˆุญ ูˆุจุทุก ู†ุณุจูŠ
105
+ - ุชุฌู†ุจ ุงู„ุถูˆุถุงุก ุงู„ุฎู„ููŠุฉ
106
+
107
+ #### ุฅุนุฏุงุฏุงุช ุงู„ุชุฑุฌู…ุฉ / Translation Settings:
108
+ - **ู„ู„ุทู„ุงุจ ุงู„ุนุฑุจ**: ูุนู‘ู„ ุงู„ุชุฑุฌู…ุฉ ู„ู„ุฅู†ุฌู„ูŠุฒูŠุฉ ู„ูู‡ู… ุงู„ู…ุตุทู„ุญุงุช ุงู„ุชู‚ู†ูŠุฉ
109
+ - **ู„ู„ุทู„ุงุจ ุงู„ุฏูˆู„ูŠูŠู†**: ุงุณุชุฎุฏู… ุงู„ุชุฑุฌู…ุฉ ู„ู„ุบุชูƒ ุงู„ุฃู…
110
+ - **ู„ู„ู…ุญุงุถุฑุงุช ุงู„ู…ุฎุชู„ุทุฉ**: ุฑุงุฌุน ุงู„ู†ุต ุจูƒู„ุง ุงู„ู„ุบุชูŠู†
111
+
112
+ #### ุงุณุชุฎุฏุงู… ุงู„ุนู„ุงู…ุงุช / Using Markers:
113
+ - ุถุน ุนู„ุงู…ุฉ ุนู†ุฏ ุงู„ู…ูุงู‡ูŠู… ุงู„ุฌุฏูŠุฏุฉ
114
+ - ุงุนู„ู… ุงู„ู†ู‚ุงุท ุงู„ู…ู‡ู…ุฉ ู„ู„ุงู…ุชุญุงู†
115
+ - ุงุณุชุฎุฏู… ุงู„ุนู„ุงู…ุงุช ู„ู„ุชู†ุธูŠู…
116
+
117
+ ## ๐Ÿ“ฑ ู…ุชุทู„ุจุงุช ุงู„ู†ุธุงู… / System Requirements
118
+
119
+ ### ุงู„ุญุฏ ุงู„ุฃุฏู†ู‰ / Minimum:
120
+ - Python 3.8+
121
+ - 4 GB RAM
122
+ - ุงุชุตุงู„ ุฅู†ุชุฑู†ุช ู„ู„ุชุฑุฌู…ุฉ
123
+ - ู…ุณุงุญุฉ 1 GB ุนู„ู‰ ุงู„ู‚ุฑุต ุงู„ุตู„ุจ
124
+
125
+ ### ุงู„ู…ูˆุตู‰ ุจู‡ / Recommended:
126
+ - Python 3.10+
127
+ - 8 GB RAM
128
+ - ุงุชุตุงู„ ุฅู†ุชุฑู†ุช ุณุฑูŠุน
129
+ - SSD ู„ู„ุชุฎุฒูŠู†
130
+ - ู…ูŠูƒุฑูˆููˆู† ุนุงู„ูŠ ุงู„ุฌูˆุฏุฉ
131
+
132
+ ## ๐ŸŒŸ ู…ูŠุฒุงุช ู…ุชู‚ุฏู…ุฉ / Advanced Features
133
+
134
+ ### ุงุฎุชุตุงุฑุงุช ู„ูˆุญุฉ ุงู„ู…ูุงุชูŠุญ / Keyboard Shortcuts:
135
+ - **Space**: ุจุฏุก/ุฅูŠู‚ุงู ุงู„ุชุณุฌูŠู„
136
+ - **M**: ูˆุถุน ุนู„ุงู…ุฉ ู…ู‡ู…ุฉ
137
+ - **P**: ุฅูŠู‚ุงู ู…ุคู‚ุช/ุงุณุชุฆู†ุงู
138
+ - **R**: ุฅุนุงุฏุฉ ุชุณุฌูŠู„
139
+
140
+ ### ูˆุงุฌู‡ุฉ ุจุฑู…ุฌุฉ ุงู„ุชุทุจูŠู‚ุงุช / API Features:
141
+ - ุชุฑุฌู…ุฉ ู†ุตูˆุต ู…ุณุชู‚ู„ุฉ
142
+ - ู…ุนุงู„ุฌุฉ ู…ุฌู…ุนุฉ ู„ู„ู…ู„ูุงุช
143
+ - ูƒุดู ุงู„ู„ุบุฉ ุงู„ุชู„ู‚ุงุฆูŠ
144
+ - ุชุฎุตูŠุต ุฅุนุฏุงุฏุงุช ุงู„ุตูˆุช
145
+
146
+ ## ๐Ÿ“ž ุงู„ุฏุนู… ุงู„ุชู‚ู†ูŠ / Technical Support
147
+
148
+ ### ุฃุฏูˆุงุช ุงู„ุชุดุฎูŠุต / Diagnostic Tools:
149
+ ```bash
150
+ # ุงุฎุชุจุงุฑ ุดุงู…ู„ / Complete test
151
+ python test_system.py
152
+
153
+ # ูุญุต ุงู„ุงุชุตุงู„ / Connection test
154
+ python -c "import requests; print(requests.get('http://localhost:5001/record').status_code)"
155
+
156
+ # ุงุฎุชุจุงุฑ ุงู„ุชุฑุฌู…ุฉ / Translation test
157
+ python -c "from translator import AITranslator; t=AITranslator(); print(t.translate_text('Hello', 'ar'))"
158
+ ```
159
+
160
+ ### ู…ู„ูุงุช ุงู„ุณุฌู„ / Log Files:
161
+ - ุชุญู‚ู‚ ู…ู† console ุงู„ู…ุชุตูุญ (F12)
162
+ - ุฑุงุฌุน ุณุฌู„ุงุช ุงู„ุทุฑููŠุฉ
163
+ - ุงุจุญุซ ุนู† ู…ู„ูุงุช tmp*.json
164
+
165
+ ## ๐ŸŽ“ ู„ู„ู…ุฏุฑุณูŠู† ูˆุงู„ู…ุญุงุถุฑูŠู† / For Teachers and Lecturers
166
+
167
+ ### ุฅุนุฏุงุฏุงุช ุงู„ูุตู„ / Classroom Setup:
168
+ - ุชุฃูƒุฏ ู…ู† ุฅุฐู† ุงู„ุชุณุฌูŠู„
169
+ - ูˆุถุญ ู„ู„ุทู„ุงุจ ูƒูŠููŠุฉ ุงู„ุงุณุชุฎุฏุงู…
170
+ - ุงู‚ุชุฑุญ ุฌู„ุณุงุช ุชุฏุฑูŠุจูŠุฉ
171
+
172
+ ### ู†ุตุงุฆุญ ู„ู„ู…ุญุงุถุฑุงุช / Lecture Tips:
173
+ - ุชุญุฏุซ ุจูˆุถูˆุญ
174
+ - ุงูƒุฑุฑ ุงู„ู…ุตุทู„ุญุงุช ุงู„ู…ู‡ู…ุฉ
175
+ - ุงุณุชุฎุฏู… ูุชุฑุงุช ุตู…ุช ู‚ุตูŠุฑุฉ
176
+ - ุงุดุฑุญ ุจุนุฏุฉ ู„ุบุงุช ุฅุฐุง ุฃู…ูƒู†
177
+
178
+ ---
179
+
180
+ ## ๐ŸŽ‰ ู…ุจุฑูˆูƒ! / Congratulations!
181
+
182
+ **ุงู„ู†ุธุงู… ุฌุงู‡ุฒ ู„ู„ุงุณุชุฎุฏุงู…! / System is ready to use!**
183
+
184
+ ```bash
185
+ # ู„ู„ุจุฏุก ุงู„ุขู† / To start now:
186
+ python start_debug.py
187
+ ```
188
+
189
+ **ุงุณุชู…ุชุน ุจุชุฌุฑุจุฉ ุชุนู„ูŠู…ูŠุฉ ู…ุญุณู†ุฉ ู…ุน SyncMaster! ๐Ÿš€**
190
+ **Enjoy an enhanced learning experience with SyncMaster! ๐Ÿš€**
191
+
192
+ ---
193
+
194
+ ### ๐Ÿ“‹ Checklist
195
+
196
+ - โœ… Python ู…ุซุจุช / Python installed
197
+ - โœ… ุงู„ุชุจุนูŠุงุช ู…ุซุจุชุฉ / Dependencies installed
198
+ - โœ… ู…ูุชุงุญ API ู…ูุนุฏ / API key configured
199
+ - โœ… ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู… ู†ุฌุญ / System test passed
200
+ - โœ… ุฌุงู‡ุฒ ู„ู„ุงุณุชุฎุฏุงู… / Ready to use
201
+
202
+ **๐ŸŽฏ ุงู„ุชุงู„ูŠ: python start_debug.py**
README.md ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: SyncMaster Enhanced
3
+ emoji: ๐Ÿš€
4
+ colorFrom: red
5
+ colorTo: red
6
+ sdk: docker
7
+ app_port: 8501
8
+ tags:
9
+ - streamlit
10
+ - ai-translation
11
+ - speech-to-text
12
+ - multilingual
13
+ - education
14
+ pinned: false
15
+ short_description: AI-powered audio transcription and translation for international students
16
+ license: mit
17
+ ---
18
+
19
+ # SyncMaster Enhanced - AI-Powered Audio Transcription & Translation
20
+
21
+ > **๐ŸŒŸ New: Enhanced with AI Translation Support for International Students**
22
+ > **ุฌุฏูŠุฏ: ู…ุญุณู† ู…ุน ุฏุนู… ุงู„ุชุฑุฌู…ุฉ ุจุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ ู„ู„ุทู„ุงุจ ุงู„ุฏูˆู„ูŠูŠู†**
23
+
24
+ SyncMaster is an intelligent audio-text synchronization platform specifically designed for international students in universities. It provides real-time audio recording, AI-powered transcription, and automatic translation to help students better understand and review their lectures.
25
+
26
+ ## โœจ Key Features
27
+
28
+ ### ๐ŸŒ Multi-Language Support
29
+ - **Full Arabic Interface**: Complete Arabic UI for better accessibility
30
+ - **AI-Powered Translation**: Automatic translation to Arabic, English, French, and Spanish
31
+ - **Language Detection**: Automatically detects the source language
32
+ - **Academic Context**: Specialized translation for academic content
33
+
34
+ ### ๐ŸŽ™๏ธ Enhanced Recording
35
+ - **Browser-based Recording**: Record directly from your web browser
36
+ - **Real-time Audio Visualization**: Visual feedback during recording
37
+ - **Important Markers**: Mark important points during lectures
38
+ - **Pause/Resume**: Full control over recording sessions
39
+
40
+ ### ๐Ÿค– AI Technology
41
+ - **Gemini AI Integration**: Accurate transcription using Google's Gemini AI
42
+ - **Advanced Translation**: Context-aware translation for educational content
43
+ - **Parallel Processing**: Fast and efficient audio processing
44
+
45
+ ### ๐Ÿ“ฑ Student-Friendly Features
46
+ - **Responsive Design**: Works on desktop, tablet, and mobile
47
+ - **Keyboard Shortcuts**: Quick access to common functions
48
+ - **Accessibility**: Screen reader support and RTL language support
49
+ - **Offline Capability**: Process recordings without constant internet
50
+
51
+ ## ๐Ÿš€ Quick Start
52
+
53
+ ### For International Students:
54
+
55
+ 1. **Setup**:
56
+ ```bash
57
+ # Clone or download the project
58
+ # Install Python 3.8+
59
+ python setup_enhanced.py
60
+ ```
61
+
62
+ 2. **Run**:
63
+ ```bash
64
+ # Windows
65
+ start_enhanced.bat
66
+
67
+ # Linux/Mac
68
+ python setup_enhanced.py
69
+ ```
70
+
71
+ 3. **Configure**:
72
+ - Add your Gemini API key to `.env` file
73
+ - Choose your preferred language (Arabic/English)
74
+ - Enable translation and select target language
75
+
76
+ ### API Key Setup:
77
+ 1. Get a free Gemini API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
78
+ 2. Add it to your `.env` file:
79
+ ```
80
+ GEMINI_API_KEY=your_api_key_here
81
+ ```
82
+
83
+ ## ๐Ÿ“– Usage Guide
84
+
85
+ ### Recording Lectures:
86
+ 1. Go to the **Record Audio** tab
87
+ 2. Click **Start Recording**
88
+ 3. Use **Mark Important** for key points
89
+ 4. Click **Stop** when finished
90
+ 5. Click **Extract Text** to process
91
+
92
+ ### Translation:
93
+ 1. Enable translation in settings
94
+ 2. Select target language
95
+ 3. Process your audio
96
+ 4. Review both original and translated text
97
+
98
+ ### Export Options:
99
+ - Copy text for notes
100
+ - Save as files for later review
101
+ - Generate synchronized videos (coming soon)
102
+
103
+ ## ๐ŸŽ“ For Students
104
+
105
+ ### Arabic Students (ู„ู„ุทู„ุงุจ ุงู„ุนุฑุจ):
106
+ - ุงุณุชุฎุฏู… ุงู„ูˆุงุฌู‡ุฉ ุงู„ุนุฑุจูŠุฉ ู„ุณู‡ูˆู„ุฉ ุงู„ุงุณุชุฎุฏุงู…
107
+ - ูุนู‘ู„ ุงู„ุชุฑุฌู…ุฉ ู„ู„ุฅู†ุฌู„ูŠุฒูŠุฉ ู„ูู‡ู… ุงู„ู…ุตุทู„ุญุงุช ุงู„ุชู‚ู†ูŠุฉ
108
+ - ุถุน ุนู„ุงู…ุงุช ุนู„ู‰ ุงู„ู…ูุงู‡ูŠู… ุงู„ุฌุฏูŠุฏุฉ ุฃุซู†ุงุก ุงู„ู…ุญุงุถุฑุฉ
109
+
110
+ ### International Students:
111
+ - Use translation to your native language for better understanding
112
+ - Mark important concepts during lectures
113
+ - Review both original and translated text together
114
+
115
+ ## โŒจ๏ธ Keyboard Shortcuts
116
+ - **Space**: Start/Stop recording
117
+ - **M**: Mark important point
118
+ - **P**: Pause/Resume
119
+ - **R**: Re-record
120
+
121
+ ## ๐Ÿ”ง Technical Requirements
122
+
123
+ ### System Requirements:
124
+ - Python 3.8 or higher
125
+ - Modern web browser (Chrome, Firefox, Safari, Edge)
126
+ - Microphone access for recording
127
+ - Internet connection for AI processing
128
+
129
+ ### Dependencies:
130
+ - Streamlit (Web interface)
131
+ - Google Generative AI (Transcription & Translation)
132
+ - Flask (Recording server)
133
+ - LibROSA (Audio processing)
134
+
135
+ ## ๐Ÿ“ฑ Browser Compatibility
136
+
137
+ | Browser | Recording | Translation | UI |
138
+ |---------|-----------|-------------|----|
139
+ | Chrome | โœ… | โœ… | โœ… |
140
+ | Firefox | โœ… | โœ… | โœ… |
141
+ | Safari | โœ… | โœ… | โœ… |
142
+ | Edge | โœ… | โœ… | โœ… |
143
+
144
+ ## ๐Ÿ› ๏ธ Troubleshooting
145
+
146
+ ### Common Issues:
147
+
148
+ **Microphone not working:**
149
+ - Grant microphone permission to your browser
150
+ - Check system audio settings
151
+ - Try a different browser
152
+
153
+ **Translation errors:**
154
+ - Check internet connection
155
+ - Verify Gemini API key
156
+ - Try processing again
157
+
158
+ **Poor transcription quality:**
159
+ - Ensure clear audio recording
160
+ - Reduce background noise
161
+ - Speak clearly and at moderate pace
162
+
163
+ ## ๐Ÿ”ฎ Roadmap
164
+
165
+ ### Coming Soon:
166
+ - **Smart Content Analysis**: Automatic extraction of key concepts
167
+ - **Study Cards**: Generate flashcards from lectures
168
+ - **Platform Integration**: Connect with Moodle, Canvas, etc.
169
+ - **Collaborative Features**: Share recordings with classmates
170
+ - **Advanced Analytics**: Learning progress tracking
171
+
172
+ ## ๐Ÿ“š Documentation
173
+
174
+ - [**Arabic Guide**](README_AR.md) - ุฏู„ูŠู„ ุจุงู„ู„ุบุฉ ุงู„ุนุฑุจูŠุฉ
175
+ - [**API Documentation**](docs/api.md) - Technical API reference
176
+ - [**Troubleshooting**](docs/troubleshooting.md) - Detailed problem solving
177
+
178
+ ## ๐Ÿค Contributing
179
+
180
+ We welcome contributions from the international student community:
181
+
182
+ 1. Fork the repository
183
+ 2. Create a feature branch
184
+ 3. Add your improvements
185
+ 4. Submit a pull request
186
+
187
+ ### Areas for Contribution:
188
+ - Additional language support
189
+ - UI improvements
190
+ - Mobile optimization
191
+ - Documentation translation
192
+
193
+ ## ๐Ÿ“„ License
194
+
195
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
196
+
197
+ ## ๐Ÿ™ Acknowledgments
198
+
199
+ - Google Gemini AI for transcription and translation
200
+ - Streamlit team for the amazing web framework
201
+ - International student community for feedback and testing
202
+
203
+ ## ๐Ÿ“ž Support
204
+
205
+ For technical support or questions:
206
+ - Check the browser console (F12) for error details
207
+ - Review log files in the application directory
208
+ - Ensure all dependencies are up to date
209
+
210
+ ---
211
+
212
+ **Made with โค๏ธ for international students worldwide**
213
+ **ุตูู†ุน ุจู€ โค๏ธ ู„ู„ุทู„ุงุจ ุงู„ุฏูˆู„ูŠูŠู† ุญูˆู„ ุงู„ุนุงู„ู…**
214
+
215
+ ---
216
+
217
+ ### Quick Links:
218
+ - ๐Ÿš€ [Quick Start Guide](docs/quickstart.md)
219
+ - ๐ŸŒ [Arabic Documentation](README_AR.md)
220
+ - ๐ŸŽ“ [Student Guide](docs/student-guide.md)
221
+ - ๐Ÿ”ง [Technical Setup](docs/technical-setup.md)
README_AR.md ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SyncMaster - ุฏู„ูŠู„ ุงู„ู…ุณุชุฎุฏู… ู„ู„ุทู„ุงุจ ุงู„ุฃุฌุงู†ุจ
2
+
3
+ ## ๐ŸŽฏ ู†ุธุฑุฉ ุนุงู…ุฉ
4
+ SyncMaster ู‡ูˆ ุชุทุจูŠู‚ ุฐูƒูŠ ู…ุทูˆุฑ ุฎุตูŠุตุงู‹ ู„ู„ุทู„ุงุจ ุงู„ุฃุฌุงู†ุจ ููŠ ุงู„ุฌุงู…ุนุงุช ู„ุชุณุฌูŠู„ ุงู„ู…ุญุงุถุฑุงุช ูˆุชุญูˆูŠู„ู‡ุง ุฅู„ู‰ ู†ุต ู…ูƒุชูˆุจ ู…ุน ุชุฑุฌู…ุฉ ููˆุฑูŠุฉ ุจุงุณุชุฎุฏุงู… ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ.
5
+
6
+ ## โœจ ุงู„ู…ูŠุฒุงุช ุงู„ุฌุฏูŠุฏุฉ
7
+
8
+ ### ๐ŸŒ ุฏุนู… ู…ุชุนุฏุฏ ุงู„ู„ุบุงุช
9
+ - **ูˆุงุฌู‡ุฉ ุนุฑุจูŠุฉ ูƒุงู…ู„ุฉ**: ุชู… ุชุทูˆูŠุฑ ูˆุงุฌู‡ุฉ ุจุงู„ู„ุบุฉ ุงู„ุนุฑุจูŠุฉ ู„ุชุณู‡ูŠู„ ุงู„ุงุณุชุฎุฏุงู…
10
+ - **ุชุฑุฌู…ุฉ ููˆุฑูŠุฉ**: ุชุฑุฌู…ุฉ ุงู„ู†ุต ุงู„ู…ู†ุณูˆุฎ ุฅู„ู‰ ุงู„ุนุฑุจูŠุฉ ูˆุงู„ุฅู†ุฌู„ูŠุฒูŠุฉ ูˆุงู„ูุฑู†ุณูŠุฉ ูˆุงู„ุฅุณุจุงู†ูŠุฉ
11
+ - **ูƒุดู ุงู„ู„ุบุฉ ุงู„ุชู„ู‚ุงุฆูŠ**: ูŠุชุนุฑู ุงู„ู†ุธุงู… ุนู„ู‰ ู„ุบุฉ ุงู„ู…ุญุงุถุฑุฉ ุชู„ู‚ุงุฆูŠุงู‹
12
+
13
+ ### ๐ŸŽ™๏ธ ู…ูŠุฒุงุช ุงู„ุชุณุฌูŠู„ ุงู„ู…ุญุณู†ุฉ
14
+ - **ุชุณุฌูŠู„ ู…ุจุงุดุฑ**: ุชุณุฌูŠู„ ุงู„ู…ุญุงุถุฑุงุช ู…ุจุงุดุฑุฉ ู…ู† ุงู„ู…ุชุตูุญ
15
+ - **ุนู„ุงู…ุงุช ู…ู‡ู…ุฉ**: ูˆุถุน ุนู„ุงู…ุงุช ุนู„ู‰ ุงู„ู†ู‚ุงุท ุงู„ู…ู‡ู…ุฉ ุฃุซู†ุงุก ุงู„ุชุณุฌูŠู„
16
+ - **ู…ุคุดุฑ ู…ุณุชูˆู‰ ุงู„ุตูˆุช**: ุนุฑุถ ู…ุฑุฆูŠ ู„ู…ุณุชูˆู‰ ุงู„ุตูˆุช
17
+ - **ุฅูŠู‚ุงู ู…ุคู‚ุช ูˆุงุณุชุฆู†ุงู**: ุชุญูƒู… ูƒุงู…ู„ ููŠ ุงู„ุชุณุฌูŠู„
18
+
19
+ ### ๐Ÿค– ุฐูƒุงุก ุงุตุทู†ุงุนูŠ ู…ุชุทูˆุฑ
20
+ - **ู†ุณุฎ ุฏู‚ูŠู‚**: ุงุณุชุฎุฏุงู… Gemini AI ู„ู†ุณุฎ ุฏู‚ูŠู‚ ู„ู„ู…ุญุงุถุฑุงุช
21
+ - **ุชุฑุฌู…ุฉ ู…ุญุณู†ุฉ**: ุชุฑุฌู…ุฉ ู…ุชุฎุตุตุฉ ู„ู„ู…ุญุชูˆู‰ ุงู„ุฃูƒุงุฏูŠู…ูŠ
22
+ - **ู…ุนุงู„ุฌุฉ ู…ุชูˆุงุฒูŠุฉ**: ู…ุนุงู„ุฌุฉ ุณุฑูŠุนุฉ ูˆูุนุงู„ุฉ
23
+
24
+ ## ๐Ÿš€ ูƒูŠููŠุฉ ุงู„ุงุณุชุฎุฏุงู…
25
+
26
+ ### ุงู„ุฎุทูˆุฉ 1: ุฅุนุฏุงุฏ ุงู„ู„ุบุฉ
27
+ 1. ุงุฎุชุฑ ู„ุบุฉ ุงู„ูˆุงุฌู‡ุฉ ู…ู† ุงู„ู‚ุงุฆู…ุฉ ุงู„ุนู„ูˆูŠุฉ (ุงู„ุนุฑุจูŠุฉ/English)
28
+ 2. ูุนู‘ู„ ุงู„ุชุฑุฌู…ุฉ ุงู„ุชู„ู‚ุงุฆูŠุฉ
29
+ 3. ุงุฎุชุฑ ุงู„ู„ุบุฉ ุงู„ู…ุณุชู‡ุฏูุฉ ู„ู„ุชุฑุฌู…ุฉ
30
+
31
+ ### ุงู„ุฎุทูˆุฉ 2: ุงู„ุชุณุฌูŠู„
32
+ 1. ุงุถุบุท ุนู„ู‰ ุชุจูˆูŠุจ "๐ŸŽ™๏ธ Record Audio"
33
+ 2. ุงุถุบุท "Start Recording" ู„ุจุฏุก ุงู„ุชุณุฌูŠู„
34
+ 3. ุงุณุชุฎุฏู… "Mark Important" ู„ูˆุถุน ุนู„ุงู…ุงุช ุนู„ู‰ ุงู„ู†ู‚ุงุท ุงู„ู…ู‡ู…ุฉ
35
+ 4. ุงุถุบุท "Stop" ู„ุฅู†ู‡ุงุก ุงู„ุชุณุฌูŠู„
36
+
37
+ ### ุงู„ุฎุทูˆุฉ 3: ุงู„ู…ุนุงู„ุฌุฉ ูˆุงู„ุชุฑุฌู…ุฉ
38
+ 1. ุงุถุบุท "Extract Text" ู„ุจุฏุก ุงู„ู…ุนุงู„ุฌุฉ
39
+ 2. ุงู†ุชุธุฑ ุญุชู‰ ูŠูƒุชู…ู„ ุงู„ู†ุณุฎ ูˆุงู„ุชุฑุฌู…ุฉ
40
+ 3. ุฑุงุฌุน ุงู„ู†ุต ุงู„ุฃุตู„ูŠ ูˆุงู„ู…ุชุฑุฌู…
41
+
42
+ ### ุงู„ุฎุทูˆุฉ 4: ุงู„ุชุตุฏูŠุฑ
43
+ 1. ุงุญูุธ ุงู„ู†ุชุงุฆุฌ ุฃูˆ ุงู†ุณุฎู‡ุง
44
+ 2. ุงุณุชุฎุฏู… ุงู„ู…ู„ู ุงู„ู…ุญููˆุธ ู„ู„ู…ุฑุงุฌุนุฉ ู„ุงุญู‚ุงู‹
45
+
46
+ ## โŒจ๏ธ ุงุฎุชุตุงุฑุงุช ู„ูˆุญุฉ ุงู„ู…ูุงุชูŠุญ
47
+ - **Space**: ุจุฏุก/ุฅูŠู‚ุงู ุงู„ุชุณุฌูŠู„
48
+ - **M**: ูˆุถุน ุนู„ุงู…ุฉ ู…ู‡ู…ุฉ
49
+ - **P**: ุฅูŠู‚ุงู ู…ุคู‚ุช/ุงุณุชุฆู†ุงู
50
+ - **R**: ุฅุนุงุฏุฉ ุชุณุฌูŠู„
51
+
52
+ ## ๐Ÿ“ฑ ู†ุตุงุฆุญ ู„ู„ุทู„ุงุจ ุงู„ุฃุฌุงู†ุจ
53
+
54
+ ### ู„ู„ุทู„ุงุจ ุงู„ุนุฑุจ:
55
+ - ุงุณุชุฎุฏู… ุงู„ูˆุงุฌู‡ุฉ ุงู„ุนุฑุจูŠุฉ ู„ู„ุณู‡ูˆู„ุฉ
56
+ - ูุนู‘ู„ ุงู„ุชุฑุฌู…ุฉ ู„ู„ุฅู†ุฌู„ูŠุฒูŠุฉ ู„ูู‡ู… ุงู„ู…ุตุทู„ุญุงุช ุงู„ุชู‚ู†ูŠุฉ
57
+ - ุถุน ุนู„ุงู…ุงุช ุนู„ู‰ ุงู„ู…ูุงู‡ูŠู… ุงู„ุฌุฏูŠุฏุฉ
58
+
59
+ ### ู„ู„ุทู„ุงุจ ุงู„ุฏูˆู„ูŠูŠู†:
60
+ - ุงุณุชุฎุฏู… ุงู„ุชุฑุฌู…ุฉ ุฅู„ู‰ ู„ุบุชูƒ ุงู„ุฃู… ู„ู„ูู‡ู… ุงู„ุฃูุถู„
61
+ - ุงุนุชู…ุฏ ุนู„ู‰ ุงู„ุนู„ุงู…ุงุช ุงู„ู…ู‡ู…ุฉ ู„ู„ู…ุฑุงุฌุนุฉ ุงู„ุณุฑูŠุนุฉ
62
+ - ุฑุงุฌุน ุงู„ู†ุต ุงู„ู…ุชุฑุฌู… ูˆุงู„ุฃุตู„ูŠ ู…ุนุงู‹
63
+
64
+ ## ๐Ÿ”ง ุฅุนุฏุงุฏุงุช ู…ุชู‚ุฏู…ุฉ
65
+
66
+ ### ุฌูˆุฏุฉ ุงู„ุชุณุฌูŠู„:
67
+ - **ุนุงู„ูŠุฉ**: ู„ู„ู…ุญุงุถุฑุงุช ุงู„ู…ู‡ู…ุฉ (320 kbps)
68
+ - **ู…ุชูˆุณุทุฉ**: ู„ู„ุงุณุชุฎุฏุงู… ุงู„ุนุงุฏูŠ (192 kbps)
69
+ - **ู…ู†ุฎูุถุฉ**: ู„ุชูˆููŠุฑ ุงู„ู…ุณุงุญุฉ (128 kbps)
70
+
71
+ ### ุฅุนุฏุงุฏุงุช ุงู„ุชุฑุฌู…ุฉ:
72
+ - **Arabic**: ู„ู„ุทู„ุงุจ ุงู„ุนุฑุจ
73
+ - **English**: ู„ู„ู…ุญุชูˆู‰ ุงู„ุฏูˆู„ูŠ
74
+ - **French**: ู„ู„ุทู„ุงุจ ุงู„ูุฑู†ูƒูˆููˆู†ูŠูŠู†
75
+ - **Spanish**: ู„ู„ุทู„ุงุจ ุงู„ู†ุงุทู‚ูŠู† ุจุงู„ุฅุณุจุงู†ูŠุฉ
76
+
77
+ ## ๐Ÿ› ๏ธ ุงุณุชูƒุดุงู ุงู„ุฃุฎุทุงุก
78
+
79
+ ### ู…ุดุงูƒู„ ุงู„ู…ูŠูƒุฑูˆููˆู†:
80
+ 1. ุชุฃูƒุฏ ู…ู† ุฅุนุทุงุก ุฅุฐู† ุงู„ู…ูŠูƒุฑูˆููˆู† ู„ู„ู…ุชุตูุญ
81
+ 2. ุชุญู‚ู‚ ู…ู† ุฅุนุฏุงุฏุงุช ุงู„ุตูˆุช ููŠ ุงู„ู†ุธุงู…
82
+ 3. ุฌุฑุจ ู…ุชุตูุญ ุขุฎุฑ ุฅุฐุง ู„ุฒู… ุงู„ุฃู…ุฑ
83
+
84
+ ### ู…ุดุงูƒู„ ุงู„ุชุฑุฌู…ุฉ:
85
+ 1. ุชุฃูƒุฏ ู…ู† ุงุชุตุงู„ ุงู„ุฅู†ุชุฑู†ุช
86
+ 2. ุชุญู‚ู‚ ู…ู† ุตุญุฉ ู…ูุชุงุญ API
87
+ 3. ุฌุฑุจ ุฅุนุงุฏุฉ ุงู„ู…ุนุงู„ุฌุฉ
88
+
89
+ ### ู…ุดุงูƒู„ ููŠ ุงู„ู†ุณุฎ:
90
+ 1. ุชุฃูƒุฏ ู…ู† ูˆุถูˆุญ ุงู„ุตูˆุช
91
+ 2. ู‚ู„ู„ ุงู„ุถูˆุถุงุก ููŠ ุงู„ุฎู„ููŠุฉ
92
+ 3. ุชุญุฏุซ ุจูˆุถูˆุญ ูˆุจุทุก ู†ุณุจูŠุงู‹
93
+
94
+ ## ๐Ÿ“ž ุงู„ุฏุนู… ุงู„ุชู‚ู†ูŠ
95
+
96
+ ### ุงู„ุญุตูˆู„ ุนู„ู‰ ุงู„ู…ุณุงุนุฏุฉ:
97
+ - ุชุญู‚ู‚ ู…ู† console ุงู„ู…ุชุตูุญ (F12) ู„ู„ุฃุฎุทุงุก
98
+ - ุฑุงุฌุน ู…ู„ูุงุช ุงู„ุณุฌู„ ููŠ ู…ุฌู„ุฏ ุงู„ุชุทุจูŠู‚
99
+ - ุชุฃูƒุฏ ู…ู† ุชุญุฏูŠุซ ุฌู…ูŠุน ุงู„ู…ูƒุชุจุงุช
100
+
101
+ ### ู†ุตุงุฆุญ ู„ู„ุฃุฏุงุก ุงู„ุฃูุถู„:
102
+ - ุงุณุชุฎุฏู… Chrome ุฃูˆ Firefox ู„ู„ุชูˆุงูู‚ ุงู„ุฃูุถู„
103
+ - ุฃุบู„ู‚ ุงู„ุชุทุจูŠู‚ุงุช ุงู„ุฃุฎุฑู‰ ุฃุซู†ุงุก ุงู„ุชุณุฌูŠู„
104
+ - ุชุฃูƒุฏ ู…ู† ู…ุณุงุญุฉ ูƒุงููŠุฉ ุนู„ู‰ ุงู„ู‚ุฑุต ุงู„ุตู„ุจ
105
+
106
+ ## ๐ŸŽ“ ู†ุตุงุฆุญ ุฃูƒุงุฏูŠู…ูŠุฉ
107
+
108
+ ### ู„ู„ู…ุญุงุถุฑุงุช:
109
+ - ุงุฌู„ุณ ููŠ ู…ู‚ุฏู…ุฉ ุงู„ู‚ุงุนุฉ ู„ู„ุตูˆุช ุงู„ุฃูˆุถุญ
110
+ - ุงุณุชุฎุฏู… ุนู„ุงู…ุงุช ุงู„ู…ุญุงุถุฑ ุงู„ู…ู‡ู…ุฉ ูƒุฏู„ูŠู„
111
+ - ุฑุงุฌุน ุงู„ุชุฑุฌู…ุฉ ู…ุน ุฒู…ู„ุงุก ุงู„ุฏุฑุงุณุฉ
112
+
113
+ ### ู„ู„ู…ุฐุงูƒุฑุฉ:
114
+ - ุงุณุชุฎุฏู… ุงู„ู†ุต ุงู„ู…ุชุฑุฌู… ู„ู„ู…ุฑุงุฌุนุฉ ุงู„ุณุฑูŠุนุฉ
115
+ - ุงุจุญุซ ุนู† ุงู„ู…ูุงู‡ูŠู… ุงู„ู…ุชุฑุฌู…ุฉ ููŠ ู…ุตุงุฏุฑ ุฅุถุงููŠุฉ
116
+ - ุงุฑุจุท ุงู„ู†ุต ุงู„ุฃุตู„ูŠ ุจุงู„ุชุฑุฌู…ุฉ ู„ุชุญุณูŠู† ุงู„ู„ุบุฉ
117
+
118
+ ## ๐Ÿ”ฎ ู…ูŠุฒุงุช ู‚ุงุฏู…ุฉ
119
+
120
+ ### ุงู„ุชุญุฏูŠุซุงุช ุงู„ู…ุฎุทุทุฉ:
121
+ - **ุชุญู„ูŠู„ ุงู„ู…ุญุชูˆู‰**: ุงุณุชุฎุฑุงุฌ ุงู„ู†ู‚ุงุท ุงู„ุฑุฆูŠุณูŠุฉ ุชู„ู‚ุงุฆูŠุงู‹
122
+ - **ุจุทุงู‚ุงุช ุงู„ู…ุฑุงุฌุนุฉ**: ุฅู†ุดุงุก ุจุทุงู‚ุงุช ุฏุฑุงุณุฉ ู…ู† ุงู„ู…ุญุงุถุฑุงุช
123
+ - **ุงู„ุชูƒุงู…ู„ ู…ุน ุงู„ู…ู†ุตุงุช**: ุฑุจุท ู…ุน Moodle ูˆCanvas
124
+ - **ุงู„ู…ุดุงุฑูƒุฉ ุงู„ุชุนุงูˆู†ูŠุฉ**: ู…ุดุงุฑูƒุฉ ุงู„ู…ุญุงุถุฑุงุช ู…ุน ุงู„ุฒู…ู„ุงุก
125
+
126
+ ---
127
+
128
+ ## ๐Ÿ“„ ุฅุฎู„ุงุก ุงู„ู…ุณุคูˆู„ูŠุฉ
129
+
130
+ ู‡ุฐุง ุงู„ุชุทุจูŠู‚ ู…ุฎุตุต ู„ู„ุงุณุชุฎุฏุงู… ุงู„ุชุนู„ูŠู…ูŠ. ุชุฃูƒุฏ ู…ู† ุงู„ุญุตูˆู„ ุนู„ู‰ ุฅุฐู† ุงู„ู…ุญุงุถุฑ ู‚ุจู„ ุชุณุฌูŠู„ ุงู„ู…ุญุงุถุฑุงุช. ุงู„ู†ุณุฎ ูˆุงู„ุชุฑุฌู…ุฉ ู‚ุฏ ูŠุญุชูˆูŠุงู† ุนู„ู‰ ุฃุฎุทุงุกุŒ ู„ุฐุง ุฑุงุฌุนู‡ู…ุง ุฏุงุฆู…ุงู‹.
131
+
132
+ ---
133
+
134
+ **ู†ุชู…ู†ู‰ ู„ูƒ ุชุฌุฑุจุฉ ุชุนู„ูŠู…ูŠุฉ ู…ู…ุชุงุฒุฉ ู…ุน SyncMaster! ๐ŸŽ“โœจ**
SOLUTION_SUMMARY.md ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐ŸŽ‰ ุชู… ุญู„ ุงู„ู…ุดูƒู„ุฉ ุจู†ุฌุงุญ!
2
+
3
+ ## โœ… ู…ู„ุฎุต ุงู„ุญู„
4
+
5
+ ุชู… ุญู„ ู…ุดูƒู„ุฉ "system offline" ููŠ ู…ูŠุฒุฉ Lecture Recorder ุจู†ุฌุงุญ. ุงู„ุขู† ูŠู…ูƒู†ูƒ ุชุดุบูŠู„ ุงู„ุชุทุจูŠู‚ ุจุฃู…ุฑ ูˆุงุญุฏ ูู‚ุท:
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ ## ๐Ÿ”ง ุงู„ุชุบูŠูŠุฑุงุช ุงู„ุชูŠ ุชู…ุช
12
+
13
+ ### 1. ู…ู„ูุงุช ุฌุฏูŠุฏุฉ ุชู… ุฅู†ุดุงุคู‡ุง:
14
+ - `integrated_server.py` - ุฎุงุฏู… ู…ุชูƒุงู…ู„ ู„ู„ุชุณุฌูŠู„
15
+ - `main.py` - ู†ู‚ุทุฉ ุฏุฎูˆู„ ุจุณูŠุทุฉ ูˆู…ุฏู…ุฌุฉ
16
+ - `app_config.py` - ุฅุนุฏุงุฏุงุช ุงู„ุชุทุจูŠู‚
17
+ - `startup.py` - ู…ูุดุบู„ ู…ุชู‚ุฏู… ู„ู„ุชุทูˆูŠุฑ
18
+
19
+ ### 2. ู…ู„ูุงุช ุชู… ุชุนุฏูŠู„ู‡ุง:
20
+ - `app.py` - ุฅุถุงูุฉ ุงุณุชูŠุฑุงุฏ ุงู„ุฎุงุฏู… ุงู„ู…ุฏู…ุฌ
21
+ - `package.json` - ุชุญุฏูŠุซ ุฃูˆุงู…ุฑ ุงู„ุชุดุบูŠู„
22
+
23
+ ## ๐Ÿš€ ูƒูŠููŠุฉ ุงู„ุงุณุชุฎุฏุงู…
24
+
25
+ ### ู„ู„ุงุณุชุฎุฏุงู… ุงู„ุนุงุฏูŠ:
26
+ ```bash
27
+ npm run dev
28
+ ```
29
+
30
+ ### ู„ู„ู†ุดุฑ ุนู„ู‰ HuggingFace:
31
+ ```bash
32
+ npm start
33
+ ```
34
+
35
+ ### ู„ู„ุชุทูˆูŠุฑ ุงู„ู…ุชู‚ุฏู… (ุฎูˆุงุฏู… ู…ู†ูุตู„ุฉ):
36
+ ```bash
37
+ npm run dev-separate
38
+ ```
39
+
40
+ ## โœจ ุงู„ู…ู…ูŠุฒุงุช ุงู„ุฌุฏูŠุฏุฉ
41
+
42
+ 1. **๐ŸŽฏ ุชุดุบูŠู„ ู…ูˆุญุฏ**: ุฃู…ุฑ ูˆุงุญุฏ ูู‚ุท ู„ุชุดุบูŠู„ ูƒู„ ุดูŠุก
43
+ 2. **โ˜๏ธ ุฌุงู‡ุฒ ู„ู„ุณุญุงุจุฉ**: ูŠุนู…ู„ ุชู„ู‚ุงุฆูŠุงู‹ ุนู„ู‰ HuggingFace ูˆ Railway
44
+ 3. **๐Ÿ”ง ุชูƒูˆูŠู† ุฐูƒูŠ**: ูŠูƒุชุดู ุงู„ุจูŠุฆุฉ ูˆูŠุชูƒูŠู ู…ุนู‡ุง
45
+ 4. **๐Ÿ›ก๏ธ ู…ุนุงู„ุฌุฉ ุฃุฎุทุงุก ู…ุญุณู†ุฉ**: ุชุดุบูŠู„ ุงุญุชูŠุงุทูŠ ููŠ ุญุงู„ุฉ ูุดู„ ุงู„ุทุฑูŠู‚ุฉ ุงู„ุฃูˆู„ู‰
46
+ 5. **๐Ÿ“Š ู…ุฑุงู‚ุจุฉ ุงู„ุญุงู„ุฉ**: ูุญุต ุชู„ู‚ุงุฆูŠ ู„ุญุงู„ุฉ ุงู„ุฎูˆุงุฏู…
47
+
48
+ ## ๐Ÿงช ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู…
49
+
50
+ ุชู… ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู… ูˆุฃุธู‡ุฑ ุงู„ู†ุชุงุฆุฌ ุงู„ุชุงู„ูŠุฉ:
51
+ - โœ… ุฎุงุฏู… ุงู„ุชุณุฌูŠู„ ูŠุจุฏุฃ ุชู„ู‚ุงุฆูŠุงู‹
52
+ - โœ… Streamlit ูŠุนู…ู„ ุนู„ู‰ ุงู„ู…ู†ูุฐ 5050
53
+ - โœ… ุฎุงุฏู… ุงู„ุชุณุฌูŠู„ ูŠุนู…ู„ ุนู„ู‰ ุงู„ู…ู†ูุฐ 5001
54
+ - โœ… ุงู„ุชูƒุงู…ู„ ุจูŠู† ุงู„ุฎูˆุงุฏู… ูŠุนู…ู„ ุจู†ุฌุงุญ
55
+
56
+ ## ๐ŸŽŠ ุงู„ู†ุชูŠุฌุฉ ุงู„ู†ู‡ุงุฆูŠุฉ
57
+
58
+ **ุงู„ู…ุดูƒู„ุฉ ู…ุญู„ูˆู„ุฉ ุชู…ุงู…ุงู‹!**
59
+
60
+ ู„ู† ุชุญุชุงุฌ ุจุนุฏ ุงู„ุขู† ุฅู„ู‰:
61
+ - โŒ ุชุดุบูŠู„ `python recorder_server.py` ู…ู†ูุตู„ุงู‹
62
+ - โŒ ุงู„ู‚ู„ู‚ ุจุดุฃู† "system offline"
63
+ - โŒ ุชุดุบูŠู„ ุฃูˆุงู…ุฑ ู…ุชุนุฏุฏุฉ
64
+
65
+ ูู‚ุท ุงุณุชุฎุฏู… `npm run dev` ูˆุณูŠุนู…ู„ ูƒู„ ุดูŠุก ุชู„ู‚ุงุฆูŠุงู‹! ๐Ÿš€
66
+
67
+ ---
68
+
69
+ **ุฌุงู‡ุฒ ู„ู„ู†ุดุฑ ุนู„ู‰ HuggingFace ุงู„ุขู†!** ๐ŸŒŸ
SUMMARY_FIX_REPORT.md ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ุญู„ ู…ุดูƒู„ุฉ ุฒุฑ ุงู„ุชู„ุฎูŠุต - ุชู‚ุฑูŠุฑ ุงู„ุฅุตู„ุงุญ ุงู„ู†ู‡ุงุฆูŠ ๐ŸŽ‰
2
+
3
+ ## ๐Ÿ“‹ ู…ู„ุฎุต ุงู„ู…ุดูƒู„ุฉ
4
+ ูƒุงู† ุฒุฑ "Generate Smart Lecture Summary" ู„ุง ูŠุนู…ู„ ููŠ ุชู„ุฎูŠุต ุงู„ู†ุต ุงู„ู…ุณุชุฎุฑุฌ ู…ู† ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ ุจุนุฏ ุฌู„ุจู‡ ู…ู† ุงู„ุตูˆุชุŒ ู…ุน ุธู‡ูˆุฑ ุฎุทุฃ CORS:
5
+
6
+ ```
7
+ Access to fetch at 'http://localhost:5001/summarize' from origin 'http://localhost:5054' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
8
+ ```
9
+
10
+ ## ๐Ÿ” ุงู„ุชุดุฎูŠุต ุงู„ู…ู†ุฌุฒ
11
+ ุชู… ุฅู†ุดุงุก ู†ุธุงู… ุชุดุฎูŠุต ุดุงู…ู„ ูƒุดู ุนู†:
12
+
13
+ ### 1. ู…ุดูƒู„ุฉ CORS ุงู„ุฑุฆูŠุณูŠุฉ โŒ
14
+ - **ุงู„ู…ุดูƒู„ุฉ**: ุงู„ุฎุงุฏู… ูŠุฑุณู„ `'*, *'` ุจุฏู„ุงู‹ ู…ู† `'*'`
15
+ - **ุงู„ุณุจุจ**: ุชูƒุฑุงุฑ ุฅุนุฏุงุฏุงุช CORS - ู…ุฑุฉ ู…ู† `flask-cors` ูˆู…ุฑุฉ ูŠุฏูˆูŠุงู‹ ููŠ ูƒู„ endpoint
16
+ - **ุงู„ู†ุชูŠุฌุฉ**: ุชูƒุฑุงุฑ header `Access-Control-Allow-Origin`
17
+
18
+ ### 2. ู…ูƒุชุจุฉ ู…ูู‚ูˆุฏุฉ โŒ
19
+ - **ุงู„ู…ุดูƒู„ุฉ**: `google-generativeai` ุบูŠุฑ ู…ุซุจุชุฉ
20
+ - **ุงู„ุชุฃุซูŠุฑ**: ูุดู„ ููŠ ูˆุธูŠูุฉ ุงู„ุชู„ุฎูŠุต
21
+
22
+ ## โœ… ุงู„ุญู„ูˆู„ ุงู„ู…ุทุจู‚ุฉ
23
+
24
+ ### 1. ุฅุตู„ุงุญ ู…ุดูƒู„ุฉ CORS
25
+ #### ุฃ. ุชุจุณูŠุท ุฅุนุฏุงุฏ CORS ููŠ `recorder_server.py`:
26
+ ```python
27
+ # ู‚ุจู„ ุงู„ุฅุตู„ุงุญ - ุฅุนุฏุงุฏ ู…ุนู‚ุฏ
28
+ CORS(app, resources={
29
+ r"/record": {"origins": "*"},
30
+ r"/translate": {"origins": "*"},
31
+ r"/languages": {"origins": "*"},
32
+ r"/ui-translations/*": {"origins": "*"},
33
+ r"/notes": {"origins": "*"},
34
+ r"/notes/*": {"origins": "*"},
35
+ r"/summarize": {"origins": "*"}
36
+ })
37
+
38
+ # ุจุนุฏ ุงู„ุฅุตู„ุงุญ - ุฅุนุฏุงุฏ ู…ุจุณุท ูˆุตุญูŠุญ
39
+ CORS(app,
40
+ origins="*",
41
+ methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
42
+ allow_headers=['Content-Type', 'Authorization']
43
+ )
44
+ ```
45
+
46
+ #### ุจ. ุฅุฒุงู„ุฉ ุงู„ุฅุนุฏุงุฏุงุช ุงู„ูŠุฏูˆูŠุฉ ุงู„ู…ูƒุฑุฑุฉ:
47
+ ```python
48
+ # ู‚ุจู„ ุงู„ุฅุตู„ุงุญ - ุฅุนุฏุงุฏ ูŠุฏูˆูŠ ู…ูƒุฑุฑ
49
+ if request.method == 'OPTIONS':
50
+ headers = {
51
+ 'Access-Control-Allow-Origin': '*',
52
+ 'Access-Control-Allow-Methods': 'POST, OPTIONS',
53
+ 'Access-Control-Allow-Headers': 'Content-Type',
54
+ }
55
+ return ('', 204, headers)
56
+
57
+ # ุจุนุฏ ุงู„ุฅุตู„ุงุญ - ุชุจุณูŠุท
58
+ if request.method == 'OPTIONS':
59
+ return '', 204 # flask-cors ุณุชุชูˆู„ู‰ ุงู„ุฃู…ุฑ
60
+ ```
61
+
62
+ ### 2. ุชุซุจูŠุช ุงู„ู…ูƒุชุจุงุช ุงู„ู…ูู‚ูˆุฏุฉ
63
+ ```bash
64
+ pip install google-generativeai
65
+ ```
66
+
67
+ ### 3. ุชุญุณูŠู† ู…ุนุงู„ุฌุฉ ุงู„ุฃุฎุทุงุก ููŠ JavaScript
68
+ ุชู… ุชุญุฏูŠุซ ุฏุงู„ุฉ `generateSummary()` ููŠ `templates/recorder.html`:
69
+ - ุฑุณุงุฆู„ ุฎุทุฃ ู…ุญุฏุฏุฉ ุจุงู„ู„ุบุฉ ุงู„ุนุฑุจูŠุฉ
70
+ - ู…ุนุงู„ุฌุฉ ุฃูุถู„ ู„ุชู†ุณูŠู‚ุงุช ุงู„ุงุณุชุฌุงุจุฉ ุงู„ู…ุฎุชู„ูุฉ
71
+ - ุชุดุฎูŠุต ุฃูˆุถุญ ู„ู„ู…ุดุงูƒู„
72
+
73
+ ### 4. ุชุญุณูŠู† ุฏุงู„ุฉ ุนุฑุถ ุงู„ู†ุชุงุฆุฌ
74
+ ุชู… ุชุญุฏูŠุซ `displaySummaryResults()` ู„ู„ุชุนุงู…ู„ ู…ุน:
75
+ - ุชู†ุณูŠู‚ุงุช ู…ุฎุชู„ูุฉ ู„ู„ุงุณุชุฌุงุจุฉ (ู†ุต ุฃูˆ ูƒุงุฆู†)
76
+ - ุนุฑุถ ู…ุญุชูˆู‰ ุงุญุชูŠุงุทูŠ ููŠ ุญุงู„ุฉ ุนุฏู… ูˆุฌูˆุฏ ุงู„ู…ุญุชูˆู‰ ุงู„ู…ุชูˆู‚ุน
77
+
78
+ ## ๐Ÿงช ุฃุฏูˆุงุช ุงู„ุชุดุฎูŠุต ุงู„ู…ูู†ุดุฃุฉ
79
+
80
+ ### 1. `diagnose_summary.py`
81
+ ู†ุธุงู… ุชุดุฎูŠุต ุดุงู…ู„ ูŠูุญุต:
82
+ - ุญุงู„ุฉ ุงู„ุนู…ู„ูŠุงุช ูˆุงู„ู…ู†ุงูุฐ
83
+ - ุฅุนุฏุงุฏุงุช CORS
84
+ - ูˆุธูŠูุฉ ุงู„ุชู„ุฎูŠุต
85
+ - ุงู„ู…ูƒุชุจุงุช ุงู„ู…ุทู„ูˆุจุฉ
86
+
87
+ ### 2. `test_summary_button.py`
88
+ ุงุฎุชุจุงุฑ ู…ุจุณุท ูˆู…ุจุงุดุฑ ู„ุฒุฑ ุงู„ุชู„ุฎูŠุต
89
+
90
+ ### 3. `test_summarize.py`
91
+ ุงุฎุชุจุงุฑ ุฃุณุงุณูŠ ู„ู€ endpoint ุงู„ุชู„ุฎูŠุต
92
+
93
+ ## ๐Ÿ“Š ู†ุชุงุฆุฌ ุงู„ุงุฎุชุจุงุฑ ุงู„ู†ู‡ุงุฆูŠุฉ โœ…
94
+
95
+ ```
96
+ ๐ŸŽ‰ ุฌู…ูŠุน ุงู„ุงุฎุชุจุงุฑุงุช ู†ุฌุญุช!
97
+ โœ… ุฒุฑ ุงู„ุชู„ุฎูŠุต ูŠุนู…ู„ ุจุดูƒู„ ุตุญูŠุญ
98
+
99
+ ๐Ÿ“Š ู…ู„ุฎุต ุงู„ุชุดุฎูŠุต:
100
+ ๐Ÿ“ฆ ุงู„ู…ูƒุชุจุงุช: โœ… ู…ูˆุฌูˆุฏุฉ
101
+ ๐Ÿ”ง ุนู…ู„ูŠุฉ Python: โœ… ุชุนู…ู„
102
+ ๐ŸŒ ุงู„ู…ู†ูุฐ 5001: โœ… ู…ูุชูˆุญ
103
+ ๐Ÿ”ง CORS: โœ… ุตุญูŠุญ
104
+ ๐Ÿค– ุงู„ุชู„ุฎูŠุต: โœ… ูŠุนู…ู„
105
+ ```
106
+
107
+ ## ๐Ÿ”ง ุงู„ู…ู„ูุงุช ุงู„ู…ูุนุฏู‘ู„ุฉ
108
+
109
+ ### 1. `recorder_server.py`
110
+ - ุฅุตู„ุงุญ ุฅุนุฏุงุฏุงุช CORS
111
+ - ุฅุฒุงู„ุฉ ุงู„ุชูƒุฑุงุฑ ููŠ headers
112
+ - ุชุจุณูŠุท ู…ุนุงู„ุฌุฉ OPTIONS requests
113
+
114
+ ### 2. `templates/recorder.html`
115
+ - ุชุญุณูŠู† ุฏุงู„ุฉ `generateSummary()`
116
+ - ุชุญุณูŠู† ุฏุงู„ุฉ `displaySummaryResults()`
117
+ - ุฑุณุงุฆู„ ุฎุทุฃ ุฃูˆุถุญ
118
+
119
+ ### 3. ู…ู„ูุงุช ุงู„ุชุดุฎูŠุต ุงู„ุฌุฏูŠุฏุฉ
120
+ - `diagnose_summary.py`
121
+ - `test_summary_button.py`
122
+ - `test_summarize.py`
123
+
124
+ ## ๐Ÿš€ ูƒูŠููŠุฉ ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุญู„
125
+
126
+ ### 1. ุชุดุบูŠู„ ุงู„ุฎุงุฏู…:
127
+ ```bash
128
+ python recorder_server.py
129
+ ```
130
+
131
+ ### 2. ุชุดุบูŠู„ ุงู„ุชุดุฎูŠุต:
132
+ ```bash
133
+ python diagnose_summary.py
134
+ ```
135
+
136
+ ### 3. ุงุฎุชุจุงุฑ ุฒุฑ ุงู„ุชู„ุฎูŠุต:
137
+ ```bash
138
+ python test_summary_button.py
139
+ ```
140
+
141
+ ### 4. ุงุฎุชุจุงุฑ ู…ู† ุงู„ูˆุงุฌู‡ุฉ:
142
+ 1. ุงูุชุญ `http://localhost:5054`
143
+ 2. ุณุฌู„ ุตูˆุช ุฃูˆ ุงุฏุฎู„ ู†ุต
144
+ 3. ุงุถุบุท ุฒุฑ "๐Ÿค– Generate Smart Lecture Summary"
145
+ 4. ุชุฃูƒุฏ ู…ู† ุธู‡ูˆุฑ ุงู„ู…ู„ุฎุต
146
+
147
+ ## ๐Ÿ’ก ู†ุตุงุฆุญ ู„ู„ู…ุณุชู‚ุจู„
148
+
149
+ ### 1. ุชุฌู†ุจ ุชูƒุฑุงุฑ CORS
150
+ - ุงุณุชุฎุฏู… ุฅุนุฏุงุฏ CORS ูˆุงุญุฏ ูู‚ุท
151
+ - ู„ุง ุชุถุน ุฅุนุฏุงุฏุงุช ูŠุฏูˆูŠุฉ ุฅุถุงููŠุฉ
152
+
153
+ ### 2. ู…ุฑุงู‚ุจุฉ ุงู„ุชุจุนูŠุงุช
154
+ - ุชุฃูƒุฏ ู…ู† ุชุซุจูŠุช ุฌู…ูŠุน ุงู„ู…ูƒุชุจุงุช ุงู„ู…ุทู„ูˆุจุฉ
155
+ - ุงุณุชุฎุฏู… `requirements.txt` ู…ุญุฏุซ
156
+
157
+ ### 3. ุงุณุชุฎุฏุงู… ุฃุฏูˆุงุช ุงู„ุชุดุฎูŠุต
158
+ - ุดุบู„ `diagnose_summary.py` ุนู†ุฏ ู…ูˆุงุฌู‡ุฉ ู…ุดุงูƒู„
159
+ - ูŠูˆูุฑ ุชุดุฎูŠุต ุณุฑูŠุน ูˆุดุงู…ู„
160
+
161
+ ## ๐ŸŽฏ ุงู„ุฎู„ุงุตุฉ
162
+
163
+ ุชู… ุญู„ ู…ุดูƒู„ุฉ ุฒุฑ ุงู„ุชู„ุฎูŠุต ุจู†ุฌุงุญ ู…ู† ุฎู„ุงู„:
164
+ 1. โœ… ุฅุตู„ุงุญ ู…ุดูƒู„ุฉ CORS ุงู„ู…ุฒุฏูˆุฌุฉ
165
+ 2. โœ… ุชุซุจูŠุช ุงู„ู…ูƒุชุจุงุช ุงู„ู…ูู‚ูˆุฏุฉ
166
+ 3. โœ… ุชุญุณูŠู† ู…ุนุงู„ุฌุฉ ุงู„ุฃุฎุทุงุก
167
+ 4. โœ… ุฅู†ุดุงุก ุฃุฏูˆุงุช ุชุดุฎูŠุต ุดุงู…ู„ุฉ
168
+
169
+ **ุงู„ู†ุชูŠุฌุฉ: ุฒุฑ ุงู„ุชู„ุฎูŠุต ูŠุนู…ู„ ุจุดูƒู„ ู…ุซุงู„ูŠ ุงู„ุขู†! ๐ŸŽ‰**
TECHNICAL_IMPLEMENTATION.md ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SyncMaster Enhanced - Technical Implementation Summary
2
+
3
+ ## ๐ŸŽฏ Summary of Enhancements
4
+
5
+ This document outlines the comprehensive improvements made to SyncMaster to support AI-powered translation for international students.
6
+
7
+ ## ๐Ÿ”ง New Components Added
8
+
9
+ ### 1. `translator.py` - AI Translation Engine
10
+ ```python
11
+ class AITranslator:
12
+ - translate_text(text, target_language='ar', source_language='auto')
13
+ - detect_language(text)
14
+ - translate_ui_elements(ui_dict, target_language='ar')
15
+ - batch_translate(texts, target_language='ar')
16
+ ```
17
+
18
+ **Features:**
19
+ - Gemini AI-powered translation
20
+ - Academic content optimization
21
+ - Multi-language support (Arabic, English, French, Spanish)
22
+ - Batch processing capabilities
23
+ - Context-aware translation
24
+
25
+ ### 2. Enhanced `audio_processor.py`
26
+ ```python
27
+ class AudioProcessor:
28
+ - get_word_timestamps_with_translation(audio_file_path, target_language='ar')
29
+ - batch_translate_transcription(audio_file_path, target_languages)
30
+ - _create_translated_timestamps(original_timestamps, original_text, translated_text)
31
+ ```
32
+
33
+ **New Features:**
34
+ - Integrated translation with transcription
35
+ - Proportional timestamp mapping for translated text
36
+ - Multi-language processing
37
+ - Enhanced error handling and logging
38
+
39
+ ### 3. Updated `recorder_server.py`
40
+ ```python
41
+ @app.route('/record', methods=['POST'])
42
+ def record():
43
+ # Enhanced with translation parameters:
44
+ # - target_language
45
+ # - enable_translation
46
+ # - comprehensive response with both original and translated text
47
+
48
+ @app.route('/translate', methods=['POST'])
49
+ def translate_text():
50
+ # Standalone translation endpoint
51
+
52
+ @app.route('/languages', methods=['GET'])
53
+ def get_supported_languages():
54
+ # Get list of supported languages
55
+
56
+ @app.route('/ui-translations/<language>', methods=['GET'])
57
+ def get_ui_translations(language):
58
+ # Get UI translations for specific language
59
+ ```
60
+
61
+ ### 4. Enhanced `templates/recorder.html`
62
+ **New Features:**
63
+ - Multi-language interface (English/Arabic)
64
+ - RTL support for Arabic
65
+ - Translation toggle controls
66
+ - Target language selection
67
+ - Enhanced visual design
68
+ - Keyboard shortcuts
69
+ - Accessibility improvements
70
+
71
+ **UI Improvements:**
72
+ - Modern gradient design
73
+ - Responsive layout for mobile devices
74
+ - Real-time language switching
75
+ - Visual feedback for translation status
76
+ - Better error messaging
77
+
78
+ ### 5. Updated `app.py` - Main Application
79
+ **Enhancements:**
80
+ - Language selection in sidebar
81
+ - Translation settings integration
82
+ - Enhanced processing workflow
83
+ - Bilingual interface support
84
+ - Improved user experience flow
85
+
86
+ ## ๐ŸŒ Multi-Language Support Implementation
87
+
88
+ ### UI Translation System
89
+ ```python
90
+ UI_TRANSLATIONS = {
91
+ 'en': { /* English translations */ },
92
+ 'ar': { /* Arabic translations */ }
93
+ }
94
+ ```
95
+
96
+ ### Dynamic Language Switching
97
+ - Client-side language detection
98
+ - Server-side translation API
99
+ - Real-time UI updates
100
+ - RTL text direction support
101
+
102
+ ### Translation Workflow
103
+ 1. **Audio Recording** โ†’ Record with language preferences
104
+ 2. **Transcription** โ†’ AI-powered speech-to-text
105
+ 3. **Language Detection** โ†’ Automatic source language identification
106
+ 4. **Translation** โ†’ Context-aware AI translation
107
+ 5. **Presentation** โ†’ Side-by-side original and translated text
108
+
109
+ ## ๐Ÿš€ API Enhancements
110
+
111
+ ### Recording Endpoint (`/record`)
112
+ **Request Parameters:**
113
+ ```json
114
+ {
115
+ "audio_data": "binary_audio_file",
116
+ "markers": "[timestamp_array]",
117
+ "target_language": "ar|en|fr|es",
118
+ "enable_translation": "true|false"
119
+ }
120
+ ```
121
+
122
+ **Response Format:**
123
+ ```json
124
+ {
125
+ "success": true,
126
+ "original_text": "Original transcription",
127
+ "translated_text": "Translated text",
128
+ "file_path": "path/to/saved/file.json",
129
+ "markers": [timestamps],
130
+ "target_language": "ar",
131
+ "translation_enabled": true,
132
+ "translation_success": true,
133
+ "language_detected": "en"
134
+ }
135
+ ```
136
+
137
+ ### Translation Endpoint (`/translate`)
138
+ **Request:**
139
+ ```json
140
+ {
141
+ "text": "Text to translate",
142
+ "target_language": "ar",
143
+ "source_language": "auto"
144
+ }
145
+ ```
146
+
147
+ **Response:**
148
+ ```json
149
+ {
150
+ "success": true,
151
+ "original_text": "Original text",
152
+ "translated_text": "ุงู„ู†ุต ุงู„ู…ุชุฑุฌู…",
153
+ "source_language": "en",
154
+ "target_language": "ar"
155
+ }
156
+ ```
157
+
158
+ ## ๐Ÿ“ฑ Frontend Enhancements
159
+
160
+ ### JavaScript Features
161
+ ```javascript
162
+ // Language Management
163
+ async function loadTranslations(language)
164
+ function applyTranslations()
165
+ function changeLanguage()
166
+
167
+ // Enhanced Recording
168
+ function displayResults(result)
169
+ function displayMarkers(markers)
170
+ function showMessage(message, type)
171
+
172
+ // Keyboard Shortcuts
173
+ document.addEventListener('keydown', handleKeyboardShortcuts)
174
+ ```
175
+
176
+ ### CSS Improvements
177
+ ```css
178
+ /* RTL Support */
179
+ html[dir="rtl"] { direction: rtl; }
180
+
181
+ /* Modern Design */
182
+ :root {
183
+ --primary-color: #4A90E2;
184
+ --success-color: #50C878;
185
+ /* ... more color variables */
186
+ }
187
+
188
+ /* Responsive Design */
189
+ @media (max-width: 768px) {
190
+ /* Mobile optimizations */
191
+ }
192
+ ```
193
+
194
+ ## ๐Ÿ”’ Security & Performance
195
+
196
+ ### Security Measures
197
+ - Input validation for all API endpoints
198
+ - CORS configuration for cross-origin requests
199
+ - Secure file handling with temporary files
200
+ - API key protection in environment variables
201
+
202
+ ### Performance Optimizations
203
+ - Parallel processing for audio and translation
204
+ - Efficient memory management
205
+ - Chunked audio processing
206
+ - Client-side caching for translations
207
+
208
+ ## ๐Ÿ“Š File Structure Changes
209
+
210
+ ```
211
+ SyncMaster - Copy (2)/
212
+ โ”œโ”€โ”€ translator.py # NEW: AI Translation engine
213
+ โ”œโ”€โ”€ audio_processor.py # ENHANCED: With translation support
214
+ โ”œโ”€โ”€ recorder_server.py # ENHANCED: Additional endpoints
215
+ โ”œโ”€โ”€ app.py # ENHANCED: Multi-language support
216
+ โ”œโ”€โ”€ templates/
217
+ โ”‚ โ””โ”€โ”€ recorder.html # ENHANCED: Multi-language UI
218
+ โ”œโ”€โ”€ README_AR.md # NEW: Arabic documentation
219
+ โ”œโ”€โ”€ setup_enhanced.py # NEW: Enhanced setup script
220
+ โ”œโ”€โ”€ start_enhanced.bat # NEW: Quick start script
221
+ โ”œโ”€โ”€ requirements.txt # UPDATED: Additional dependencies
222
+ โ””โ”€โ”€ .env # UPDATED: Additional configuration
223
+ ```
224
+
225
+ ## ๐ŸŽ“ Educational Features
226
+
227
+ ### For International Students
228
+ 1. **Language Barrier Reduction**: Real-time translation of lectures
229
+ 2. **Better Comprehension**: Side-by-side original and translated text
230
+ 3. **Cultural Adaptation**: Interface in native language
231
+ 4. **Academic Context**: Specialized translation for educational content
232
+
233
+ ### For Arabic Students
234
+ 1. **Native Interface**: Complete Arabic UI
235
+ 2. **Technical Term Translation**: English technical terms with Arabic explanations
236
+ 3. **Reading Direction**: Proper RTL text display
237
+ 4. **Cultural Context**: Academic content adapted for Arabic speakers
238
+
239
+ ## ๐Ÿ”ง Installation & Setup
240
+
241
+ ### Enhanced Setup Process
242
+ 1. **Automated Installation**: `python setup_enhanced.py`
243
+ 2. **Dependency Management**: Automatic package installation
244
+ 3. **Configuration Validation**: Environment file checking
245
+ 4. **Service Management**: Automatic server startup
246
+
247
+ ### Quick Start Options
248
+ - **Windows**: `start_enhanced.bat`
249
+ - **Cross-platform**: `python setup_enhanced.py`
250
+ - **Manual**: Individual component startup
251
+
252
+ ## ๐Ÿ“ˆ Testing & Quality Assurance
253
+
254
+ ### Translation Quality
255
+ - Academic content optimization
256
+ - Technical term preservation
257
+ - Context-aware translation
258
+ - Fallback mechanisms
259
+
260
+ ### User Experience Testing
261
+ - Multi-language interface testing
262
+ - Mobile responsiveness
263
+ - Accessibility compliance
264
+ - Performance optimization
265
+
266
+ ## ๐Ÿ”ฎ Future Enhancements
267
+
268
+ ### Planned Features
269
+ 1. **Advanced Translation**: Subject-specific terminology
270
+ 2. **Collaboration Tools**: Shared study sessions
271
+ 3. **Learning Analytics**: Progress tracking
272
+ 4. **Platform Integration**: LMS connectivity
273
+ 5. **Offline Support**: Local processing capabilities
274
+
275
+ ### Technical Roadmap
276
+ 1. **Model Optimization**: Faster processing
277
+ 2. **Caching System**: Reduced API calls
278
+ 3. **Advanced UI**: More interactive features
279
+ 4. **Mobile App**: Native mobile application
280
+
281
+ ---
282
+
283
+ ## ๐Ÿ“ž Technical Support
284
+
285
+ ### Debugging Features
286
+ - Comprehensive logging system
287
+ - Browser console integration
288
+ - Error message localization
289
+ - Performance monitoring
290
+
291
+ ### Troubleshooting Resources
292
+ - Detailed error messages
293
+ - Multi-language support documentation
294
+ - Community forum integration
295
+ - Technical FAQ
296
+
297
+ ---
298
+
299
+ **This enhanced version of SyncMaster represents a significant advancement in making educational technology accessible to international students worldwide.**
TROUBLESHOOTING.md ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๐Ÿ› ๏ธ ุฏู„ูŠู„ ุงุณุชูƒุดุงู ุงู„ุฃุฎุทุงุก - SyncMaster Enhanced
2
+ # Troubleshooting Guide - SyncMaster Enhanced
3
+
4
+ ## ๐Ÿ” ุงู„ุฃุฎุทุงุก ุงู„ุดุงุฆุนุฉ ูˆุญู„ูˆู„ู‡ุง / Common Errors and Solutions
5
+
6
+ ### 1. ุฎุทุฃ ุงู„ุงุชุตุงู„ ุจุงู„ุฎุงุฏู… / Server Connection Error
7
+ ```
8
+ Error: Failed to fetch
9
+ POST http://localhost:5001/record net::ERR_CONNECTION_REFUSED
10
+ ```
11
+
12
+ **ุงู„ุฃุณุจุงุจ ุงู„ู…ุญุชู…ู„ุฉ / Possible Causes:**
13
+ - ุงู„ุฎุงุฏู… ุบูŠุฑ ูŠุนู…ู„ / Server not running
14
+ - ู…ู†ูุฐ 5001 ู…ุณุชุฎุฏู… ู…ู† ุจุฑู†ุงู…ุฌ ุขุฎุฑ / Port 5001 used by another application
15
+ - ุฌุฏุงุฑ ุญู…ุงูŠุฉ ูŠุญุฌุจ ุงู„ุงุชุตุงู„ / Firewall blocking connection
16
+
17
+ **ุงู„ุญู„ูˆู„ / Solutions:**
18
+
19
+ #### ุฃ) ุชุดุบูŠู„ ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู… / Run System Test:
20
+ ```bash
21
+ python test_system.py
22
+ ```
23
+
24
+ #### ุจ) ุชุดุบูŠู„ ุงู„ุฎุงุฏู… ูŠุฏูˆูŠุงู‹ / Start Server Manually:
25
+ ```bash
26
+ # ุฅูŠู‚ุงู ุฌู…ูŠุน ุงู„ุนู…ู„ูŠุงุช / Stop all processes
27
+ taskkill /f /im python.exe
28
+
29
+ # ุชุดุบูŠู„ ุงู„ุฎุงุฏู… / Start server
30
+ python recorder_server.py
31
+ ```
32
+
33
+ #### ุฌ) ุงุณุชุฎุฏุงู… ุงู„ุจุฏุก ุงู„ู…ุชู‚ุฏู… / Use Debug Startup:
34
+ ```bash
35
+ python start_debug.py
36
+ ```
37
+
38
+ #### ุฏ) ูุญุต ุงู„ู…ู†ุงูุฐ / Check Ports:
39
+ ```bash
40
+ # Windows
41
+ netstat -an | findstr :5001
42
+
43
+ # Linux/Mac
44
+ lsof -i :5001
45
+ ```
46
+
47
+ ### 2. ู…ุดูƒู„ุฉ ู…ูุชุงุญ API / API Key Issues
48
+ ```
49
+ ERROR: GEMINI_API_KEY not found in environment variables
50
+ ```
51
+
52
+ **ุงู„ุญู„ / Solution:**
53
+ 1. ุชุฃูƒุฏ ู…ู† ูˆุฌูˆุฏ ู…ู„ู `.env`:
54
+ ```bash
55
+ # ุฅู†ุดุงุก ู…ู„ู .env / Create .env file
56
+ echo GEMINI_API_KEY=your_actual_api_key_here > .env
57
+ ```
58
+
59
+ 2. ุงุญุตู„ ุนู„ู‰ ู…ูุชุงุญ API ู…ู†:
60
+ - [Google AI Studio](https://makersuite.google.com/app/apikey)
61
+
62
+ 3. ุฃุถู ุงู„ู…ูุชุงุญ ุฅู„ู‰ `.env`:
63
+ ```
64
+ GEMINI_API_KEY=AIzaSyAS7JtrXjlNjyuo3RG5z6rkwocCwFy1YuA
65
+ ```
66
+
67
+ ### 3. ู…ุดุงูƒู„ ุงู„ุตูˆุช / Audio Issues
68
+ ```
69
+ UserWarning: PySoundFile failed. Trying audioread instead.
70
+ ```
71
+
72
+ **ุงู„ุญู„ูˆู„ / Solutions:**
73
+
74
+ #### ุฃ) ุชุซุจูŠุช SoundFile ู…ุฑุฉ ุฃุฎุฑู‰ / Reinstall SoundFile:
75
+ ```bash
76
+ pip uninstall soundfile
77
+ pip install soundfile
78
+ ```
79
+
80
+ #### ุจ) ุชุซุจูŠุช FFmpeg (ุฅุฐุง ู„ุฒู… ุงู„ุฃู…ุฑ) / Install FFmpeg if needed:
81
+ ```bash
82
+ # Windows (using chocolatey)
83
+ choco install ffmpeg
84
+
85
+ # Or download from: https://ffmpeg.org/download.html
86
+ ```
87
+
88
+ #### ุฌ) ูุญุต ุชู†ุณูŠู‚ ุงู„ู…ู„ู / Check Audio Format:
89
+ - ุงุณุชุฎุฏู… WAV ุจุฏู„ุงู‹ ู…ู† MP3
90
+ - ุชุฃูƒุฏ ู…ู† ุฌูˆุฏุฉ ุงู„ุชุณุฌูŠู„
91
+
92
+ ### 4. ู…ุดุงูƒู„ ุงู„ุชุฑุฌู…ุฉ / Translation Issues
93
+ ```
94
+ WARNING: Gemini returned empty translation response
95
+ ```
96
+
97
+ **ุงู„ุญู„ูˆู„ / Solutions:**
98
+
99
+ #### ุฃ) ูุญุต ุงุชุตุงู„ ุงู„ุฅู†ุชุฑู†ุช / Check Internet Connection:
100
+ ```bash
101
+ ping google.com
102
+ ```
103
+
104
+ #### ุจ) ุงุฎุชุจุงุฑ ู…ูุชุงุญ API / Test API Key:
105
+ ```python
106
+ python test_system.py
107
+ ```
108
+
109
+ #### ุฌ) ุชุบูŠูŠุฑ ุงู„ู†ู…ูˆุฐุฌ / Change Model:
110
+ - ุฅุฐุง ูุดู„ `gemini-2.5-flash`ุŒ ุฌุฑุจ `gemini-1.5-flash`
111
+
112
+ ### 5. ู…ุดุงูƒู„ ุงู„ูˆุงุฌู‡ุฉ / UI Issues
113
+
114
+ #### ุฃ) ุงู„ูˆุงุฌู‡ุฉ ู„ุง ุชุญู…ู‘ู„ / Interface Won't Load:
115
+ ```bash
116
+ # ุชุญู‚ู‚ ู…ู† ุงู„ู…ู†ูุฐ / Check port
117
+ python -c "import socket; s=socket.socket(); s.bind(('',8501)); print('Port 8501 available')"
118
+
119
+ # ุชุดุบูŠู„ ุนู„ู‰ ู…ู†ูุฐ ู…ุฎุชู„ู / Run on different port
120
+ streamlit run app.py --server.port 8502
121
+ ```
122
+
123
+ #### ุจ) ู…ุดุงูƒู„ ุงู„ู„ุบุฉ ุงู„ุนุฑุจูŠุฉ / Arabic Language Issues:
124
+ - ุชุฃูƒุฏ ู…ู† ุฏุนู… ุงู„ู…ุชุตูุญ ู„ู„ู€ RTL
125
+ - ุงุณุชุฎุฏู… Chrome ุฃูˆ Firefox ู„ู„ุฃูุถู„
126
+
127
+ ### 6. ู…ุดุงูƒู„ ุงู„ุฃุฏุงุก / Performance Issues
128
+
129
+ #### ุฃ) ุจุทุก ููŠ ุงู„ู…ุนุงู„ุฌุฉ / Slow Processing:
130
+ - ุชุญู‚ู‚ ู…ู† ุณุฑุนุฉ ุงู„ุฅู†ุชุฑู†ุช
131
+ - ู‚ู„ู„ ุญุฌู… ุงู„ู…ู„ู ุงู„ุตูˆุชูŠ
132
+ - ุงุณุชุฎุฏู… ุฌูˆุฏุฉ ุฃู‚ู„ ู„ู„ุชุณุฌูŠู„
133
+
134
+ #### ุจ) ุงุณุชู‡ู„ุงูƒ ุฐุงูƒุฑุฉ ุนุงู„ูŠ / High Memory Usage:
135
+ ```bash
136
+ # ุฅุนุงุฏุฉ ุชุดุบูŠู„ ุงู„ู†ุธุงู… / Restart system
137
+ python start_debug.py
138
+ ```
139
+
140
+ ## ๐Ÿ”ง ุฃุฏูˆุงุช ุงู„ุชุดุฎูŠุต / Diagnostic Tools
141
+
142
+ ### 1. ุงุฎุชุจุงุฑ ุดุงู…ู„ / Complete Test:
143
+ ```bash
144
+ python test_system.py
145
+ ```
146
+
147
+ ### 2. ูุญุต ุงู„ู…ู†ุงูุฐ / Port Check:
148
+ ```python
149
+ python -c "
150
+ import socket
151
+ ports = [5001, 8501, 8502]
152
+ for port in ports:
153
+ try:
154
+ s = socket.socket()
155
+ s.bind(('localhost', port))
156
+ s.close()
157
+ print(f'Port {port}: Available โœ…')
158
+ except:
159
+ print(f'Port {port}: Busy โŒ')
160
+ "
161
+ ```
162
+
163
+ ### 3. ูุญุต ุงู„ุชุจุนูŠุงุช / Dependencies Check:
164
+ ```bash
165
+ pip list | grep -E "(streamlit|flask|librosa|soundfile|google-generativeai)"
166
+ ```
167
+
168
+ ### 4. ูุญุต ุงู„ุนู…ู„ูŠุงุช / Process Check:
169
+ ```bash
170
+ # Windows
171
+ tasklist | findstr python
172
+
173
+ # Linux/Mac
174
+ ps aux | grep python
175
+ ```
176
+
177
+ ## ๐Ÿ“ฑ ู†ุตุงุฆุญ ู„ุญู„ ุงู„ู…ุดุงูƒู„ / Troubleshooting Tips
178
+
179
+ ### ู„ู„ุทู„ุงุจ ุงู„ุฌุฏุฏ / For New Users:
180
+ 1. **ุงุจุฏุฃ ุจุงู„ุงุฎุชุจุงุฑ ุงู„ุดุงู…ู„ / Start with system test**:
181
+ ```bash
182
+ python test_system.py
183
+ ```
184
+
185
+ 2. **ุงุณุชุฎุฏู… ุงู„ุจุฏุก ุงู„ู…ุชู‚ุฏู… / Use debug startup**:
186
+ ```bash
187
+ python start_debug.py
188
+ ```
189
+
190
+ 3. **ุชุญู‚ู‚ ู…ู† ุงู„ู…ุชุทู„ุจุงุช / Check requirements**:
191
+ - Python 3.8+
192
+ - ู…ูุชุงุญ Gemini API ุตุงู„ุญ
193
+ - ุงุชุตุงู„ ุฅู†ุชุฑู†ุช ู…ุณุชู‚ุฑ
194
+
195
+ ### ู„ู„ุทู„ุงุจ ุงู„ู…ุชู‚ุฏู…ูŠู† / For Advanced Users:
196
+ 1. **ู…ุฑุงุฌุนุฉ ุงู„ุณุฌู„ุงุช / Check logs**:
197
+ - ุงูุชุญ console ุงู„ู…ุชุตูุญ (F12)
198
+ - ุฑุงุฌุน ุณุฌู„ุงุช ุงู„ุทุฑููŠุฉ
199
+
200
+ 2. **ุชุฎุตูŠุต ุงู„ุฅุนุฏุงุฏุงุช / Customize settings**:
201
+ - ุบูŠุฑ ุงู„ู…ู†ุงูุฐ ููŠ ุญุงู„ุฉ ุงู„ุชุถุงุฑุจ
202
+ - ุนุฏู‘ู„ ุฅุนุฏุงุฏุงุช ุงู„ุตูˆุช
203
+
204
+ 3. **ุงู„ุชุดุฎูŠุต ุงู„ู…ุชู‚ุฏู… / Advanced diagnostics**:
205
+ ```python
206
+ # ุงุฎุชุจุงุฑ ุงู„ุงุชุตุงู„ / Test connection
207
+ import requests
208
+ response = requests.get('http://localhost:5001/record')
209
+ print(response.status_code, response.text)
210
+ ```
211
+
212
+ ## ๐Ÿ†˜ ุทู„ุจ ุงู„ู…ุณุงุนุฏุฉ / Getting Help
213
+
214
+ ### ู…ุนู„ูˆู…ุงุช ู…ุทู„ูˆุจุฉ / Required Information:
215
+ 1. ู†ุธุงู… ุงู„ุชุดุบูŠู„ / Operating System
216
+ 2. ุฅุตุฏุงุฑ Python / Python Version
217
+ 3. ู†ุชุงุฆุฌ `python test_system.py`
218
+ 4. ุฑุณุงุฆู„ ุงู„ุฎุทุฃ ุงู„ูƒุงู…ู„ุฉ / Complete error messages
219
+ 5. ุณุฌู„ุงุช ุงู„ุทุฑููŠุฉ / Terminal logs
220
+
221
+ ### ุฎุทูˆุงุช ุงู„ุฅุจู„ุงุบ / Reporting Steps:
222
+ 1. ุดุบู‘ู„ ุงู„ุงุฎุชุจุงุฑ ุงู„ุดุงู…ู„
223
+ 2. ุงุญูุธ ุงู„ู†ุชุงุฆุฌ
224
+ 3. ุตูˆู‘ุฑ ุฑุณุงุฆู„ ุงู„ุฎุทุฃ
225
+ 4. ุงุฐูƒุฑ ุงู„ุฎุทูˆุงุช ุงู„ุชูŠ ุฃุฏุช ู„ู„ู…ุดูƒู„ุฉ
226
+
227
+ ---
228
+
229
+ ## ๐ŸŽฏ Quick Fix Commands / ุฃูˆุงู…ุฑ ุงู„ุฅุตู„ุงุญ ุงู„ุณุฑูŠุน
230
+
231
+ ```bash
232
+ # ุฅุนุงุฏุฉ ุชุนูŠูŠู† ูƒุงู…ู„ / Complete Reset
233
+ taskkill /f /im python.exe
234
+ python test_system.py
235
+ python start_debug.py
236
+
237
+ # ุฅุตู„ุงุญ ุงู„ุชุจุนูŠุงุช / Fix Dependencies
238
+ pip install --upgrade -r requirements.txt
239
+
240
+ # ุฅุตู„ุงุญ ุงู„ู…ู†ุงูุฐ / Fix Ports
241
+ python start_debug.py
242
+
243
+ # ุงุฎุชุจุงุฑ ุงู„ุชุฑุฌู…ุฉ / Test Translation
244
+ python -c "from translator import AITranslator; t=AITranslator(); print(t.translate_text('Hello', 'ar'))"
245
+ ```
246
+
247
+ ---
248
+
249
+ **ุชุฐูƒุฑ: ู…ุนุธู… ุงู„ู…ุดุงูƒู„ ุชูุญู„ ุจุฅุนุงุฏุฉ ุชุดุบูŠู„ ุงู„ู†ุธุงู… ูˆุชุดุบูŠู„ ุงู„ุงุฎุชุจุงุฑ ุงู„ุดุงู…ู„! ๐Ÿ”„**
250
+
251
+ **Remember: Most issues are solved by restarting and running the system test! ๐Ÿ”„**
app.py ADDED
@@ -0,0 +1,528 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Final, Corrected, and Heavily Logged Version
2
+
3
+ import streamlit as st
4
+ import os
5
+ import tempfile
6
+ import json
7
+ from pathlib import Path
8
+ import time
9
+ import traceback
10
+ import streamlit.components.v1 as components
11
+
12
+ # --- Integrated Server Import ---
13
+ import threading
14
+
15
+ # Global variable to track server status
16
+ recorder_server_status = {"started": False, "error": None}
17
+
18
+ def start_recorder_async():
19
+ """Start recorder server in background thread"""
20
+ try:
21
+ from integrated_server import ensure_recorder_server, stop_all_servers
22
+ result = ensure_recorder_server()
23
+ recorder_server_status["started"] = result
24
+ # Make stop_all_servers available globally
25
+ globals()['stop_all_servers'] = stop_all_servers
26
+ except Exception as e:
27
+ recorder_server_status["error"] = str(e)
28
+ print(f"Warning: Could not start integrated recorder server: {e}")
29
+
30
+ # Start recorder server in background to avoid blocking UI
31
+ recorder_thread = threading.Thread(target=start_recorder_async, daemon=True)
32
+ recorder_thread.start()
33
+
34
+ # --- Critical Imports and Initial Checks ---
35
+ AUDIO_PROCESSOR_CLASS = None
36
+ IMPORT_ERROR_TRACEBACK = None
37
+ try:
38
+ from audio_processor import AudioProcessor
39
+ AUDIO_PROCESSOR_CLASS = AudioProcessor
40
+ except Exception:
41
+ IMPORT_ERROR_TRACEBACK = traceback.format_exc()
42
+
43
+ from video_generator import VideoGenerator
44
+ from mp3_embedder import MP3Embedder
45
+ from utils import format_timestamp, validate_audio_file, get_audio_info
46
+ from translator import get_translator, get_translation, UI_TRANSLATIONS
47
+
48
+ # --- Page Configuration ---
49
+ st.set_page_config(
50
+ page_title="SyncMaster - AI Audio-Text Synchronization",
51
+ page_icon="๐ŸŽต",
52
+ layout="wide"
53
+ )
54
+
55
+ # --- Browser Console Logging Utility ---
56
+ def log_to_browser_console(messages):
57
+ """Injects JavaScript to log messages to the browser's console."""
58
+ if isinstance(messages, str):
59
+ messages = [messages]
60
+ escaped_messages = [json.dumps(str(msg)) for msg in messages]
61
+ js_code = f"""
62
+ <script>
63
+ (function() {{
64
+ const logs = [{', '.join(escaped_messages)}];
65
+ console.group("Backend Logs from SyncMaster");
66
+ logs.forEach(log => {{
67
+ const content = String(log);
68
+ if (content.includes('--- ERROR') || content.includes('--- FATAL')) {{
69
+ console.error(log);
70
+ }} else if (content.includes('--- WARNING')) {{
71
+ console.warn(log);
72
+ }} else if (content.includes('--- DEBUG')) {{
73
+ console.debug(log);
74
+ }} else {{
75
+ console.log(log);
76
+ }}
77
+ }});
78
+ console.groupEnd();
79
+ }})();
80
+ </script>
81
+ """
82
+ components.html(js_code, height=0, scrolling=False)
83
+
84
+ # --- Session State Initialization ---
85
+ def initialize_session_state():
86
+ """Initializes the session state variables if they don't exist."""
87
+ if 'step' not in st.session_state:
88
+ st.session_state.step = 1
89
+ if 'audio_file' not in st.session_state:
90
+ st.session_state.audio_file = None
91
+ if 'language' not in st.session_state:
92
+ st.session_state.language = 'en'
93
+ if 'enable_translation' not in st.session_state:
94
+ st.session_state.enable_translation = True
95
+ if 'target_language' not in st.session_state:
96
+ st.session_state.target_language = 'ar'
97
+ if 'transcription_data' not in st.session_state:
98
+ st.session_state.transcription_data = None
99
+ if 'edited_text' not in st.session_state:
100
+ st.session_state.edited_text = ""
101
+ if 'video_style' not in st.session_state:
102
+ st.session_state.video_style = {
103
+ 'animation_style': 'Karaoke Style', 'text_color': '#FFFFFF',
104
+ 'highlight_color': '#FFD700', 'background_color': '#000000',
105
+ 'font_family': 'Arial', 'font_size': 48
106
+ }
107
+
108
+ # --- Main Application Logic ---
109
+ def main():
110
+ initialize_session_state()
111
+
112
+ # Apply fast loading optimizations
113
+ st.markdown("""
114
+ <style>
115
+ .stSpinner { display: none !important; }
116
+ .main .block-container { animation: fadeIn 0.2s ease-in-out; }
117
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
118
+ .block-container { padding-top: 1rem; }
119
+ </style>
120
+ """, unsafe_allow_html=True)
121
+
122
+ # Check recorder server status (non-blocking)
123
+ if recorder_server_status.get("error"):
124
+ st.warning(f"โš ๏ธ Recorder server issue: {recorder_server_status['error']}")
125
+
126
+ # Language Selection in sidebar
127
+ with st.sidebar:
128
+ st.markdown("## ๐ŸŒ Language Settings")
129
+ language_options = {'English': 'en', 'ุงู„ุนุฑุจูŠุฉ': 'ar'}
130
+ selected_lang_display = st.selectbox(
131
+ "Interface Language",
132
+ options=list(language_options.keys()),
133
+ index=0 if st.session_state.language == 'en' else 1
134
+ )
135
+ st.session_state.language = language_options[selected_lang_display]
136
+
137
+ # Translation Settings
138
+ st.markdown("## ๐Ÿ”ค Translation Settings")
139
+ st.session_state.enable_translation = st.checkbox(
140
+ "Enable AI Translation" if st.session_state.language == 'en' else "ุชูุนูŠู„ ุงู„ุชุฑุฌู…ุฉ ุจุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ",
141
+ value=st.session_state.enable_translation,
142
+ help="Automatically translate transcribed text" if st.session_state.language == 'en' else "ุชุฑุฌู…ุฉ ุงู„ู†ุต ุชู„ู‚ุงุฆูŠุงู‹"
143
+ )
144
+
145
+ if st.session_state.enable_translation:
146
+ target_lang_options = {
147
+ 'Arabic (ุงู„ุนุฑุจูŠุฉ)': 'ar',
148
+ 'English': 'en',
149
+ 'French (Franรงais)': 'fr',
150
+ 'Spanish (Espaรฑol)': 'es'
151
+ }
152
+ selected_target = st.selectbox(
153
+ "Target Language" if st.session_state.language == 'en' else "ุงู„ู„ุบุฉ ุงู„ู…ุณุชู‡ุฏูุฉ",
154
+ options=list(target_lang_options.keys()),
155
+ index=0
156
+ )
157
+ st.session_state.target_language = target_lang_options[selected_target]
158
+
159
+ # Get translations for current language
160
+ current_translations = UI_TRANSLATIONS.get(st.session_state.language, UI_TRANSLATIONS['en'])
161
+
162
+ # Main title with translation support
163
+ st.title("๐ŸŽต SyncMaster")
164
+ if st.session_state.language == 'ar':
165
+ st.markdown("### ู…ู†ุตุฉ ุงู„ู…ุฒุงู…ู†ุฉ ุงู„ุฐูƒูŠุฉ ุจูŠู† ุงู„ุตูˆุช ูˆุงู„ู†ุต")
166
+ st.markdown("ุญูˆู„ ู…ู„ูุงุชูƒ ุงู„ุตูˆุชูŠุฉ ุฅู„ู‰ ู…ู„ูุงุช MP3 ู…ุชูˆุงูู‚ุฉ ู…ุน ุงู„ุฃุฌู‡ุฒุฉ ุงู„ู…ุญู…ูˆู„ุฉ ู…ุน ูƒู„ู…ุงุช ู…ุชุฒุงู…ู†ุฉ ูˆู…ู‚ุงุทุน ููŠุฏูŠูˆ MP4 ู…ุชุญุฑูƒุฉ.")
167
+ else:
168
+ st.markdown("### The Intelligent Audio-Text Synchronization Platform")
169
+ st.markdown("Transform your audio files into mobile-compatible MP3s with synchronized lyrics and animated MP4 videos.")
170
+
171
+ col1, col2, col3 = st.columns(3)
172
+
173
+ with col1:
174
+ if st.session_state.step >= 1:
175
+ st.success("Step 1: Upload & Process")
176
+ else:
177
+ st.info("Step 1: Upload & Process")
178
+ with col2:
179
+ if st.session_state.step >= 2:
180
+ st.success("Step 2: Review & Customize")
181
+ else:
182
+ st.info("Step 2: Review & Customize")
183
+ with col3:
184
+ if st.session_state.step >= 3:
185
+ st.success("Step 3: Export")
186
+ else:
187
+ st.info("Step 3: Export")
188
+
189
+ st.divider()
190
+
191
+ if AUDIO_PROCESSOR_CLASS is None:
192
+ st.error("Fatal Error: The application could not start correctly.")
193
+ st.subheader("An error occurred while trying to import `AudioProcessor`:")
194
+ st.code(IMPORT_ERROR_TRACEBACK, language="python")
195
+ st.stop()
196
+
197
+ if st.session_state.step == 1:
198
+ step_1_upload_and_process()
199
+ elif st.session_state.step == 2:
200
+ step_2_review_and_customize()
201
+ elif st.session_state.step == 3:
202
+ step_3_export()
203
+
204
+ # --- Step 1: Upload and Process ---
205
+ def step_1_upload_and_process():
206
+ st.header("Step 1: Choose Your Audio Source")
207
+
208
+ upload_tab, record_tab = st.tabs(["๐Ÿ“ค Upload a File", "๐ŸŽ™๏ธ Record Audio"])
209
+
210
+ with upload_tab:
211
+ st.subheader("Upload an existing audio file")
212
+ uploaded_file = st.file_uploader("Choose an audio file", type=['mp3', 'wav', 'm4a'], help="Supported formats: MP3, WAV, M4A")
213
+ if uploaded_file:
214
+ st.session_state.audio_file = uploaded_file
215
+ st.success(f"File uploaded: {uploaded_file.name}")
216
+ st.audio(uploaded_file)
217
+ if st.button("๐Ÿš€ Start AI Processing", type="primary", use_container_width=True):
218
+ process_audio()
219
+ if st.session_state.audio_file:
220
+ if st.button("๐Ÿ”„ Upload Different File"):
221
+ reset_session()
222
+ st.rerun()
223
+
224
+ with record_tab:
225
+ st.subheader("Record audio directly from your microphone")
226
+ st.info("Use the recorder below. After processing, copy the resulting file path and paste it here.")
227
+
228
+ # Show the recorder component
229
+ show_recorder_page()
230
+
231
+ st.divider()
232
+
233
+ # Input for the file path
234
+ result_file_path = st.text_input("Paste the result file path here:")
235
+
236
+ if st.button("Process Recorded Audio", use_container_width=True):
237
+ if result_file_path and os.path.exists(result_file_path):
238
+ process_recorded_audio(result_file_path)
239
+ else:
240
+ st.error("Please provide a valid file path.")
241
+
242
+ def show_recorder_page():
243
+ """Renders the HTML for the recorder page."""
244
+ try:
245
+ with open('templates/recorder.html', 'r', encoding='utf-8') as f:
246
+ html_string = f.read()
247
+ components.html(html_string, height=650, scrolling=True)
248
+ except FileNotFoundError:
249
+ st.error("Could not find the recorder HTML file. Please ensure 'templates/recorder.html' exists.")
250
+
251
+ def process_recorded_audio(file_path):
252
+ """Processes the audio data from the recorded file."""
253
+ log_to_browser_console("--- INFO: Starting recorded audio processing. ---")
254
+ try:
255
+ with open(file_path, 'r') as f:
256
+ data = json.load(f)
257
+ log_to_browser_console(f"--- DEBUG: Loaded data from {file_path}. ---")
258
+
259
+ # Convert hex back to bytes
260
+ data['audio_bytes'] = bytes.fromhex(data['audio_bytes'])
261
+ log_to_browser_console("--- DEBUG: Converted hex audio data to bytes. ---")
262
+
263
+ st.session_state.transcription_data = data
264
+ st.session_state.edited_text = data['text']
265
+ st.session_state.step = 2
266
+ st.success("๐ŸŽ‰ Recording processed successfully! Please review the results.")
267
+ log_to_browser_console("--- SUCCESS: Recording processed and session updated. ---")
268
+
269
+ except Exception as e:
270
+ st.error(f"An error occurred while processing the recorded audio: {e}")
271
+ log_to_browser_console(f"--- FATAL ERROR in process_recorded_audio: {traceback.format_exc()} ---")
272
+ finally:
273
+ if os.path.exists(file_path):
274
+ os.unlink(file_path)
275
+ log_to_browser_console(f"--- DEBUG: Cleaned up temp file: {file_path}")
276
+
277
+ time.sleep(1)
278
+ st.rerun()
279
+
280
+ def process_audio():
281
+ if not st.session_state.audio_file:
282
+ st.error("No audio file found.")
283
+ return
284
+
285
+ tmp_file_path = None
286
+ log_to_browser_console("--- INFO: Starting enhanced audio processing with translation. ---")
287
+ try:
288
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(st.session_state.audio_file.name).suffix) as tmp_file:
289
+ st.session_state.audio_file.seek(0)
290
+ tmp_file.write(st.session_state.audio_file.getvalue())
291
+ tmp_file_path = tmp_file.name
292
+
293
+ processor = AUDIO_PROCESSOR_CLASS()
294
+
295
+ # Enhanced processing with translation support
296
+ if st.session_state.enable_translation:
297
+ with st.spinner("๐ŸŽค Performing AI processing (Transcription, Timestamps & Translation)..."):
298
+ result_data, processor_logs = processor.get_word_timestamps_with_translation(
299
+ tmp_file_path,
300
+ st.session_state.target_language
301
+ )
302
+
303
+ log_to_browser_console(processor_logs)
304
+
305
+ if not result_data or not result_data.get('original_text'):
306
+ st.warning("Could not generate transcription with translation. Check the browser console (F12) for detailed error logs.")
307
+ return
308
+
309
+ # Store enhanced results
310
+ word_timestamps = result_data['word_timestamps']
311
+ full_text = result_data['original_text']
312
+ translated_text = result_data['translated_text']
313
+ translation_success = result_data.get('translation_success', False)
314
+ detected_language = result_data.get('language_detected', 'unknown')
315
+
316
+ # Show translation results
317
+ if translation_success and translated_text != full_text:
318
+ st.success(f"๐ŸŒ Translation completed successfully! Detected language: {detected_language}")
319
+
320
+ col1, col2 = st.columns(2)
321
+ with col1:
322
+ st.subheader("Original Text")
323
+ st.text_area("Original Transcription", value=full_text, height=150, disabled=True)
324
+
325
+ with col2:
326
+ st.subheader(f"Translation ({st.session_state.target_language.upper()})")
327
+ st.text_area("Translated Text", value=translated_text, height=150, disabled=True)
328
+
329
+ # Store both versions
330
+ st.session_state.translated_text = translated_text
331
+ st.session_state.translation_success = True
332
+ else:
333
+ st.info("Translation was requested but may not have been successful.")
334
+ st.session_state.translation_success = False
335
+
336
+ else:
337
+ # Standard processing without translation
338
+ with st.spinner("๐ŸŽค Performing AI processing (Transcription & Timestamps)..."):
339
+ word_timestamps, processor_logs = processor.get_word_timestamps(tmp_file_path)
340
+
341
+ log_to_browser_console(processor_logs)
342
+
343
+ if not word_timestamps:
344
+ st.warning("Could not generate timestamps. Check the browser console (F12) for detailed error logs.")
345
+ return
346
+
347
+ full_text = " ".join([d['word'] for d in word_timestamps])
348
+ st.session_state.translation_success = False
349
+
350
+ st.session_state.audio_file.seek(0)
351
+ audio_bytes = st.session_state.audio_file.read()
352
+
353
+ st.session_state.transcription_data = {
354
+ 'text': full_text, # Use the text we derived
355
+ 'word_timestamps': word_timestamps,
356
+ 'audio_bytes': audio_bytes,
357
+ 'original_suffix': Path(st.session_state.audio_file.name).suffix
358
+ }
359
+ st.session_state.edited_text = full_text
360
+ st.session_state.step = 2
361
+ st.success("๐ŸŽ‰ AI processing complete! Please review the results.")
362
+
363
+ except Exception as e:
364
+ st.error("An unexpected error occurred in the main processing flow!")
365
+ st.exception(e)
366
+ log_to_browser_console(f"--- FATAL ERROR in app.py's process_audio: {traceback.format_exc()} ---")
367
+ finally:
368
+ if tmp_file_path and os.path.exists(tmp_file_path):
369
+ os.unlink(tmp_file_path)
370
+
371
+ time.sleep(1)
372
+ st.rerun()
373
+
374
+
375
+
376
+ # --- Step 2: Review and Customize ---
377
+ def step_2_review_and_customize():
378
+ st.header("Step 2: Review & Customize")
379
+ if 'transcription_data' not in st.session_state or not st.session_state.transcription_data:
380
+ st.error("No data found. Please return to Step 1.")
381
+ if st.button("โ† Back to Step 1"):
382
+ st.session_state.step = 1; st.rerun()
383
+ return
384
+ col1, col2 = st.columns([3, 2])
385
+ with col1:
386
+ st.subheader("๐Ÿ“ Text Editor")
387
+ edited_text = st.text_area("Transcribed Text", value=st.session_state.edited_text, height=300)
388
+ st.session_state.edited_text = edited_text
389
+ with col2:
390
+ st.subheader("๐ŸŽจ Video Style Customization")
391
+ st.session_state.video_style['animation_style'] = st.selectbox("Animation Style", ["Karaoke Style", "Pop-up Word"])
392
+ st.session_state.video_style['text_color'] = st.color_picker("Text Color", st.session_state.video_style['text_color'])
393
+ st.session_state.video_style['highlight_color'] = st.color_picker("Highlight Color", st.session_state.video_style['highlight_color'])
394
+
395
+ col1_nav, _, col3_nav = st.columns([1, 2, 1])
396
+ with col1_nav:
397
+ if st.button("โ† Back to Upload"):
398
+ st.session_state.step = 1; st.rerun()
399
+ with col3_nav:
400
+ if st.button("Continue to Export โ†’", type="primary"):
401
+ st.session_state.step = 3; st.rerun()
402
+
403
+ # --- Step 3: Export ---
404
+ def step_3_export():
405
+ st.header("Step 3: Export Your Synchronized Media")
406
+ if 'transcription_data' not in st.session_state or not st.session_state.transcription_data:
407
+ st.error("No data found. Please return to Step 1.")
408
+ if st.button("โ† Back to Step 1"):
409
+ st.session_state.step = 1; st.rerun()
410
+ return
411
+
412
+ col1, col2 = st.columns(2)
413
+ with col1:
414
+ st.subheader("๐ŸŽต MP3 Export")
415
+ if st.button("๐Ÿ“ฑ Export MP3 with Lyrics", type="primary", use_container_width=True):
416
+ export_mp3()
417
+ with col2:
418
+ st.subheader("๐ŸŽฌ MP4 Video Export")
419
+ if st.button("๐ŸŽฅ Generate Video Summary", type="primary", use_container_width=True):
420
+ export_mp4()
421
+ st.divider()
422
+ col1_nav, col2_nav, _ = st.columns([1, 1, 3])
423
+ with col1_nav:
424
+ if st.button("โ† Back to Customize"):
425
+ st.session_state.step = 2; st.rerun()
426
+ with col2_nav:
427
+ if st.button("๐Ÿ”„ Start Over"):
428
+ reset_session(); st.rerun()
429
+
430
+ # --- MP3 Export Function (with robust stateless logic and logging) ---
431
+ def export_mp3():
432
+ audio_path_for_export = None
433
+ log_to_browser_console("--- INFO: Starting MP3 export process. ---")
434
+ try:
435
+ with st.spinner("Embedding lyrics..."):
436
+ suffix = st.session_state.transcription_data['original_suffix']
437
+ audio_bytes = st.session_state.transcription_data['audio_bytes']
438
+ log_to_browser_console(f"--- DEBUG: Retrieved {len(audio_bytes) / 1024:.2f} KB of audio data from session_state.")
439
+
440
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp_audio_file:
441
+ tmp_audio_file.write(audio_bytes)
442
+ audio_path_for_export = tmp_audio_file.name
443
+ log_to_browser_console(f"--- DEBUG: Temp file for export created at: {audio_path_for_export}")
444
+
445
+ embedder = MP3Embedder()
446
+ word_timestamps = st.session_state.transcription_data['word_timestamps']
447
+ log_to_browser_console(f"--- DEBUG: Passing {len(word_timestamps)} timestamps to embedder.")
448
+
449
+ output_filename = f"synced_{Path(st.session_state.audio_file.name).stem}.mp3"
450
+
451
+ output_path, log_messages = embedder.embed_sylt_lyrics(
452
+ audio_path_for_export, word_timestamps,
453
+ st.session_state.edited_text, output_filename
454
+ )
455
+ log_to_browser_console(log_messages)
456
+
457
+ st.subheader("โœ… Export Complete")
458
+ if os.path.exists(output_path):
459
+ with open(output_path, 'rb') as f:
460
+ audio_bytes_to_download = f.read()
461
+ st.audio(audio_bytes_to_download, format='audio/mp3')
462
+
463
+ verification = embedder.verify_sylt_embedding(output_path)
464
+ log_to_browser_console(f"--- DEBUG: Verification result: {json.dumps(verification)}")
465
+ st.json(verification)
466
+
467
+ if verification.get('has_sylt'):
468
+ st.success(f"Successfully embedded {verification.get('sylt_entries', 0)} words!")
469
+ else:
470
+ st.warning("Warning: Could not verify SYLT embedding.")
471
+
472
+ st.download_button("Download Synced MP3", audio_bytes_to_download,
473
+ output_filename, "audio/mpeg", use_container_width=True)
474
+ else:
475
+ st.error("Failed to create the final MP3 file.")
476
+ log_to_browser_console(f"--- ERROR: Final output file not found at expected path: {output_path}")
477
+
478
+ except Exception as e:
479
+ st.error(f"An unexpected error occurred during MP3 export: {e}")
480
+ log_to_browser_console(f"--- FATAL ERROR in export_mp3: {traceback.format_exc()} ---")
481
+ finally:
482
+ if audio_path_for_export and os.path.exists(audio_path_for_export):
483
+ os.unlink(audio_path_for_export)
484
+ log_to_browser_console(f"--- DEBUG: Cleaned up temp export file: {audio_path_for_export}")
485
+
486
+ # --- Placeholder and Utility Functions ---
487
+ def export_mp4():
488
+ st.info("MP4 export functionality is not yet implemented.")
489
+
490
+ def reset_session():
491
+ """Resets the session state by clearing specific keys and re-initializing."""
492
+ log_to_browser_console("--- INFO: Resetting session state. ---")
493
+ keys_to_clear = ['step', 'audio_file', 'transcription_data', 'edited_text', 'video_style']
494
+ for key in keys_to_clear:
495
+ if key in st.session_state:
496
+ del st.session_state[key]
497
+ initialize_session_state()
498
+
499
+ # --- Entry Point ---
500
+ if __name__ == "__main__":
501
+ # Compatibility Patches for older Streamlit versions
502
+ import inspect as _st_inspect
503
+ if "type" not in _st_inspect.signature(st.button).parameters:
504
+ _orig_button = st.button
505
+ def _patched_button(label, *args, **kwargs):
506
+ kwargs.pop("type", None); kwargs.pop("use_container_width", None)
507
+ return _orig_button(label, *args, **kwargs)
508
+ st.button = _patched_button
509
+ if hasattr(st, "download_button"):
510
+ import inspect as _dl_inspect
511
+ _dl_sig = _dl_inspect.signature(st.download_button)
512
+ if "use_container_width" not in _dl_sig.parameters:
513
+ _orig_download_button = st.download_button
514
+ def _patched_download_button(label, data, *args, **kwargs):
515
+ kwargs.pop("use_container_width", None)
516
+ return _orig_download_button(label, data, *args, **kwargs)
517
+ st.download_button = _patched_download_button
518
+
519
+ # Setup cleanup handler
520
+ import atexit
521
+ try:
522
+ from integrated_server import stop_all_servers
523
+ atexit.register(stop_all_servers)
524
+ except ImportError:
525
+ pass
526
+
527
+ initialize_session_state()
528
+ main()
app_config.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration Module for SyncMaster
3
+ ุฅุนุฏุงุฏุงุช ุงู„ุชุทุจูŠู‚ ุงู„ุฃุณุงุณูŠุฉ
4
+ """
5
+
6
+ import os
7
+ import logging
8
+
9
+ # Configure logging
10
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
+
12
+ class AppConfig:
13
+ """Configuration class for SyncMaster application"""
14
+
15
+ # Server settings
16
+ STREAMLIT_PORT = int(os.getenv('STREAMLIT_PORT', 5050))
17
+ RECORDER_PORT = int(os.getenv('RECORDER_PORT', 5001))
18
+
19
+ # Development vs Production
20
+ IS_PRODUCTION = os.getenv('SPACE_ID') is not None or os.getenv('RAILWAY_ENVIRONMENT') is not None
21
+
22
+ # Host settings
23
+ if IS_PRODUCTION:
24
+ STREAMLIT_HOST = "0.0.0.0"
25
+ RECORDER_HOST = "0.0.0.0"
26
+ else:
27
+ STREAMLIT_HOST = "localhost"
28
+ RECORDER_HOST = "localhost"
29
+
30
+ # Integration settings
31
+ USE_INTEGRATED_SERVER = IS_PRODUCTION or os.getenv('USE_INTEGRATED_SERVER', 'true').lower() == 'true'
32
+
33
+ # Logging
34
+ LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
35
+
36
+ @classmethod
37
+ def get_streamlit_url(cls):
38
+ """Get the Streamlit application URL"""
39
+ return f"http://{cls.STREAMLIT_HOST}:{cls.STREAMLIT_PORT}"
40
+
41
+ @classmethod
42
+ def get_recorder_url(cls):
43
+ """Get the recorder server URL"""
44
+ return f"http://{cls.RECORDER_HOST}:{cls.RECORDER_PORT}"
45
+
46
+ @classmethod
47
+ def log_config(cls):
48
+ """Log current configuration"""
49
+ logging.info("๐Ÿ“‹ SyncMaster Configuration:")
50
+ logging.info(f" โ€ข Production Mode: {cls.IS_PRODUCTION}")
51
+ logging.info(f" โ€ข Integrated Server: {cls.USE_INTEGRATED_SERVER}")
52
+ logging.info(f" โ€ข Streamlit: {cls.get_streamlit_url()}")
53
+ logging.info(f" โ€ข Recorder: {cls.get_recorder_url()}")
54
+
55
+ # Initialize configuration
56
+ config = AppConfig()
57
+
58
+ if __name__ == "__main__":
59
+ config.log_config()
app_launcher.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ App Launcher - ูŠุดุบู„ ุงู„ุชุทุจูŠู‚ ู…ุน ุงู„ุฎุงุฏู… ุงู„ู…ุฏู…ุฌ
4
+ """
5
+
6
+ import os
7
+ import sys
8
+
9
+ # Add current directory to path
10
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
11
+
12
+ # Force start integrated server
13
+ print("๐Ÿš€ Starting integrated recorder server...")
14
+ try:
15
+ from integrated_server import integrated_server
16
+
17
+ # Force start the server
18
+ if not integrated_server.is_running:
19
+ result = integrated_server.start_recorder_server()
20
+ if result:
21
+ print("โœ… Recorder server started successfully")
22
+ else:
23
+ print("โš ๏ธ Warning: Could not start recorder server")
24
+ else:
25
+ print("โœ… Recorder server already running")
26
+
27
+ except Exception as e:
28
+ print(f"โŒ Error starting recorder server: {e}")
29
+
30
+ print("๐Ÿ“ฑ Loading main application...")
31
+
32
+ # Execute the app.py content directly
33
+ if __name__ == "__main__":
34
+ # If running directly, execute app.py
35
+ exec(open('app.py').read())
36
+ else:
37
+ # If imported by Streamlit, import and execute
38
+ try:
39
+ exec(open('app.py').read())
40
+ print("โœ… Application loaded successfully")
41
+ except Exception as e:
42
+ print(f"โŒ Error loading application: {e}")
43
+ raise
audio_processor.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # audio_processor.py - Enhanced with AI Translation Support
2
+
3
+ import os
4
+ from dotenv import load_dotenv
5
+ import tempfile
6
+ from typing import List, Dict, Optional, Tuple
7
+ import json
8
+ import traceback
9
+
10
+ # --- DEFINITIVE NUMBA FIX ---
11
+ # This MUST be done BEFORE importing librosa
12
+ os.environ["NUMBA_CACHE_DIR"] = "/tmp"
13
+
14
+ # Now, import librosa safely
15
+ import librosa
16
+ # --- END OF FIX ---
17
+
18
+ from google import genai
19
+ from google.genai import types
20
+ from translator import AITranslator
21
+
22
+ class AudioProcessor:
23
+ def __init__(self):
24
+ self.client = None
25
+ self.translator = None
26
+ self.init_error = None
27
+ self._initialize_gemini()
28
+ self._initialize_translator()
29
+
30
+ def _initialize_gemini(self):
31
+ try:
32
+ load_dotenv()
33
+ api_key = os.getenv("GEMINI_API_KEY")
34
+ if not api_key:
35
+ raise ValueError("GEMINI_API_KEY not found in environment variables.")
36
+ self.client = genai.Client(api_key=api_key)
37
+ except Exception as e:
38
+ self.init_error = f"--- FATAL ERROR during Gemini Init: {str(e)} ---"
39
+ self.client = None
40
+
41
+ def _initialize_translator(self):
42
+ """Initialize AI translator for multi-language support"""
43
+ try:
44
+ self.translator = AITranslator()
45
+ if self.translator.init_error:
46
+ print(f"--- WARNING: Translator has initialization error: {self.translator.init_error} ---")
47
+ except Exception as e:
48
+ print(f"--- WARNING: Translator initialization failed: {str(e)} ---")
49
+ self.translator = None
50
+
51
+ def transcribe_audio(self, audio_file_path: str) -> Tuple[Optional[str], Optional[str]]:
52
+ """
53
+ Transcribes audio. Returns (text, error_message).
54
+ """
55
+ if self.init_error:
56
+ return None, self.init_error
57
+ if not self.client:
58
+ return None, "--- ERROR: Gemini client is not available. ---"
59
+
60
+ try:
61
+ if not os.path.exists(audio_file_path):
62
+ return None, f"--- ERROR: Audio file for transcription not found at: {audio_file_path} ---"
63
+
64
+ with open(audio_file_path, 'rb') as f:
65
+ audio_bytes = f.read()
66
+
67
+ file_ext = os.path.splitext(audio_file_path)[1].lower()
68
+ mime_type = {'mp3': 'audio/mpeg', 'wav': 'audio/wav', 'm4a': 'audio/mp4'}.get(file_ext, 'audio/mpeg')
69
+
70
+ response = self.client.models.generate_content(
71
+ model="gemini-2.5-flash",
72
+ contents=[types.Part.from_bytes(data=audio_bytes, mime_type=mime_type), "Transcribe this audio."]
73
+ )
74
+
75
+ if response and response.text:
76
+ return response.text.strip(), None
77
+ else:
78
+ return None, "--- WARNING: Gemini returned an empty response for transcription. ---"
79
+
80
+ except Exception as e:
81
+ error_msg = f"--- FATAL ERROR during transcription: {traceback.format_exc()} ---"
82
+ return None, error_msg
83
+
84
+ def get_audio_duration(self, audio_file_path: str) -> Tuple[Optional[float], Optional[str]]:
85
+ """
86
+ Gets audio duration. Returns (duration, error_message).
87
+ """
88
+ try:
89
+ if not os.path.exists(audio_file_path):
90
+ return None, f"--- ERROR: Audio file for duration not found at: {audio_file_path} ---"
91
+
92
+ duration = librosa.get_duration(path=audio_file_path)
93
+ if duration is None or duration < 0.1:
94
+ return None, f"--- ERROR: librosa returned an invalid duration: {duration}s ---"
95
+ return duration, None
96
+ except Exception as e:
97
+ error_msg = f"--- FATAL ERROR getting audio duration with librosa: {traceback.format_exc()} ---"
98
+ return None, error_msg
99
+
100
+ def get_word_timestamps(self, audio_file_path: str) -> Tuple[List[Dict], List[str]]:
101
+ """
102
+ Generates timestamps. Returns (timestamps, log_messages).
103
+ """
104
+ logs = ["--- INFO: Starting get_word_timestamps... ---"]
105
+
106
+ transcription, error = self.transcribe_audio(audio_file_path)
107
+ if error:
108
+ logs.append(error)
109
+ return [], logs
110
+ logs.append(f"--- DEBUG: Transcription successful. Text: '{transcription[:50]}...'")
111
+
112
+ audio_duration, error = self.get_audio_duration(audio_file_path)
113
+ if error:
114
+ logs.append(error)
115
+ return [], logs
116
+ logs.append(f"--- DEBUG: Audio duration successful. Duration: {audio_duration:.2f}s")
117
+
118
+ words = transcription.split()
119
+ if not words:
120
+ logs.append("--- WARNING: Transcription resulted in zero words. ---")
121
+ return [], logs
122
+
123
+ logs.append(f"--- INFO: Distributing {len(words)} words across the duration. ---")
124
+ word_timestamps = []
125
+ total_words = len(words)
126
+ usable_duration = max(0, audio_duration - 1.0)
127
+
128
+ for i, word in enumerate(words):
129
+ start_time = 0.5 + (i * (usable_duration / total_words))
130
+ end_time = 0.5 + ((i + 1) * (usable_duration / total_words))
131
+ word_timestamps.append({'word': word.strip(), 'start': round(start_time, 3), 'end': round(end_time, 3)})
132
+
133
+ logs.append(f"--- SUCCESS: Generated {len(word_timestamps)} word timestamps. ---")
134
+ return word_timestamps, logs
135
+
136
+ def get_word_timestamps_with_translation(self, audio_file_path: str, target_language: str = 'ar') -> Tuple[Dict, List[str]]:
137
+ """
138
+ Enhanced function that provides both transcription and translation
139
+
140
+ Args:
141
+ audio_file_path: Path to audio file
142
+ target_language: Target language for translation ('ar' for Arabic)
143
+
144
+ Returns:
145
+ Tuple of (result_dict, log_messages)
146
+ result_dict contains: {
147
+ 'original_text': str,
148
+ 'translated_text': str,
149
+ 'word_timestamps': List[Dict],
150
+ 'translated_timestamps': List[Dict],
151
+ 'language_detected': str,
152
+ 'target_language': str
153
+ }
154
+ """
155
+ logs = ["--- INFO: Starting enhanced transcription with translation... ---"]
156
+
157
+ # Get original transcription and timestamps
158
+ word_timestamps, transcription_logs = self.get_word_timestamps(audio_file_path)
159
+ logs.extend(transcription_logs)
160
+
161
+ if not word_timestamps:
162
+ logs.append("--- ERROR: No transcription available for translation ---")
163
+ return {}, logs
164
+
165
+ # Extract original text
166
+ original_text = " ".join([d['word'] for d in word_timestamps])
167
+ logs.append(f"--- INFO: Original transcription: '{original_text[:50]}...' ---")
168
+
169
+ # Initialize result dictionary
170
+ result = {
171
+ 'original_text': original_text,
172
+ 'translated_text': '',
173
+ 'word_timestamps': word_timestamps,
174
+ 'translated_timestamps': [],
175
+ 'language_detected': 'unknown',
176
+ 'target_language': target_language,
177
+ 'translation_success': False
178
+ }
179
+
180
+ # Check if translator is available
181
+ if not self.translator:
182
+ logs.append("--- WARNING: Translator not available, returning original text only ---")
183
+ result['translated_text'] = original_text
184
+ return result, logs
185
+
186
+ try:
187
+ # Detect original language
188
+ detected_lang, detect_error = self.translator.detect_language(original_text)
189
+ if detected_lang and detected_lang != 'unknown':
190
+ result['language_detected'] = detected_lang
191
+ logs.append(f"--- INFO: Detected language: {detected_lang} ---")
192
+ else:
193
+ logs.append(f"--- WARNING: Language detection failed: {detect_error} ---")
194
+
195
+ # Translate the text
196
+ translated_text, translation_error = self.translator.translate_text(
197
+ original_text,
198
+ target_language=target_language
199
+ )
200
+
201
+ if translated_text:
202
+ result['translated_text'] = translated_text
203
+ result['translation_success'] = True
204
+ logs.append(f"--- SUCCESS: Translation completed: '{translated_text[:50]}...' ---")
205
+
206
+ # Create translated timestamps by mapping words
207
+ translated_timestamps = self._create_translated_timestamps(
208
+ word_timestamps,
209
+ original_text,
210
+ translated_text
211
+ )
212
+ result['translated_timestamps'] = translated_timestamps
213
+ logs.append(f"--- INFO: Created {len(translated_timestamps)} translated timestamps ---")
214
+
215
+ else:
216
+ logs.append(f"--- ERROR: Translation failed: {translation_error} ---")
217
+ result['translated_text'] = original_text # Fallback to original
218
+ result['translated_timestamps'] = word_timestamps # Use original timestamps
219
+
220
+ except Exception as e:
221
+ error_msg = f"--- FATAL ERROR during translation process: {traceback.format_exc()} ---"
222
+ logs.append(error_msg)
223
+ result['translated_text'] = original_text # Fallback
224
+ result['translated_timestamps'] = word_timestamps
225
+
226
+ return result, logs
227
+
228
+ def _create_translated_timestamps(self, original_timestamps: List[Dict], original_text: str, translated_text: str) -> List[Dict]:
229
+ """
230
+ Create timestamps for translated text by proportional mapping
231
+
232
+ Args:
233
+ original_timestamps: Original word timestamps
234
+ original_text: Original transcribed text
235
+ translated_text: Translated text
236
+
237
+ Returns:
238
+ List of translated word timestamps
239
+ """
240
+ try:
241
+ translated_words = translated_text.split()
242
+ if not translated_words:
243
+ return []
244
+
245
+ # Get total duration from original timestamps
246
+ if not original_timestamps:
247
+ return []
248
+
249
+ start_time = original_timestamps[0]['start']
250
+ end_time = original_timestamps[-1]['end']
251
+ total_duration = end_time - start_time
252
+
253
+ # Create proportional timestamps for translated words
254
+ translated_timestamps = []
255
+ word_count = len(translated_words)
256
+
257
+ for i, word in enumerate(translated_words):
258
+ # Calculate proportional timing
259
+ word_start = start_time + (i * total_duration / word_count)
260
+ word_end = start_time + ((i + 1) * total_duration / word_count)
261
+
262
+ translated_timestamps.append({
263
+ 'word': word.strip(),
264
+ 'start': round(word_start, 3),
265
+ 'end': round(word_end, 3)
266
+ })
267
+
268
+ return translated_timestamps
269
+
270
+ except Exception as e:
271
+ print(f"--- ERROR creating translated timestamps: {str(e)} ---")
272
+ return []
273
+
274
+ def batch_translate_transcription(self, audio_file_path: str, target_languages: List[str]) -> Tuple[Dict, List[str]]:
275
+ """
276
+ Transcribe audio and translate to multiple languages
277
+
278
+ Args:
279
+ audio_file_path: Path to audio file
280
+ target_languages: List of target language codes
281
+
282
+ Returns:
283
+ Tuple of (results_dict, log_messages)
284
+ """
285
+ logs = ["--- INFO: Starting batch translation process... ---"]
286
+
287
+ # Get original transcription
288
+ word_timestamps, transcription_logs = self.get_word_timestamps(audio_file_path)
289
+ logs.extend(transcription_logs)
290
+
291
+ if not word_timestamps:
292
+ return {}, logs
293
+
294
+ original_text = " ".join([d['word'] for d in word_timestamps])
295
+
296
+ # Initialize results
297
+ results = {
298
+ 'original': {
299
+ 'text': original_text,
300
+ 'timestamps': word_timestamps,
301
+ 'language': 'detected'
302
+ },
303
+ 'translations': {}
304
+ }
305
+
306
+ # Translate to each target language
307
+ if self.translator:
308
+ for lang_code in target_languages:
309
+ try:
310
+ translated_text, error = self.translator.translate_text(original_text, lang_code)
311
+ if translated_text:
312
+ translated_timestamps = self._create_translated_timestamps(
313
+ word_timestamps, original_text, translated_text
314
+ )
315
+ results['translations'][lang_code] = {
316
+ 'text': translated_text,
317
+ 'timestamps': translated_timestamps,
318
+ 'success': True
319
+ }
320
+ logs.append(f"--- SUCCESS: Translation to {lang_code} completed ---")
321
+ else:
322
+ results['translations'][lang_code] = {
323
+ 'text': original_text,
324
+ 'timestamps': word_timestamps,
325
+ 'success': False,
326
+ 'error': error
327
+ }
328
+ logs.append(f"--- ERROR: Translation to {lang_code} failed: {error} ---")
329
+ except Exception as e:
330
+ logs.append(f"--- FATAL ERROR translating to {lang_code}: {str(e)} ---")
331
+ else:
332
+ logs.append("--- WARNING: Translator not available for batch translation ---")
333
+
334
+ return results, logs
comprehensive_test.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ ุงุฎุชุจุงุฑ ุดุงู…ู„ ู„ู„ุชุญู‚ู‚ ู…ู† ุฅุตู„ุงุญ ุฌู…ูŠุน ุงู„ู…ุดุงูƒู„
5
+ """
6
+
7
+ import requests
8
+ import json
9
+ import time
10
+
11
+ def test_server_health():
12
+ """ุงุฎุชุจุงุฑ ุตุญุฉ ุงู„ุฎุงุฏู…"""
13
+ print("๐Ÿฅ ุงุฎุชุจุงุฑ ุตุญุฉ ุงู„ุฎุงุฏู…...")
14
+
15
+ try:
16
+ response = requests.get('http://localhost:5001/record', timeout=5)
17
+ if response.status_code == 200:
18
+ data = response.json()
19
+ print(f"โœ… ุงู„ุฎุงุฏู… ูŠุนู…ู„: {data.get('message')}")
20
+ return True
21
+ else:
22
+ print(f"โŒ ู…ุดูƒู„ุฉ ููŠ ุงู„ุฎุงุฏู…: {response.status_code}")
23
+ return False
24
+ except Exception as e:
25
+ print(f"โŒ ู„ุง ูŠู…ูƒู† ุงู„ูˆุตูˆู„ ู„ู„ุฎุงุฏู…: {e}")
26
+ return False
27
+
28
+ def test_cors_headers():
29
+ """ุงุฎุชุจุงุฑ CORS headers"""
30
+ print("\n๐Ÿ”ง ุงุฎุชุจุงุฑ CORS headers...")
31
+
32
+ try:
33
+ # ุงุฎุชุจุงุฑ OPTIONS request
34
+ response = requests.options('http://localhost:5001/summarize', timeout=5)
35
+
36
+ print(f"Status Code: {response.status_code}")
37
+
38
+ # ูุญุต CORS headers
39
+ cors_origin = response.headers.get('Access-Control-Allow-Origin')
40
+ cors_methods = response.headers.get('Access-Control-Allow-Methods')
41
+ cors_headers = response.headers.get('Access-Control-Allow-Headers')
42
+
43
+ print(f"CORS Origin: '{cors_origin}'")
44
+ print(f"CORS Methods: '{cors_methods}'")
45
+ print(f"CORS Headers: '{cors_headers}'")
46
+
47
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุนุฏู… ูˆุฌูˆุฏ ู‚ูŠู… ู…ูƒุฑุฑุฉ
48
+ if cors_origin and ',' in cors_origin and cors_origin.count('*') > 1:
49
+ print("โŒ ู…ุดูƒู„ุฉ: CORS Origin ูŠุญุชูˆูŠ ุนู„ู‰ ู‚ูŠู… ู…ูƒุฑุฑุฉ!")
50
+ return False
51
+ elif cors_origin == '*':
52
+ print("โœ… CORS Origin ุตุญูŠุญ")
53
+ return True
54
+ else:
55
+ print(f"โš ๏ธ CORS Origin ุบูŠุฑ ู…ุชูˆู‚ุน: {cors_origin}")
56
+ return False
57
+
58
+ except Exception as e:
59
+ print(f"โŒ ุฎุทุฃ ููŠ ุงุฎุชุจุงุฑ CORS: {e}")
60
+ return False
61
+
62
+ def test_summarization():
63
+ """ุงุฎุชุจุงุฑ ูˆุธูŠูุฉ ุงู„ุชู„ุฎูŠุต"""
64
+ print("\n๐Ÿค– ุงุฎุชุจุงุฑ ูˆุธูŠูุฉ ุงู„ุชู„ุฎูŠุต...")
65
+
66
+ test_data = {
67
+ "text": "Hello, how are you? What are you doing today? Tell me about your work and your plans.",
68
+ "language": "arabic",
69
+ "type": "full"
70
+ }
71
+
72
+ try:
73
+ response = requests.post(
74
+ 'http://localhost:5001/summarize',
75
+ json=test_data,
76
+ headers={'Content-Type': 'application/json'},
77
+ timeout=30
78
+ )
79
+
80
+ print(f"Status Code: {response.status_code}")
81
+
82
+ if response.status_code == 200:
83
+ data = response.json()
84
+ if data.get('success'):
85
+ print("โœ… ุงู„ุชู„ุฎูŠุต ู†ุฌุญ!")
86
+ summary = data.get('summary', '')
87
+ print(f"ุงู„ู…ู„ุฎุต: {summary[:100]}...")
88
+ return True
89
+ else:
90
+ print(f"โŒ ูุดู„ ุงู„ุชู„ุฎูŠุต: {data.get('error')}")
91
+ return False
92
+ else:
93
+ print(f"โŒ ุฎุทุฃ HTTP: {response.status_code}")
94
+ print(f"ุงู„ุฑุฏ: {response.text}")
95
+ return False
96
+
97
+ except Exception as e:
98
+ print(f"โŒ ุฎุทุฃ ููŠ ุงุฎุชุจุงุฑ ุงู„ุชู„ุฎูŠุต: {e}")
99
+ return False
100
+
101
+ def test_javascript_syntax():
102
+ """ุงุฎุชุจุงุฑ ุตูŠุบุฉ JavaScript"""
103
+ print("\n๐Ÿ“ ุงุฎุชุจุงุฑ ุตูŠุบุฉ JavaScript...")
104
+
105
+ try:
106
+ with open('templates/recorder.html', 'r', encoding='utf-8') as f:
107
+ content = f.read()
108
+
109
+ # ูุญุต ุจุณูŠุท ู„ู„ุฃู‚ูˆุงุณ
110
+ js_start = content.find('<script>')
111
+ js_end = content.find('</script>')
112
+
113
+ if js_start == -1 or js_end == -1:
114
+ print("โŒ ู„ุง ูŠู…ูƒู† ุงู„ุนุซูˆุฑ ุนู„ู‰ JavaScript")
115
+ return False
116
+
117
+ js_content = content[js_start:js_end]
118
+
119
+ # ุนุฏ ุงู„ุฃู‚ูˆุงุณ
120
+ open_braces = js_content.count('{')
121
+ close_braces = js_content.count('}')
122
+
123
+ print(f"ุฃู‚ูˆุงุณ ูุชุญ: {open_braces}")
124
+ print(f"ุฃู‚ูˆุงุณ ุฅุบู„ุงู‚: {close_braces}")
125
+
126
+ if open_braces == close_braces:
127
+ print("โœ… ุงู„ุฃู‚ูˆุงุณ ู…ุชูˆุงุฒู†ุฉ")
128
+
129
+ # ูุญุต ู„ู„ูƒู„ู…ุงุช ุงู„ู…ูุชุงุญูŠุฉ ุงู„ุฃุณุงุณูŠุฉ
130
+ if 'function' in js_content and 'async function' in js_content:
131
+ print("โœ… ุงู„ุฏูˆุงู„ ู…ูˆุฌูˆุฏุฉ")
132
+ return True
133
+ else:
134
+ print("โš ๏ธ ู„ุง ูŠู…ูƒู† ุงู„ุนุซูˆุฑ ุนู„ู‰ ุงู„ุฏูˆุงู„")
135
+ return False
136
+ else:
137
+ print(f"โŒ ุงู„ุฃู‚ูˆุงุณ ุบูŠุฑ ู…ุชูˆุงุฒู†ุฉ! ุงู„ูุฑู‚: {open_braces - close_braces}")
138
+ return False
139
+
140
+ except Exception as e:
141
+ print(f"โŒ ุฎุทุฃ ููŠ ูุญุต JavaScript: {e}")
142
+ return False
143
+
144
+ def test_translation_endpoints():
145
+ """ุงุฎุชุจุงุฑ endpoints ุงู„ุชุฑุฌู…ุฉ"""
146
+ print("\n๐ŸŒ ุงุฎุชุจุงุฑ endpoints ุงู„ุชุฑุฌู…ุฉ...")
147
+
148
+ try:
149
+ # ุงุฎุชุจุงุฑ ู‚ุงุฆู…ุฉ ุงู„ู„ุบุงุช
150
+ response = requests.get('http://localhost:5001/languages', timeout=5)
151
+ if response.status_code == 200:
152
+ print("โœ… endpoint ุงู„ู„ุบุงุช ูŠุนู…ู„")
153
+ else:
154
+ print(f"โš ๏ธ ู…ุดูƒู„ุฉ ููŠ endpoint ุงู„ู„ุบุงุช: {response.status_code}")
155
+
156
+ # ุงุฎุชุจุงุฑ UI translations
157
+ response = requests.get('http://localhost:5001/ui-translations/en', timeout=5)
158
+ if response.status_code == 200:
159
+ print("โœ… endpoint UI translations ูŠุนู…ู„")
160
+ else:
161
+ print(f"โš ๏ธ ู…ุดูƒู„ุฉ ููŠ endpoint UI translations: {response.status_code}")
162
+
163
+ return True
164
+
165
+ except Exception as e:
166
+ print(f"โŒ ุฎุทุฃ ููŠ ุงุฎุชุจุงุฑ endpoints ุงู„ุชุฑุฌู…ุฉ: {e}")
167
+ return False
168
+
169
+ def comprehensive_test():
170
+ """ุงุฎุชุจุงุฑ ุดุงู…ู„ ู„ุฌู…ูŠุน ุงู„ูˆุธุงุฆู"""
171
+ print("๐Ÿš€ ุจุฏุก ุงู„ุงุฎุชุจุงุฑ ุงู„ุดุงู…ู„")
172
+ print("=" * 60)
173
+
174
+ tests = [
175
+ ("ุตุญุฉ ุงู„ุฎุงุฏู…", test_server_health),
176
+ ("CORS Headers", test_cors_headers),
177
+ ("ูˆุธูŠูุฉ ุงู„ุชู„ุฎูŠุต", test_summarization),
178
+ ("ุตูŠุบุฉ JavaScript", test_javascript_syntax),
179
+ ("endpoints ุงู„ุชุฑุฌู…ุฉ", test_translation_endpoints)
180
+ ]
181
+
182
+ results = []
183
+
184
+ for test_name, test_func in tests:
185
+ print(f"\n๐Ÿงช ุงุฎุชุจุงุฑ: {test_name}")
186
+ print("-" * 40)
187
+
188
+ try:
189
+ result = test_func()
190
+ results.append((test_name, result))
191
+
192
+ if result:
193
+ print(f"โœ… {test_name}: ู†ุฌุญ")
194
+ else:
195
+ print(f"โŒ {test_name}: ูุดู„")
196
+
197
+ except Exception as e:
198
+ print(f"โŒ {test_name}: ุฎุทุฃ - {e}")
199
+ results.append((test_name, False))
200
+
201
+ # ุงู„ู†ุชุงุฆุฌ ุงู„ู†ู‡ุงุฆูŠุฉ
202
+ print("\n" + "=" * 60)
203
+ print("๐Ÿ“Š ู…ู„ุฎุต ู†ุชุงุฆุฌ ุงู„ุงุฎุชุจุงุฑ:")
204
+ print("=" * 60)
205
+
206
+ passed = 0
207
+ total = len(results)
208
+
209
+ for test_name, result in results:
210
+ status = "โœ… ู†ุฌุญ" if result else "โŒ ูุดู„"
211
+ print(f" {test_name}: {status}")
212
+ if result:
213
+ passed += 1
214
+
215
+ print(f"\nุงู„ู†ุชูŠุฌุฉ ุงู„ู†ู‡ุงุฆูŠุฉ: {passed}/{total} ุงุฎุชุจุงุฑุงุช ู†ุฌุญุช")
216
+
217
+ if passed == total:
218
+ print("๐ŸŽ‰ ุฌู…ูŠุน ุงู„ุงุฎุชุจุงุฑุงุช ู†ุฌุญุช! ุงู„ู†ุธุงู… ูŠุนู…ู„ ุจุดูƒู„ ู…ุซุงู„ูŠ")
219
+ return True
220
+ else:
221
+ print(f"โš ๏ธ {total - passed} ุงุฎุชุจุงุฑุงุช ูุดู„ุช - ู‡ู†ุงูƒ ู…ุดุงูƒู„ ุชุญุชุงุฌ ุฅุตู„ุงุญ")
222
+ return False
223
+
224
+ if __name__ == "__main__":
225
+ comprehensive_test()
database.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # database.py - ู†ุธุงู… ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช ุงู„ุจุณูŠุทุฉ ู„ู„ู…ู„ุงุญุธุงุช
2
+
3
+ import sqlite3
4
+ import json
5
+ import os
6
+ from datetime import datetime
7
+ from typing import List, Dict, Optional
8
+
9
+ class NotesDatabase:
10
+ """ู‚ุงุนุฏุฉ ุจูŠุงู†ุงุช ุจุณูŠุทุฉ ู„ุญูุธ ุงู„ู…ู„ุงุญุธุงุช ูˆุงู„ู…ู„ุฎุตุงุช"""
11
+
12
+ def __init__(self, db_path: str = "lecture_notes.db"):
13
+ self.db_path = db_path
14
+ self.init_database()
15
+
16
+ def init_database(self):
17
+ """ุฅู†ุดุงุก ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช ูˆุงู„ุฌุฏุงูˆู„"""
18
+ with sqlite3.connect(self.db_path) as conn:
19
+ cursor = conn.cursor()
20
+
21
+ # ุฌุฏูˆู„ ุงู„ู…ู„ุงุญุธุงุช ุงู„ุฑุฆูŠุณูŠ
22
+ cursor.execute('''
23
+ CREATE TABLE IF NOT EXISTS lecture_notes (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ title TEXT NOT NULL,
26
+ original_text TEXT NOT NULL,
27
+ translated_text TEXT,
28
+ summary TEXT,
29
+ key_points TEXT,
30
+ subject TEXT,
31
+ date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
32
+ date_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
33
+ audio_file_path TEXT,
34
+ language_detected TEXT,
35
+ target_language TEXT,
36
+ markers TEXT
37
+ )
38
+ ''')
39
+
40
+ # ุฌุฏูˆู„ ุงู„ู…ู„ุฎุตุงุช ุงู„ุณุฑูŠุนุฉ
41
+ cursor.execute('''
42
+ CREATE TABLE IF NOT EXISTS quick_summaries (
43
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
44
+ note_id INTEGER,
45
+ summary_type TEXT,
46
+ content TEXT,
47
+ date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
48
+ FOREIGN KEY (note_id) REFERENCES lecture_notes (id)
49
+ )
50
+ ''')
51
+
52
+ conn.commit()
53
+
54
+ def save_lecture_note(self, data: Dict) -> int:
55
+ """ุญูุธ ู…ู„ุงุญุธุฉ ู…ุญุงุถุฑุฉ ุฌุฏูŠุฏุฉ"""
56
+ with sqlite3.connect(self.db_path) as conn:
57
+ cursor = conn.cursor()
58
+
59
+ cursor.execute('''
60
+ INSERT INTO lecture_notes
61
+ (title, original_text, translated_text, summary, key_points,
62
+ subject, audio_file_path, language_detected, target_language, markers)
63
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
64
+ ''', (
65
+ data.get('title', 'ู…ุญุงุถุฑุฉ ุฌุฏูŠุฏุฉ'),
66
+ data.get('original_text', ''),
67
+ data.get('translated_text', ''),
68
+ data.get('summary', ''),
69
+ data.get('key_points', ''),
70
+ data.get('subject', ''),
71
+ data.get('audio_file_path', ''),
72
+ data.get('language_detected', ''),
73
+ data.get('target_language', ''),
74
+ json.dumps(data.get('markers', []))
75
+ ))
76
+
77
+ note_id = cursor.lastrowid
78
+ conn.commit()
79
+ return note_id
80
+
81
+ def get_all_notes(self, limit: int = 50) -> List[Dict]:
82
+ """ุงุณุชุฑุฌุงุน ุฌู…ูŠุน ุงู„ู…ู„ุงุญุธุงุช"""
83
+ with sqlite3.connect(self.db_path) as conn:
84
+ cursor = conn.cursor()
85
+
86
+ cursor.execute('''
87
+ SELECT * FROM lecture_notes
88
+ ORDER BY date_created DESC
89
+ LIMIT ?
90
+ ''', (limit,))
91
+
92
+ columns = [description[0] for description in cursor.description]
93
+ notes = []
94
+
95
+ for row in cursor.fetchall():
96
+ note = dict(zip(columns, row))
97
+ # ุชุญูˆูŠู„ markers ู…ู† JSON string ุฅู„ู‰ list
98
+ if note['markers']:
99
+ try:
100
+ note['markers'] = json.loads(note['markers'])
101
+ except:
102
+ note['markers'] = []
103
+ notes.append(note)
104
+
105
+ return notes
106
+
107
+ def get_note_by_id(self, note_id: int) -> Optional[Dict]:
108
+ """ุงุณุชุฑุฌุงุน ู…ู„ุงุญุธุฉ ู…ุญุฏุฏุฉ ุจุงู„ู€ ID"""
109
+ with sqlite3.connect(self.db_path) as conn:
110
+ cursor = conn.cursor()
111
+
112
+ cursor.execute('SELECT * FROM lecture_notes WHERE id = ?', (note_id,))
113
+ row = cursor.fetchone()
114
+
115
+ if row:
116
+ columns = [description[0] for description in cursor.description]
117
+ note = dict(zip(columns, row))
118
+ if note['markers']:
119
+ try:
120
+ note['markers'] = json.loads(note['markers'])
121
+ except:
122
+ note['markers'] = []
123
+ return note
124
+
125
+ return None
126
+
127
+ def update_note(self, note_id: int, data: Dict) -> bool:
128
+ """ุชุญุฏูŠุซ ู…ู„ุงุญุธุฉ ู…ูˆุฌูˆุฏุฉ"""
129
+ with sqlite3.connect(self.db_path) as conn:
130
+ cursor = conn.cursor()
131
+
132
+ # ุจู†ุงุก query ุงู„ุชุญุฏูŠุซ ุจู†ุงุกู‹ ุนู„ู‰ ุงู„ุจูŠุงู†ุงุช ุงู„ู…ูˆุฌูˆุฏุฉ
133
+ update_fields = []
134
+ values = []
135
+
136
+ for field in ['title', 'summary', 'key_points', 'subject']:
137
+ if field in data:
138
+ update_fields.append(f"{field} = ?")
139
+ values.append(data[field])
140
+
141
+ if not update_fields:
142
+ return False
143
+
144
+ update_fields.append("date_modified = CURRENT_TIMESTAMP")
145
+ values.append(note_id)
146
+
147
+ query = f"UPDATE lecture_notes SET {', '.join(update_fields)} WHERE id = ?"
148
+
149
+ cursor.execute(query, values)
150
+ conn.commit()
151
+
152
+ return cursor.rowcount > 0
153
+
154
+ def delete_note(self, note_id: int) -> bool:
155
+ """ุญุฐู ู…ู„ุงุญุธุฉ"""
156
+ with sqlite3.connect(self.db_path) as conn:
157
+ cursor = conn.cursor()
158
+
159
+ # ุญุฐู ุงู„ู…ู„ุฎุตุงุช ุงู„ู…ุฑุชุจุทุฉ ุฃูˆู„ุงู‹
160
+ cursor.execute('DELETE FROM quick_summaries WHERE note_id = ?', (note_id,))
161
+
162
+ # ุซู… ุญุฐู ุงู„ู…ู„ุงุญุธุฉ
163
+ cursor.execute('DELETE FROM lecture_notes WHERE id = ?', (note_id,))
164
+
165
+ conn.commit()
166
+ return cursor.rowcount > 0
167
+
168
+ def search_notes(self, query: str) -> List[Dict]:
169
+ """ุงู„ุจุญุซ ููŠ ุงู„ู…ู„ุงุญุธุงุช"""
170
+ with sqlite3.connect(self.db_path) as conn:
171
+ cursor = conn.cursor()
172
+
173
+ search_query = f"%{query}%"
174
+ cursor.execute('''
175
+ SELECT * FROM lecture_notes
176
+ WHERE title LIKE ? OR original_text LIKE ?
177
+ OR translated_text LIKE ? OR summary LIKE ?
178
+ ORDER BY date_created DESC
179
+ ''', (search_query, search_query, search_query, search_query))
180
+
181
+ columns = [description[0] for description in cursor.description]
182
+ notes = []
183
+
184
+ for row in cursor.fetchall():
185
+ note = dict(zip(columns, row))
186
+ if note['markers']:
187
+ try:
188
+ note['markers'] = json.loads(note['markers'])
189
+ except:
190
+ note['markers'] = []
191
+ notes.append(note)
192
+
193
+ return notes
194
+
195
+ def get_notes_by_subject(self, subject: str) -> List[Dict]:
196
+ """ุงุณุชุฑุฌุงุน ุงู„ู…ู„ุงุญุธุงุช ุญุณุจ ุงู„ู…ุงุฏุฉ"""
197
+ with sqlite3.connect(self.db_path) as conn:
198
+ cursor = conn.cursor()
199
+
200
+ cursor.execute('''
201
+ SELECT * FROM lecture_notes
202
+ WHERE subject = ?
203
+ ORDER BY date_created DESC
204
+ ''', (subject,))
205
+
206
+ columns = [description[0] for description in cursor.description]
207
+ notes = []
208
+
209
+ for row in cursor.fetchall():
210
+ note = dict(zip(columns, row))
211
+ if note['markers']:
212
+ try:
213
+ note['markers'] = json.loads(note['markers'])
214
+ except:
215
+ note['markers'] = []
216
+ notes.append(note)
217
+
218
+ return notes
219
+
220
+ def get_subjects(self) -> List[str]:
221
+ """ุงุณุชุฑุฌุงุน ู‚ุงุฆู…ุฉ ุงู„ู…ูˆุงุฏ ุงู„ุฏุฑุงุณูŠุฉ"""
222
+ with sqlite3.connect(self.db_path) as conn:
223
+ cursor = conn.cursor()
224
+
225
+ cursor.execute('''
226
+ SELECT DISTINCT subject FROM lecture_notes
227
+ WHERE subject IS NOT NULL AND subject != ''
228
+ ORDER BY subject
229
+ ''')
230
+
231
+ return [row[0] for row in cursor.fetchall()]
diagnose_summary.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ ู…ู„ู ุชุดุฎูŠุต ู…ุดูƒู„ุฉ ุฒุฑ ุงู„ุชู„ุฎูŠุต - ู…ุนุงู„ุฌุฉ ุดุงู…ู„ุฉ
5
+ """
6
+
7
+ import subprocess
8
+ import sys
9
+ import json
10
+ import requests
11
+ import time
12
+ import os
13
+
14
+ def check_server_process():
15
+ """ุงู„ุชุญู‚ู‚ ู…ู† ุนู…ู„ูŠุฉ ุงู„ุฎุงุฏู…"""
16
+ print("๐Ÿ” ุงู„ุจุญุซ ุนู† ุนู…ู„ูŠุฉ ุงู„ุฎุงุฏู…...")
17
+
18
+ try:
19
+ # ุงู„ุจุญุซ ููŠ ุงู„ุนู…ู„ูŠุงุช ุงู„ุฌุงุฑูŠุฉ
20
+ result = subprocess.run(
21
+ ['tasklist', '/FI', 'IMAGENAME eq python.exe'],
22
+ capture_output=True, text=True, shell=True
23
+ )
24
+
25
+ python_processes = []
26
+ for line in result.stdout.split('\n'):
27
+ if 'python.exe' in line:
28
+ python_processes.append(line.strip())
29
+
30
+ print(f"ุนุฏุฏ ุงู„ุนู…ู„ูŠุงุช Python ุงู„ุฌุงุฑูŠุฉ: {len(python_processes)}")
31
+
32
+ if python_processes:
33
+ print("โœ… ุงู„ุนู…ู„ูŠุงุช ุงู„ู…ูˆุฌูˆุฏุฉ:")
34
+ for proc in python_processes[:5]: # ุฃูˆู„ 5 ูู‚ุท
35
+ print(f" {proc}")
36
+
37
+ return len(python_processes) > 0
38
+
39
+ except Exception as e:
40
+ print(f"โŒ ุฎุทุฃ ููŠ ูุญุต ุงู„ุนู…ู„ูŠุงุช: {e}")
41
+ return False
42
+
43
+ def check_port_status():
44
+ """ูุญุต ุญุงู„ุฉ ุงู„ู…ู†ุงูุฐ"""
45
+ print("\n๐ŸŒ ูุญุต ุญุงู„ุฉ ุงู„ู…ู†ุงูุฐ...")
46
+
47
+ ports_to_check = [5001, 5054, 8501]
48
+ port_status = {}
49
+
50
+ for port in ports_to_check:
51
+ try:
52
+ result = subprocess.run(
53
+ ['netstat', '-an'],
54
+ capture_output=True, text=True, shell=True
55
+ )
56
+
57
+ is_listening = f':{port}' in result.stdout and 'LISTENING' in result.stdout
58
+ port_status[port] = is_listening
59
+
60
+ status_emoji = "โœ…" if is_listening else "โŒ"
61
+ print(f" {status_emoji} Port {port}: {'LISTENING' if is_listening else 'NOT LISTENING'}")
62
+
63
+ except Exception as e:
64
+ print(f" โŒ Port {port}: ุฎุทุฃ ููŠ ุงู„ูุญุต - {e}")
65
+ port_status[port] = False
66
+
67
+ return port_status
68
+
69
+ def test_cors_issue():
70
+ """ุงุฎุชุจุงุฑ ู…ุดูƒู„ุฉ CORS"""
71
+ print("\n๐Ÿ”ง ุงุฎุชุจุงุฑ ู…ุดูƒู„ุฉ CORS...")
72
+
73
+ try:
74
+ # ุทู„ุจ OPTIONS
75
+ response = requests.options(
76
+ 'http://localhost:5001/summarize',
77
+ timeout=5
78
+ )
79
+
80
+ print(f"Status Code: {response.status_code}")
81
+
82
+ # ูุญุต CORS headers
83
+ headers = dict(response.headers)
84
+ cors_origin = headers.get('Access-Control-Allow-Origin', 'ุบูŠุฑ ู…ูˆุฌูˆุฏ')
85
+
86
+ print(f"CORS Origin: '{cors_origin}'")
87
+
88
+ # ุชุญู‚ู‚ ู…ู† ุงู„ู…ุดูƒู„ุฉ
89
+ if ',' in cors_origin:
90
+ print("โŒ ู…ุดูƒู„ุฉ CORS: ุงู„ู‚ูŠู…ุฉ ุชุญุชูˆูŠ ุนู„ู‰ ููˆุงุตู„ ู…ุชุนุฏุฏุฉ!")
91
+ print(" ู‡ุฐุง ูŠุณุจุจ ุงู„ุฎุทุฃ: 'multiple values *, *'")
92
+ return False
93
+ elif cors_origin == '*':
94
+ print("โœ… CORS header ุตุญูŠุญ")
95
+ return True
96
+ else:
97
+ print(f"โš ๏ธ CORS header ุบูŠุฑ ู…ุชูˆู‚ุน: {cors_origin}")
98
+ return False
99
+
100
+ except requests.exceptions.ConnectionError:
101
+ print("โŒ ู„ุง ูŠู…ูƒู† ุงู„ุงุชุตุงู„ ุจุงู„ุฎุงุฏู… - ุงู„ุฎุงุฏู… ุบูŠุฑ ู…ุดุชุบู„")
102
+ return False
103
+ except Exception as e:
104
+ print(f"โŒ ุฎุทุฃ ููŠ ุงุฎุชุจุงุฑ CORS: {e}")
105
+ return False
106
+
107
+ def test_summarize_functionality():
108
+ """ุงุฎุชุจุงุฑ ูˆุธูŠูุฉ ุงู„ุชู„ุฎูŠุต"""
109
+ print("\n๐Ÿค– ุงุฎุชุจุงุฑ ูˆุธูŠูุฉ ุงู„ุชู„ุฎูŠุต...")
110
+
111
+ test_data = {
112
+ "text": "Hello, how are you? What are you doing today? Tell me about your work.",
113
+ "language": "arabic",
114
+ "type": "full"
115
+ }
116
+
117
+ try:
118
+ response = requests.post(
119
+ 'http://localhost:5001/summarize',
120
+ json=test_data,
121
+ headers={'Content-Type': 'application/json'},
122
+ timeout=30
123
+ )
124
+
125
+ print(f"Status Code: {response.status_code}")
126
+
127
+ if response.status_code == 200:
128
+ data = response.json()
129
+ print(f"Response Keys: {list(data.keys())}")
130
+
131
+ if data.get('success'):
132
+ print("โœ… ุงู„ุชู„ุฎูŠุต ู†ุฌุญ!")
133
+ print(f"Summary Type: {data.get('type', 'ุบูŠุฑ ู…ุญุฏุฏ')}")
134
+
135
+ if 'summary' in data:
136
+ summary_preview = str(data['summary'])[:100] + "..."
137
+ print(f"Summary Preview: {summary_preview}")
138
+ return True
139
+ else:
140
+ print("โš ๏ธ ู„ุง ูŠูˆุฌุฏ ู…ู„ุฎุต ููŠ ุงู„ุงุณุชุฌุงุจุฉ")
141
+ return False
142
+ else:
143
+ print(f"โŒ ูุดู„ ุงู„ุชู„ุฎูŠุต: {data.get('error', 'ุฎุทุฃ ุบูŠุฑ ู…ุนุฑูˆู')}")
144
+ return False
145
+ else:
146
+ error_text = response.text[:200]
147
+ print(f"โŒ ุฎุทุฃ HTTP {response.status_code}: {error_text}")
148
+ return False
149
+
150
+ except requests.exceptions.Timeout:
151
+ print("โŒ ุงู†ุชู‡ุช ู…ู‡ู„ุฉ ุงู„ุทู„ุจ - ุงู„ุฎุงุฏู… ุจุทูŠุก ุฃูˆ ู„ุง ูŠุณุชุฌูŠุจ")
152
+ return False
153
+ except Exception as e:
154
+ print(f"โŒ ุฎุทุฃ ููŠ ุงุฎุชุจุงุฑ ุงู„ุชู„ุฎูŠุต: {e}")
155
+ return False
156
+
157
+ def check_dependencies():
158
+ """ูุญุต ุงู„ู…ูƒุชุจุงุช ุงู„ู…ุทู„ูˆุจุฉ"""
159
+ print("\n๐Ÿ“ฆ ูุญุต ุงู„ู…ูƒุชุจุงุช ุงู„ู…ุทู„ูˆุจุฉ...")
160
+
161
+ required_packages = [
162
+ 'flask', 'flask-cors', 'requests',
163
+ 'google-generativeai', 'librosa', 'soundfile'
164
+ ]
165
+
166
+ missing_packages = []
167
+
168
+ for package in required_packages:
169
+ try:
170
+ result = subprocess.run(
171
+ [sys.executable, '-c', f'import {package.replace("-", "_")}'],
172
+ capture_output=True, text=True
173
+ )
174
+
175
+ if result.returncode == 0:
176
+ print(f" โœ… {package}")
177
+ else:
178
+ print(f" โŒ {package} - ุบูŠุฑ ู…ุซุจุช")
179
+ missing_packages.append(package)
180
+
181
+ except Exception as e:
182
+ print(f" โŒ {package} - ุฎุทุฃ ููŠ ุงู„ูุญุต: {e}")
183
+ missing_packages.append(package)
184
+
185
+ if missing_packages:
186
+ print(f"\nโš ๏ธ ุงู„ู…ูƒุชุจุงุช ุงู„ู…ูู‚ูˆุฏุฉ: {', '.join(missing_packages)}")
187
+ print("ุชุดุบูŠู„ ุงู„ุฃู…ุฑ: pip install " + " ".join(missing_packages))
188
+ return False
189
+ else:
190
+ print("\nโœ… ุฌู…ูŠุน ุงู„ู…ูƒุชุจุงุช ู…ุซุจุชุฉ")
191
+ return True
192
+
193
+ def restart_server_suggestion():
194
+ """ุงู‚ุชุฑุงุญุงุช ู„ุฅุนุงุฏุฉ ุชุดุบูŠู„ ุงู„ุฎุงุฏู…"""
195
+ print("\n๐Ÿ”„ ุงู‚ุชุฑุงุญุงุช ุงู„ุฅุตู„ุงุญ:")
196
+ print("1. ุฅูŠู‚ุงู ุงู„ุฎุงุฏู… ุงู„ุญุงู„ูŠ (Ctrl+C ููŠ ุงู„ุชูŠุฑู…ูŠู†ุงู„)")
197
+ print("2. ุชุดุบูŠู„ ุงู„ุฎุงุฏู… ู…ุฑุฉ ุฃุฎุฑู‰:")
198
+ print(" python recorder_server.py")
199
+ print("\n3. ุฃูˆ ุงุณุชุฎุฏุงู… ู…ู„ู ุงู„ุจุฏุก:")
200
+ print(" python start_debug.py")
201
+ print("\n4. ุงู„ุชุญู‚ู‚ ู…ู† ุชุดุบูŠู„ ุงู„ุฎุงุฏู…:")
202
+ print(" curl http://localhost:5001/record")
203
+
204
+ def main():
205
+ """ุงู„ุฏุงู„ุฉ ุงู„ุฑุฆูŠุณูŠุฉ ู„ู„ุชุดุฎูŠุต"""
206
+ print("=" * 60)
207
+ print("๐Ÿš€ ุชุดุฎูŠุต ุดุงู…ู„ ู„ู…ุดูƒู„ุฉ ุฒุฑ ุงู„ุชู„ุฎูŠุต")
208
+ print("=" * 60)
209
+
210
+ # ูุญุต ุงู„ุนู…ู„ูŠุงุช
211
+ has_python_process = check_server_process()
212
+
213
+ # ูุญุต ุงู„ู…ู†ุงูุฐ
214
+ port_status = check_port_status()
215
+
216
+ # ูุญุต ุงู„ู…ูƒุชุจุงุช
217
+ dependencies_ok = check_dependencies()
218
+
219
+ # ุฅุฐุง ูƒุงู† ุงู„ู…ู†ูุฐ ู…ูุชูˆุญุŒ ุงุฎุชุจุฑ CORS ูˆุงู„ุชู„ุฎูŠุต
220
+ if port_status.get(5001, False):
221
+ cors_ok = test_cors_issue()
222
+ if cors_ok:
223
+ summarize_ok = test_summarize_functionality()
224
+ else:
225
+ summarize_ok = False
226
+ print("\nโŒ ู„ุง ูŠู…ูƒู† ุงุฎุชุจุงุฑ ุงู„ุชู„ุฎูŠุต ุจุณุจุจ ู…ุดูƒู„ุฉ CORS")
227
+ else:
228
+ cors_ok = False
229
+ summarize_ok = False
230
+ print("\nโŒ ู„ุง ูŠู…ูƒู† ุงุฎุชุจุงุฑ CORS/ุงู„ุชู„ุฎูŠุต - ุงู„ุฎุงุฏู… ุบูŠุฑ ู…ุดุชุบู„")
231
+
232
+ # ุงู„ู†ุชูŠุฌุฉ ุงู„ู†ู‡ุงุฆูŠุฉ
233
+ print("\n" + "=" * 60)
234
+ print("๐Ÿ“Š ู…ู„ุฎุต ุงู„ุชุดุฎูŠุต:")
235
+ print("=" * 60)
236
+
237
+ print(f" ๐Ÿ“ฆ ุงู„ู…ูƒุชุจุงุช: {'โœ… ู…ูˆุฌูˆุฏุฉ' if dependencies_ok else 'โŒ ู…ูู‚ูˆุฏุฉ'}")
238
+ print(f" ๐Ÿ”ง ุนู…ู„ูŠุฉ Python: {'โœ… ุชุนู…ู„' if has_python_process else 'โŒ ู„ุง ุชุนู…ู„'}")
239
+ print(f" ๐ŸŒ ุงู„ู…ู†ูุฐ 5001: {'โœ… ู…ูุชูˆุญ' if port_status.get(5001) else 'โŒ ู…ุบู„ู‚'}")
240
+ print(f" ๐Ÿ”ง CORS: {'โœ… ุตุญูŠุญ' if cors_ok else 'โŒ ู…ุดูƒู„ุฉ'}")
241
+ print(f" ๐Ÿค– ุงู„ุชู„ุฎูŠุต: {'โœ… ูŠุนู…ู„' if summarize_ok else 'โŒ ู„ุง ูŠุนู…ู„'}")
242
+
243
+ if summarize_ok:
244
+ print("\n๐ŸŽ‰ ุฌู…ูŠุน ุงู„ุงุฎุชุจุงุฑุงุช ู†ุฌุญุช! ุงู„ู…ุดูƒู„ุฉ ู…ุญู„ูˆู„ุฉ.")
245
+ elif not port_status.get(5001):
246
+ print("\n๐Ÿ”„ ูŠุฌุจ ุชุดุบูŠู„ ุงู„ุฎุงุฏู… ุฃูˆู„ุงู‹")
247
+ restart_server_suggestion()
248
+ elif not cors_ok:
249
+ print("\n๐Ÿ”„ ู…ุดูƒู„ุฉ CORS - ูŠุฌุจ ุฅุนุงุฏุฉ ุชุดุบูŠู„ ุงู„ุฎุงุฏู…")
250
+ restart_server_suggestion()
251
+ else:
252
+ print("\n๐Ÿ” ู‡ู†ุงูƒ ู…ุดูƒู„ุฉ ุฃุฎุฑู‰ ุชุญุชุงุฌ ู„ู…ุฒูŠุฏ ู…ู† ุงู„ุชุญู‚ู‚")
253
+
254
+ print("=" * 60)
255
+
256
+ if __name__ == "__main__":
257
+ main()
fast_loading.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Fast Loading Configuration for Streamlit
3
+ ุชุญุณูŠู† ุณุฑุนุฉ ุชุญู…ูŠู„ Streamlit
4
+ """
5
+
6
+ import streamlit as st
7
+
8
+ def apply_fast_loading_config():
9
+ """Apply configurations for faster loading"""
10
+
11
+ # Custom CSS to prevent flash of unstyled content
12
+ st.markdown("""
13
+ <style>
14
+ /* Hide loading spinner faster */
15
+ .stSpinner {
16
+ display: none !important;
17
+ }
18
+
19
+ /* Faster fade-in */
20
+ .main .block-container {
21
+ animation: fadeIn 0.1s ease-in-out;
22
+ }
23
+
24
+ @keyframes fadeIn {
25
+ from { opacity: 0; }
26
+ to { opacity: 1; }
27
+ }
28
+
29
+ /* Optimize fonts loading */
30
+ body {
31
+ font-display: swap;
32
+ }
33
+
34
+ /* Remove unnecessary margins */
35
+ .block-container {
36
+ padding-top: 1rem;
37
+ }
38
+ </style>
39
+ """, unsafe_allow_html=True)
40
+
41
+ def show_instant_content():
42
+ """Show content immediately without waiting"""
43
+ st.markdown("""
44
+ <div style="text-align: center; padding: 20px;">
45
+ <h1>๐ŸŽต SyncMaster</h1>
46
+ <p>ู…ู†ุตุฉ ุงู„ู…ุฒุงู…ู†ุฉ ุงู„ุฐูƒูŠุฉ ุจูŠู† ุงู„ุตูˆุช ูˆุงู„ู†ุต</p>
47
+ <div style="background: linear-gradient(45deg, #1f77b4, #17becf);
48
+ color: white; padding: 10px; border-radius: 5px; margin: 10px;">
49
+ โœ… ุงู„ุชุทุจูŠู‚ ุฌุงู‡ุฒ ู„ู„ุงุณุชุฎุฏุงู…
50
+ </div>
51
+ </div>
52
+ """, unsafe_allow_html=True)
integrated_server.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Integrated Server Module
3
+ ูŠุฏู…ุฌ ุฎุงุฏู… Flask ู„ู„ุชุณุฌูŠู„ ู…ุน ุชุทุจูŠู‚ Streamlit
4
+ """
5
+
6
+ import threading
7
+ import time
8
+ import logging
9
+ import sys
10
+ import subprocess
11
+ import os
12
+ import socket
13
+ from pathlib import Path
14
+
15
+ # Import configuration
16
+ try:
17
+ from app_config import config
18
+ except ImportError:
19
+ # Fallback configuration
20
+ class FallbackConfig:
21
+ RECORDER_PORT = 5001
22
+ RECORDER_HOST = "localhost"
23
+ USE_INTEGRATED_SERVER = True
24
+ config = FallbackConfig()
25
+
26
+ # Configure logging
27
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
28
+
29
+ def is_port_in_use(port):
30
+ """Check if a port is already in use"""
31
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
32
+ try:
33
+ s.bind(('localhost', port))
34
+ return False
35
+ except socket.error:
36
+ return True
37
+
38
+ class IntegratedServer:
39
+ def __init__(self):
40
+ self.flask_process = None
41
+ self.flask_thread = None
42
+ self.is_running = False
43
+ self.port = config.RECORDER_PORT
44
+ self.host = config.RECORDER_HOST
45
+
46
+ def start_recorder_server_thread(self):
47
+ """Start the recorder server in a separate thread"""
48
+ try:
49
+ # Check if port is already in use
50
+ if is_port_in_use(self.port):
51
+ logging.info(f"โœ… Port {self.port} already in use, assuming recorder server is running")
52
+ self.is_running = True
53
+ return True
54
+
55
+ # Import Flask app from recorder_server
56
+ from recorder_server import app
57
+
58
+ # Configure Flask to run without debug mode
59
+ app.config['DEBUG'] = False
60
+ app.config['TESTING'] = False
61
+
62
+ # Run Flask app in thread
63
+ def run_flask():
64
+ try:
65
+ app.run(
66
+ host=self.host,
67
+ port=self.port,
68
+ debug=False,
69
+ use_reloader=False,
70
+ threaded=True
71
+ )
72
+ except Exception as e:
73
+ logging.error(f"Error running Flask server: {e}")
74
+
75
+ self.flask_thread = threading.Thread(target=run_flask, daemon=True)
76
+ self.flask_thread.start()
77
+
78
+ # Wait for server to start with shorter intervals
79
+ max_retries = 15
80
+ for i in range(max_retries):
81
+ time.sleep(0.5) # Reduced from 1 second to 0.5 seconds
82
+ if self.is_server_responding():
83
+ self.is_running = True
84
+ logging.info(f"โœ… Recorder server started successfully on port {self.port}")
85
+ return True
86
+ if i < 5: # Only log first few attempts to reduce noise
87
+ logging.info(f"โณ Waiting for recorder server to start... ({i+1}/{max_retries})")
88
+
89
+ logging.warning("โš ๏ธ Recorder server thread started but not responding")
90
+ return False
91
+
92
+ except Exception as e:
93
+ logging.error(f"โŒ Failed to start recorder server thread: {e}")
94
+ return False
95
+
96
+ def start_recorder_server_process(self):
97
+ """Start the recorder server as a separate process (fallback method)"""
98
+ try:
99
+ # Check if port is already in use
100
+ if is_port_in_use(self.port):
101
+ logging.info(f"โœ… Port {self.port} already in use, assuming recorder server is running")
102
+ self.is_running = True
103
+ return True
104
+
105
+ # Start recorder server as subprocess
106
+ self.flask_process = subprocess.Popen(
107
+ [sys.executable, 'recorder_server.py'],
108
+ stdout=subprocess.PIPE,
109
+ stderr=subprocess.PIPE,
110
+ cwd=os.getcwd()
111
+ )
112
+
113
+ # Wait for server to start with shorter intervals
114
+ max_retries = 20
115
+ for i in range(max_retries):
116
+ time.sleep(0.5) # Reduced from 1 second to 0.5 seconds
117
+
118
+ # Check if process is still running
119
+ if self.flask_process.poll() is not None:
120
+ logging.error("โŒ Recorder server process terminated")
121
+ return False
122
+
123
+ # Check if server is responding
124
+ if self.is_server_responding():
125
+ self.is_running = True
126
+ logging.info(f"โœ… Recorder server started successfully as subprocess on port {self.port}")
127
+ return True
128
+
129
+ if i < 5: # Only log first few attempts
130
+ logging.info(f"โณ Waiting for recorder server to respond... ({i+1}/{max_retries})")
131
+
132
+ logging.warning("โš ๏ธ Recorder server process started but not responding")
133
+ return False
134
+
135
+ except Exception as e:
136
+ logging.error(f"โŒ Failed to start recorder server process: {e}")
137
+ return False
138
+
139
+ def start_recorder_server(self):
140
+ """Start recorder server using the best available method"""
141
+ logging.info("๐Ÿš€ Starting integrated recorder server...")
142
+
143
+ # If already running, don't start again
144
+ if self.is_running and self.is_server_responding():
145
+ logging.info("โœ… Recorder server already running and responding")
146
+ return True
147
+
148
+ # Try thread method first (cleaner for integration)
149
+ if self.start_recorder_server_thread():
150
+ logging.info("โœ… Recorder server started using thread method")
151
+ return True
152
+
153
+ # Fallback to process method
154
+ logging.info("โš ๏ธ Thread method failed, trying process method...")
155
+ if self.start_recorder_server_process():
156
+ logging.info("โœ… Recorder server started using process method")
157
+ return True
158
+
159
+ logging.error("โŒ Failed to start recorder server using any method")
160
+ return False
161
+
162
+ def stop_recorder_server(self):
163
+ """Stop the recorder server"""
164
+ try:
165
+ if self.flask_process and self.flask_process.poll() is None:
166
+ self.flask_process.terminate()
167
+ try:
168
+ self.flask_process.wait(timeout=5)
169
+ logging.info("โœ… Recorder server process terminated")
170
+ except subprocess.TimeoutExpired:
171
+ self.flask_process.kill()
172
+ logging.info("โš ๏ธ Recorder server process killed")
173
+
174
+ self.is_running = False
175
+
176
+ except Exception as e:
177
+ logging.error(f"โŒ Error stopping recorder server: {e}")
178
+
179
+ def is_server_responding(self):
180
+ """Check if the recorder server is responding"""
181
+ try:
182
+ import requests
183
+ # Very fast check - reduce timeout further
184
+ response = requests.get(f'http://{self.host}:{self.port}/record', timeout=0.5)
185
+ return response.status_code == 200
186
+ except:
187
+ return False
188
+
189
+ def is_server_running(self):
190
+ """Check if the recorder server is running"""
191
+ return self.is_running and self.is_server_responding()
192
+
193
+ # Global instance
194
+ integrated_server = IntegratedServer()
195
+
196
+ def ensure_recorder_server():
197
+ """Ensure recorder server is running, start if not"""
198
+ try:
199
+ # Quick check first - if server responds immediately, no delay
200
+ if integrated_server.is_server_responding():
201
+ logging.info("โœ… Recorder server already running")
202
+ integrated_server.is_running = True
203
+ return True
204
+
205
+ # Only start if not detected
206
+ logging.info("๐Ÿ”„ Recorder server not detected, starting...")
207
+ result = integrated_server.start_recorder_server()
208
+ return result
209
+ except Exception as e:
210
+ logging.error(f"โŒ Error in ensure_recorder_server: {e}")
211
+ return False
212
+
213
+ def stop_all_servers():
214
+ """Stop all integrated servers"""
215
+ integrated_server.stop_recorder_server()
216
+
217
+ # Auto-start when module is imported (but not in main)
218
+ if __name__ != "__main__":
219
+ # Only auto-start if not running as main module and not already started
220
+ try:
221
+ if not integrated_server.is_running:
222
+ ensure_recorder_server()
223
+ except Exception as e:
224
+ logging.error(f"โŒ Error during auto-start: {e}")
225
+
226
+ if __name__ == "__main__":
227
+ # Test the integrated server
228
+ print("Testing Integrated Server...")
229
+ success = ensure_recorder_server()
230
+ if success:
231
+ print("โœ… Server test successful")
232
+ time.sleep(2)
233
+ stop_all_servers()
234
+ else:
235
+ print("โŒ Server test failed")
lecture_notes.db ADDED
Binary file (16.4 kB). View file
 
main.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple launcher for SyncMaster with integrated server
4
+ ู…ูุดุบู„ ุจุณูŠุท ู…ุน ุฎุงุฏู… ู…ุชูƒุงู…ู„ - ู…ุซุงู„ูŠ ู„ู€ HuggingFace
5
+ """
6
+
7
+ import streamlit as st
8
+ import os
9
+ import sys
10
+ import time
11
+
12
+ # Add current directory to path
13
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
14
+
15
+ # Initialize integrated server first
16
+ recorder_server_started = False
17
+
18
+ def start_integrated_server():
19
+ """Start the integrated recorder server"""
20
+ global recorder_server_started
21
+
22
+ if recorder_server_started:
23
+ return True
24
+
25
+ try:
26
+ from integrated_server import ensure_recorder_server
27
+ result = ensure_recorder_server()
28
+ if result:
29
+ recorder_server_started = True
30
+ st.success("โœ… Integrated recorder server is running on port 5001")
31
+ else:
32
+ st.warning("โš ๏ธ Could not start integrated recorder server")
33
+ return result
34
+ except Exception as e:
35
+ st.error(f"โŒ Error starting integrated recorder server: {e}")
36
+ return False
37
+
38
+ # Start the integrated server when the module loads
39
+ start_integrated_server()
40
+
41
+ # Import the main app module
42
+ try:
43
+ import app
44
+ except Exception as e:
45
+ st.error(f"โŒ Error loading main application: {e}")
46
+ st.stop()
47
+
48
+ if __name__ == "__main__":
49
+ print("๐Ÿš€ SyncMaster main.py executed directly")
mp3_embedder.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from mutagen.mp3 import MP3
2
+ from mutagen.id3 import ID3, SYLT, USLT, Encoding
3
+ import os
4
+ import tempfile
5
+ import shutil
6
+ import subprocess
7
+ from typing import List, Dict, Tuple
8
+
9
+ # --- Helper function to check for ffmpeg ---
10
+ def is_ffmpeg_available():
11
+ """Check if ffmpeg is installed and accessible in the system's PATH."""
12
+ return shutil.which("ffmpeg") is not None
13
+
14
+ class MP3Embedder:
15
+ """Handles embedding SYLT synchronized lyrics into MP3 files with robust error handling."""
16
+
17
+ def __init__(self):
18
+ """Initialize the MP3 embedder."""
19
+ self.temp_dir = "/tmp/audio_sync"
20
+ os.makedirs(self.temp_dir, exist_ok=True)
21
+
22
+ self.ffmpeg_available = is_ffmpeg_available()
23
+
24
+ def embed_sylt_lyrics(self, audio_path: str, word_timestamps: List[Dict],
25
+ text: str, output_filename: str) -> Tuple[str, List[str]]:
26
+ """
27
+ Embeds SYLT synchronized lyrics into an MP3 file and returns logs.
28
+
29
+ Returns:
30
+ A tuple containing:
31
+ - The path to the output MP3 file.
32
+ - A list of log messages detailing the process.
33
+ """
34
+ log_messages = []
35
+ def log_and_print(message):
36
+ log_messages.append(message)
37
+ print(f"MP3_EMBEDDER: {message}")
38
+ log_and_print(f"--- MP3Embedder initialized. ffmpeg available: {self.ffmpeg_available} ---")
39
+ log_and_print(f"--- Starting SYLT embedding for: {os.path.basename(audio_path)} ---")
40
+ output_path = os.path.join(self.temp_dir, output_filename)
41
+ try:
42
+ # --- Step 1: Ensure the file is in MP3 format ---
43
+ if not audio_path.lower().endswith('.mp3'):
44
+ if self.ffmpeg_available:
45
+ log_and_print(f"'{os.path.basename(audio_path)}' is not an MP3. Converting with ffmpeg...")
46
+ try:
47
+ subprocess.run(
48
+ ['ffmpeg', '-i', audio_path, '-codec:a', 'libmp3lame', '-q:a', '2', output_path],
49
+ check=True, capture_output=True, text=True
50
+ )
51
+ log_and_print("--- ffmpeg conversion successful. ---")
52
+ except subprocess.CalledProcessError as e:
53
+ log_and_print("--- ERROR: ffmpeg conversion failed. ---")
54
+ log_and_print(f"--- ffmpeg stderr: {e.stderr} ---")
55
+ log_and_print("--- Fallback: Copying original file without conversion. ---")
56
+ shutil.copy2(audio_path, output_path)
57
+ else:
58
+ log_and_print("--- WARNING: ffmpeg is not available. Cannot convert non-MP3 file. Copying directly. ---")
59
+ shutil.copy2(audio_path, output_path)
60
+ else:
61
+ log_and_print("--- Audio is already MP3. Copying to temporary location. ---")
62
+ shutil.copy2(audio_path, output_path)
63
+
64
+ # --- Step 2: Create SYLT data ---
65
+ log_and_print("--- Creating SYLT data from timestamps... ---")
66
+ sylt_data = self._create_sylt_data(word_timestamps)
67
+ if not sylt_data:
68
+ log_and_print("--- WARNING: No SYLT data could be created. Skipping embedding. ---")
69
+ return output_path, log_messages
70
+
71
+ log_and_print(f"--- Created {len(sylt_data)} SYLT entries. ---")
72
+
73
+ # --- Step 3: Embed data into the MP3 file ---
74
+ try:
75
+ log_and_print("--- Loading MP3 file with mutagen... ---")
76
+ audio_file = MP3(output_path, ID3=ID3)
77
+
78
+ if audio_file.tags is None:
79
+ log_and_print("--- No ID3 tags found. Creating new ones. ---")
80
+ audio_file.add_tags()
81
+
82
+ # --- Embed SYLT (Synchronized Lyrics) ---
83
+ log_and_print("--- Creating and adding SYLT frame... ---")
84
+ sylt_frame = SYLT(
85
+ encoding=Encoding.UTF8,
86
+ lang='eng',
87
+ format=2,
88
+ type=1,
89
+ text=sylt_data
90
+ )
91
+ audio_file.tags.delall('SYLT')
92
+ audio_file.tags.add(sylt_frame)
93
+
94
+ # --- Embed USLT (Unsynchronized Lyrics) as a fallback ---
95
+ log_and_print("--- Creating and adding USLT frame... ---")
96
+ uslt_frame = USLT(
97
+ encoding=Encoding.UTF8,
98
+ lang='eng',
99
+ desc='',
100
+ text=text
101
+ )
102
+ audio_file.tags.delall('USLT')
103
+ audio_file.tags.add(uslt_frame)
104
+
105
+ audio_file.save()
106
+ log_and_print("--- Successfully embedded SYLT and USLT frames. ---")
107
+
108
+ except Exception as e:
109
+ log_and_print(f"--- ERROR: Failed to embed SYLT/USLT: {e} ---")
110
+ return output_path, log_messages
111
+
112
+ except Exception as e:
113
+ log_and_print(f"--- ERROR: Unexpected error in embed_sylt_lyrics: {e} ---")
114
+ return output_path, log_messages
115
+
116
+ def _create_sylt_data(self, word_timestamps: List[Dict]) -> List[tuple]:
117
+ """
118
+ Create SYLT data format from word timestamps
119
+
120
+ Args:
121
+ word_timestamps: List of word timestamp dictionaries
122
+
123
+ Returns:
124
+ List of tuples (text, timestamp_in_milliseconds)
125
+ """
126
+ # Debug print to check incoming data
127
+ print(f"DEBUG: word_timestamps received in _create_sylt_data: {word_timestamps}")
128
+ try:
129
+ sylt_data = []
130
+
131
+ for word_data in word_timestamps:
132
+ word = word_data.get('word', '').strip()
133
+ start_time = word_data.get('start', 0)
134
+
135
+ if word:
136
+ # Convert seconds to milliseconds
137
+ timestamp_ms = int(start_time * 1000)
138
+ sylt_data.append((word, timestamp_ms))
139
+
140
+ return sylt_data
141
+
142
+ except Exception as e:
143
+ print(f"Error creating SYLT data: {str(e)}")
144
+ return []
145
+
146
+ def _create_line_based_sylt_data(self, word_timestamps: List[Dict], max_words_per_line: int = 6) -> List[tuple]:
147
+ """
148
+ Create line-based SYLT data (alternative approach)
149
+
150
+ Args:
151
+ word_timestamps: List of word timestamp dictionaries
152
+ max_words_per_line: Maximum words per line
153
+
154
+ Returns:
155
+ List of tuples (line_text, timestamp_in_milliseconds)
156
+ """
157
+ try:
158
+ sylt_data = []
159
+ current_line = []
160
+
161
+ for word_data in word_timestamps:
162
+ current_line.append(word_data)
163
+
164
+ # Check if we should end this line
165
+ if len(current_line) >= max_words_per_line:
166
+ if current_line:
167
+ line_text = ' '.join([w.get('word', '') for w in current_line]).strip()
168
+ start_time = current_line[0].get('start', 0)
169
+ timestamp_ms = int(start_time * 1000)
170
+
171
+ if line_text:
172
+ sylt_data.append((line_text, timestamp_ms))
173
+
174
+ current_line = []
175
+
176
+ # Add remaining words as final line
177
+ if current_line:
178
+ line_text = ' '.join([w.get('word', '') for w in current_line]).strip()
179
+ start_time = current_line[0].get('start', 0)
180
+ timestamp_ms = int(start_time * 1000)
181
+
182
+ if line_text:
183
+ sylt_data.append((line_text, timestamp_ms))
184
+
185
+ return sylt_data
186
+
187
+ except Exception as e:
188
+ print(f"Error creating line-based SYLT data: {str(e)}")
189
+ return []
190
+
191
+ def verify_sylt_embedding(self, mp3_path: str) -> Dict:
192
+ """
193
+ Verify that SYLT lyrics are properly embedded
194
+
195
+ Args:
196
+ mp3_path: Path to the MP3 file
197
+
198
+ Returns:
199
+ Dictionary with verification results
200
+ """
201
+ try:
202
+ audio_file = MP3(mp3_path)
203
+
204
+ result = {
205
+ 'has_sylt': False,
206
+ 'has_uslt': False,
207
+ 'sylt_entries': 0,
208
+ 'error': None
209
+ }
210
+
211
+ if audio_file.tags:
212
+ # Check for SYLT
213
+ sylt_frames = audio_file.tags.getall('SYLT')
214
+ if sylt_frames:
215
+ result['has_sylt'] = True
216
+ result['sylt_entries'] = len(sylt_frames[0].text) if sylt_frames[0].text else 0
217
+
218
+ # Check for USLT (fallback)
219
+ uslt_frames = audio_file.tags.getall('USLT')
220
+ if uslt_frames:
221
+ result['has_uslt'] = True
222
+
223
+ return result
224
+
225
+ except Exception as e:
226
+ return {
227
+ 'has_sylt': False,
228
+ 'has_uslt': False,
229
+ 'sylt_entries': 0,
230
+ 'error': str(e)
231
+ }
232
+
233
+ def extract_sylt_lyrics(self, mp3_path: str) -> List[Dict]:
234
+ """
235
+ Extract SYLT lyrics from an MP3 file (for debugging)
236
+
237
+ Args:
238
+ mp3_path: Path to the MP3 file
239
+
240
+ Returns:
241
+ List of dictionaries with text and timestamp
242
+ """
243
+ try:
244
+ audio_file = MP3(mp3_path)
245
+ lyrics_data = []
246
+
247
+ if audio_file.tags:
248
+ sylt_frames = audio_file.tags.getall('SYLT')
249
+
250
+ for frame in sylt_frames:
251
+ if frame.text:
252
+ for text, timestamp_ms in frame.text:
253
+ lyrics_data.append({
254
+ 'text': text,
255
+ 'timestamp': timestamp_ms / 1000.0 # Convert to seconds
256
+ })
257
+
258
+ return lyrics_data
259
+
260
+ except Exception as e:
261
+ print(f"Error extracting SYLT lyrics: {str(e)}")
262
+ return []
263
+
264
+ def create_lrc_file(self, word_timestamps: List[Dict], output_path: str) -> str:
265
+ """
266
+ Create an LRC (lyrics) file as an additional export option
267
+
268
+ Args:
269
+ word_timestamps: List of word timestamp dictionaries
270
+ output_path: Path for the output LRC file
271
+
272
+ Returns:
273
+ Path to the created LRC file
274
+ """
275
+ try:
276
+ lrc_lines = []
277
+
278
+ # Group words into lines
279
+ current_line = []
280
+ for word_data in word_timestamps:
281
+ current_line.append(word_data)
282
+
283
+ if len(current_line) >= 8: # 8 words per line
284
+ if current_line:
285
+ line_text = ' '.join([w.get('word', '') for w in current_line])
286
+ start_time = current_line[0].get('start', 0)
287
+
288
+ # Format timestamp as [mm:ss.xx]
289
+ minutes = int(start_time // 60)
290
+ seconds = start_time % 60
291
+ timestamp_str = f"[{minutes:02d}:{seconds:05.2f}]"
292
+
293
+ lrc_lines.append(f"{timestamp_str}{line_text}")
294
+ current_line = []
295
+
296
+ # Add remaining words
297
+ if current_line:
298
+ line_text = ' '.join([w.get('word', '') for w in current_line])
299
+ start_time = current_line[0].get('start', 0)
300
+
301
+ minutes = int(start_time // 60)
302
+ seconds = start_time % 60
303
+ timestamp_str = f"[{minutes:02d}:{seconds:05.2f}]"
304
+
305
+ lrc_lines.append(f"{timestamp_str}{line_text}")
306
+
307
+ # Write LRC file
308
+ with open(output_path, 'w', encoding='utf-8') as f:
309
+ f.write('\n'.join(lrc_lines))
310
+
311
+ return output_path
312
+
313
+ except Exception as e:
314
+ raise Exception(f"Error creating LRC file: {str(e)}")
315
+
316
+ def __del__(self):
317
+ """Clean up temporary files"""
318
+ import shutil
319
+ if hasattr(self, 'temp_dir') and os.path.exists(self.temp_dir):
320
+ try:
321
+ shutil.rmtree(self.temp_dir)
322
+ except:
323
+ pass
package-lock.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "syncmaster",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "syncmaster",
9
+ "version": "0.1.0"
10
+ }
11
+ }
12
+ }
package.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "syncmaster",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "AI Audio-Text Synchronization Platform โ€“ convenience wrapper for Streamlit dev server with integrated recorder",
6
+ "scripts": {
7
+ "dev": "streamlit run app.py --server.port 5050 --server.address localhost",
8
+ "start": "streamlit run app.py --server.port 5050 --server.address 0.0.0.0",
9
+ "dev-launcher": "streamlit run app_launcher.py --server.port 5050 --server.address localhost",
10
+ "dev-separate": "python startup.py",
11
+ "build": "echo 'Installing Python dependencies...' && pip install -r requirements.txt"
12
+ },
13
+ "dependencies": {
14
+ "streamlit": "^1.28.0"
15
+ },
16
+ "keywords": ["ai", "audio", "transcription", "synchronization", "streamlit"],
17
+ "author": "SyncMaster Team",
18
+ "license": "MIT"
19
+ }
packages.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ ffmpeg
2
+ libavcodec-extra
3
+ libavformat-dev
4
+ libavutil-dev
5
+ libmp3lame0
performance_test.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Performance Test for SyncMaster
3
+ ุงุฎุชุจุงุฑ ุฃุฏุงุก ุงู„ุชุทุจูŠู‚
4
+ """
5
+
6
+ import time
7
+ import requests
8
+ import threading
9
+
10
+ def test_load_time(url, test_name):
11
+ """Test page load time"""
12
+ try:
13
+ start = time.time()
14
+ response = requests.get(url, timeout=10)
15
+ end = time.time()
16
+
17
+ load_time = end - start
18
+ status = response.status_code
19
+
20
+ print(f"๐Ÿงช {test_name}:")
21
+ print(f" โฑ๏ธ Load Time: {load_time:.3f} seconds")
22
+ print(f" ๐Ÿ“Š Status: {status}")
23
+ print(f" โœ… {'FAST' if load_time < 0.5 else 'SLOW' if load_time > 1.0 else 'OK'}")
24
+ print()
25
+
26
+ return load_time, status
27
+ except Exception as e:
28
+ print(f"โŒ {test_name} failed: {e}")
29
+ return None, None
30
+
31
+ def run_performance_tests():
32
+ """Run comprehensive performance tests"""
33
+ print("๐Ÿš€ SyncMaster Performance Test")
34
+ print("=" * 40)
35
+
36
+ # Test multiple requests to see consistency
37
+ tests = [
38
+ ("First Load", "http://localhost:5050"),
39
+ ("Second Load", "http://localhost:5050"),
40
+ ("Third Load", "http://localhost:5050"),
41
+ ("Recorder API", "http://localhost:5001/record")
42
+ ]
43
+
44
+ results = []
45
+ for test_name, url in tests:
46
+ load_time, status = test_load_time(url, test_name)
47
+ if load_time:
48
+ results.append(load_time)
49
+ time.sleep(0.5) # Small delay between tests
50
+
51
+ if results:
52
+ avg_time = sum(results) / len(results)
53
+ print(f"๐Ÿ“Š Average Load Time: {avg_time:.3f} seconds")
54
+ print(f"๐ŸŽฏ Performance Rating: {'EXCELLENT' if avg_time < 0.2 else 'GOOD' if avg_time < 0.5 else 'NEEDS IMPROVEMENT'}")
55
+
56
+ return results
57
+
58
+ if __name__ == "__main__":
59
+ run_performance_tests()
pyproject.toml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "repl-nix-workspace"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "google-genai>=1.23.0",
8
+ "librosa>=0.11.0",
9
+ "moviepy>=2.2.1",
10
+ "mutagen>=1.47.0",
11
+ "numpy>=2.2.6",
12
+ "openai>=1.93.0",
13
+ "sift-stack-py>=0.7.0",
14
+ "streamlit>=1.46.1",
15
+ ]
recorder_server.py ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ import os
3
+ import tempfile
4
+ from pathlib import Path
5
+ from audio_processor import AudioProcessor
6
+ import logging
7
+ import json
8
+ import traceback
9
+ from flask_cors import CORS
10
+ import soundfile as sf
11
+ import librosa
12
+ from translator import get_translator, get_translation
13
+ from database import NotesDatabase
14
+ from summarizer import LectureSummarizer
15
+
16
+ app = Flask(__name__)
17
+ # ุฅุนุฏุงุฏ CORS ุจุดูƒู„ ุตุญูŠุญ ู„ุชุฌู†ุจ ุชูƒุฑุงุฑ headers
18
+ CORS(app,
19
+ origins="*",
20
+ methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
21
+ allow_headers=['Content-Type', 'Authorization']
22
+ )
23
+
24
+ # Configure logging
25
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
26
+
27
+ # Initialize database and summarizer
28
+ db = NotesDatabase()
29
+ summarizer = LectureSummarizer()
30
+
31
+ @app.route('/record', methods=['GET', 'POST', 'OPTIONS'])
32
+ def record():
33
+ if request.method == 'OPTIONS':
34
+ # Handle CORS preflight request - flask-cors ุณุชุชูˆู„ู‰ headers
35
+ return '', 204
36
+
37
+ if request.method == 'GET':
38
+ logging.info("--- RECORD ENDPOINT: Health check request received. ---")
39
+ return jsonify({'status': 'ok', 'message': 'Recorder server is running.'})
40
+
41
+ logging.info("--- RECORD ENDPOINT: POST request received. ---")
42
+ logging.info(f"Request Headers: {request.headers}")
43
+
44
+ if 'audio_data' not in request.files:
45
+ logging.error("--- RECORD ENDPOINT: 'audio_data' not in request files. ---")
46
+ return jsonify({'success': False, 'error': 'No audio file found'})
47
+
48
+ audio_file = request.files['audio_data']
49
+ markers = json.loads(request.form.get('markers', '[]'))
50
+ target_language = request.form.get('target_language', 'ar') # Default to Arabic
51
+ enable_translation = request.form.get('enable_translation', 'true').lower() == 'true'
52
+
53
+ print(f"๐Ÿ” RECORD ENDPOINT DEBUG:")
54
+ print(f"- enable_translation: {enable_translation}")
55
+ print(f"- target_language: {target_language}")
56
+
57
+ logging.info(f"--- RECORD ENDPOINT: Received file: {audio_file.filename} ---")
58
+ logging.info(f"--- RECORD ENDPOINT: Target language: {target_language} ---")
59
+ logging.info(f"--- RECORD ENDPOINT: Translation enabled: {enable_translation} ---")
60
+
61
+ if markers:
62
+ logging.info(f"--- RECORD ENDPOINT: Received {len(markers)} markers: {markers} ---")
63
+
64
+ tmp_file_path = None
65
+ try:
66
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
67
+ audio_file.save(tmp_file.name)
68
+ tmp_file_path = tmp_file.name
69
+ logging.info(f"--- RECORD ENDPOINT: Audio saved to temporary file: {tmp_file_path} ---")
70
+
71
+ # --- File Standardization Step ---
72
+ try:
73
+ logging.info(f"--- RECORD ENDPOINT: Standardizing audio file: {tmp_file_path} ---")
74
+ y, sr = librosa.load(tmp_file_path, sr=None, mono=True)
75
+ sf.write(tmp_file_path, y, sr)
76
+ logging.info(f"--- RECORD ENDPOINT: Audio file successfully standardized. ---")
77
+ except Exception as e:
78
+ logging.warning(f"--- RECORD ENDPOINT: Could not standardize audio file, proceeding with original. Error: {e} ---")
79
+ # --- End Standardization ---
80
+
81
+ processor = AudioProcessor()
82
+
83
+ # Choose processing method based on translation requirement
84
+ if enable_translation:
85
+ logging.info("--- RECORD ENDPOINT: Calling enhanced processor with translation ---")
86
+ result_data, processor_logs = processor.get_word_timestamps_with_translation(
87
+ tmp_file_path, target_language
88
+ )
89
+
90
+ for log_msg in processor_logs:
91
+ logging.info(f"--- AUDIO_PROCESSOR LOG: {log_msg} ---")
92
+
93
+ if not result_data or not result_data.get('original_text'):
94
+ logging.error("--- RECORD ENDPOINT: Failed to generate transcription with translation. ---")
95
+ return jsonify({
96
+ 'success': False,
97
+ 'error': 'Could not generate transcription with translation.',
98
+ 'logs': processor_logs
99
+ })
100
+
101
+ # Prepare enhanced response with translation
102
+ full_text = result_data['original_text']
103
+ translated_text = result_data['translated_text']
104
+ word_timestamps = result_data['word_timestamps']
105
+ translated_timestamps = result_data.get('translated_timestamps', [])
106
+
107
+ logging.info(f"--- RECORD ENDPOINT: Original text: '{full_text[:100]}...'")
108
+ logging.info(f"--- RECORD ENDPOINT: Translated text: '{translated_text[:100]}...'")
109
+
110
+ else:
111
+ # Standard processing without translation
112
+ logging.info("--- RECORD ENDPOINT: Calling standard audio_processor.get_word_timestamps ---")
113
+ word_timestamps, processor_logs = processor.get_word_timestamps(tmp_file_path)
114
+
115
+ for log_msg in processor_logs:
116
+ logging.info(f"--- AUDIO_PROCESSOR LOG: {log_msg} ---")
117
+
118
+ if not word_timestamps:
119
+ logging.error("--- RECORD ENDPOINT: Failed to generate timestamps. ---")
120
+ return jsonify({
121
+ 'success': False,
122
+ 'error': 'Could not generate timestamps.',
123
+ 'logs': processor_logs
124
+ })
125
+
126
+ full_text = " ".join([d['word'] for d in word_timestamps])
127
+ translated_text = full_text # No translation requested
128
+ translated_timestamps = word_timestamps
129
+ result_data = {
130
+ 'original_text': full_text,
131
+ 'translated_text': translated_text,
132
+ 'word_timestamps': word_timestamps,
133
+ 'translated_timestamps': translated_timestamps,
134
+ 'translation_success': not enable_translation
135
+ }
136
+
137
+ logging.info(f"--- RECORD ENDPOINT: Transcription successful. Text: '{full_text[:100]}...'")
138
+
139
+ # Read audio file for storage
140
+ with open(tmp_file_path, 'rb') as f:
141
+ audio_bytes = f.read()
142
+
143
+ # Prepare comprehensive transcription data
144
+ transcription_data = {
145
+ 'original_text': result_data['original_text'],
146
+ 'translated_text': result_data['translated_text'],
147
+ 'word_timestamps': result_data['word_timestamps'],
148
+ 'translated_timestamps': result_data.get('translated_timestamps', []),
149
+ 'audio_bytes': audio_bytes.hex(),
150
+ 'original_suffix': '.wav',
151
+ 'markers': markers,
152
+ 'target_language': target_language,
153
+ 'translation_enabled': enable_translation,
154
+ 'translation_success': result_data.get('translation_success', False),
155
+ 'language_detected': result_data.get('language_detected', 'unknown')
156
+ }
157
+
158
+ # Save comprehensive data to file
159
+ result_file_path = ""
160
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".json", dir=".", encoding='utf-8') as json_file:
161
+ json.dump(transcription_data, json_file, ensure_ascii=False, indent=2)
162
+ result_file_path = json_file.name
163
+ logging.info(f"--- RECORD ENDPOINT: Enhanced transcription data saved to {result_file_path} ---")
164
+
165
+ # Prepare successful response
166
+ response_data = {
167
+ 'success': True,
168
+ 'original_text': result_data['original_text'],
169
+ 'translated_text': result_data['translated_text'],
170
+ 'file_path': result_file_path,
171
+ 'markers': markers,
172
+ 'target_language': target_language,
173
+ 'translation_enabled': enable_translation,
174
+ 'translation_success': result_data.get('translation_success', False),
175
+ 'language_detected': result_data.get('language_detected', 'unknown')
176
+ }
177
+
178
+ print(f"๐Ÿ” DEBUG RESPONSE DATA:")
179
+ print(f"- success: {response_data['success']}")
180
+ print(f"- original_text: {response_data['original_text']}")
181
+ print(f"- translated_text: {response_data['translated_text']}")
182
+ print(f"- target_language: {response_data['target_language']}")
183
+ print(f"- translation_enabled: {response_data['translation_enabled']}")
184
+ print(f"- translation_success: {response_data['translation_success']}")
185
+ print(f"- language_detected: {response_data['language_detected']}")
186
+
187
+ logging.info(f"--- RECORD ENDPOINT: Sending enhanced response with translation support ---")
188
+ return jsonify(response_data)
189
+
190
+ except Exception as e:
191
+ logging.error(f"--- RECORD ENDPOINT FATAL ERROR: {traceback.format_exc()} ---")
192
+ return jsonify({'success': False, 'error': str(e), 'trace': traceback.format_exc()}), 500
193
+ finally:
194
+ if tmp_file_path and os.path.exists(tmp_file_path):
195
+ os.unlink(tmp_file_path)
196
+
197
+ @app.route('/translate', methods=['POST', 'OPTIONS'])
198
+ def translate_text():
199
+ """Endpoint for standalone text translation"""
200
+ if request.method == 'OPTIONS':
201
+ # flask-cors ุณุชุชูˆู„ู‰ ู…ุนุงู„ุฌุฉ CORS headers
202
+ return '', 204
203
+
204
+ try:
205
+ data = request.get_json()
206
+ if not data or 'text' not in data:
207
+ return jsonify({'success': False, 'error': 'No text provided for translation'})
208
+
209
+ text = data['text']
210
+ target_language = data.get('target_language', 'ar')
211
+ source_language = data.get('source_language', 'auto')
212
+
213
+ logging.info(f"--- TRANSLATE ENDPOINT: Translating text to {target_language} ---")
214
+
215
+ translator = get_translator()
216
+ if not translator:
217
+ return jsonify({'success': False, 'error': 'Translation service not available'})
218
+
219
+ translated_text, error = translator.translate_text(text, target_language, source_language)
220
+
221
+ if translated_text:
222
+ # Detect source language if auto
223
+ detected_lang = 'unknown'
224
+ if source_language == 'auto':
225
+ detected_lang, _ = translator.detect_language(text)
226
+
227
+ response_data = {
228
+ 'success': True,
229
+ 'original_text': text,
230
+ 'translated_text': translated_text,
231
+ 'source_language': detected_lang if source_language == 'auto' else source_language,
232
+ 'target_language': target_language
233
+ }
234
+ logging.info(f"--- TRANSLATE ENDPOINT: Translation successful ---")
235
+ return jsonify(response_data)
236
+ else:
237
+ logging.error(f"--- TRANSLATE ENDPOINT: Translation failed: {error} ---")
238
+ return jsonify({'success': False, 'error': error})
239
+
240
+ except Exception as e:
241
+ logging.error(f"--- TRANSLATE ENDPOINT ERROR: {traceback.format_exc()} ---")
242
+ return jsonify({'success': False, 'error': str(e)}), 500
243
+
244
+ @app.route('/languages', methods=['GET'])
245
+ def get_supported_languages():
246
+ """Get list of supported languages"""
247
+ try:
248
+ translator = get_translator()
249
+ if translator:
250
+ languages = translator.get_supported_languages()
251
+ return jsonify({'success': True, 'languages': languages})
252
+ else:
253
+ return jsonify({'success': False, 'error': 'Translation service not available'})
254
+ except Exception as e:
255
+ return jsonify({'success': False, 'error': str(e)}), 500
256
+
257
+ @app.route('/ui-translations/<language>', methods=['GET'])
258
+ def get_ui_translations(language):
259
+ """Get UI translations for a specific language"""
260
+ try:
261
+ from translator import UI_TRANSLATIONS
262
+ translations = UI_TRANSLATIONS.get(language, UI_TRANSLATIONS['en'])
263
+ return jsonify({'success': True, 'translations': translations, 'language': language})
264
+ except Exception as e:
265
+ return jsonify({'success': False, 'error': str(e)}), 500
266
+
267
+ # ======= ูˆุธุงุฆู ุงู„ู…ู„ุฎุต ูˆุงู„ู…ู„ุงุญุธุงุช ุงู„ุฌุฏูŠุฏุฉ =======
268
+
269
+ @app.route('/summarize', methods=['POST', 'OPTIONS'])
270
+ def summarize_text():
271
+ """ุฅู†ุดุงุก ู…ู„ุฎุต ุฐูƒูŠ ู…ู† ุงู„ู†ุต"""
272
+ logging.info(f"--- SUMMARIZE ENDPOINT: {request.method} request received ---")
273
+ logging.info(f"--- SUMMARIZE ENDPOINT: Origin: {request.headers.get('Origin', 'None')} ---")
274
+ logging.info(f"--- SUMMARIZE ENDPOINT: Headers: {dict(request.headers)} ---")
275
+
276
+ if request.method == 'OPTIONS':
277
+ logging.info("--- SUMMARIZE ENDPOINT: Handling OPTIONS preflight request ---")
278
+ # ู„ุง ุญุงุฌุฉ ู„ุฅุนุฏุงุฏ headers ูŠุฏูˆูŠุงู‹ุŒ flask-cors ุณุชุชูˆู„ู‰ ุงู„ุฃู…ุฑ
279
+ return '', 204
280
+
281
+ try:
282
+ data = request.get_json()
283
+ if not data or 'text' not in data:
284
+ return jsonify({'success': False, 'error': 'ู„ู… ูŠุชู… ุชูˆููŠุฑ ู†ุต ู„ู„ุชู„ุฎูŠุต'})
285
+
286
+ text = data['text']
287
+ language = data.get('language', 'ar')
288
+ subject = data.get('subject', '')
289
+ summary_type = data.get('type', 'full') # full, key_points, questions
290
+
291
+ logging.info(f"--- SUMMARIZE ENDPOINT: Creating {summary_type} summary ---")
292
+
293
+ if summary_type == 'key_points':
294
+ result, error = summarizer.extract_key_points(text, language)
295
+ result_key = 'key_points'
296
+ elif summary_type == 'questions':
297
+ result, error = summarizer.generate_review_questions(text, language)
298
+ result_key = 'questions'
299
+ elif summary_type == 'study_notes':
300
+ result, error = summarizer.generate_study_notes(text, subject, language)
301
+ result_key = 'study_notes'
302
+ else: # full summary
303
+ result, error = summarizer.generate_summary(text, language)
304
+ result_key = 'summary'
305
+
306
+ if result:
307
+ response_data = {
308
+ 'success': True,
309
+ result_key: result,
310
+ 'type': summary_type,
311
+ 'language': language
312
+ }
313
+ logging.info(f"--- SUMMARIZE ENDPOINT: {summary_type} summary created successfully ---")
314
+ return jsonify(response_data)
315
+ else:
316
+ logging.error(f"--- SUMMARIZE ENDPOINT: Failed to create {summary_type}: {error} ---")
317
+ return jsonify({'success': False, 'error': error})
318
+
319
+ except Exception as e:
320
+ logging.error(f"--- SUMMARIZE ENDPOINT ERROR: {traceback.format_exc()} ---")
321
+ return jsonify({'success': False, 'error': str(e)}), 500
322
+
323
+ @app.route('/test-summarize', methods=['GET'])
324
+ def test_summarize():
325
+ """ุงุฎุชุจุงุฑ ุจุณูŠุท ู„ู€ summarize endpoint"""
326
+ return jsonify({
327
+ 'success': True,
328
+ 'message': 'Summarize endpoint is working!',
329
+ 'test_summary': {
330
+ 'main_summary': 'This is a test summary',
331
+ 'key_points': ['Point 1', 'Point 2', 'Point 3'],
332
+ 'review_questions': ['Question 1?', 'Question 2?'],
333
+ 'study_notes': 'These are test study notes'
334
+ }
335
+ })
336
+
337
+ @app.route('/notes', methods=['GET', 'POST', 'OPTIONS'])
338
+ def handle_notes():
339
+ """ุงู„ุชุนุงู…ู„ ู…ุน ุงู„ู…ู„ุงุญุธุงุช - ุนุฑุถ ูˆุญูุธ"""
340
+ if request.method == 'OPTIONS':
341
+ # flask-cors ุณุชุชูˆู„ู‰ ู…ุนุงู„ุฌุฉ CORS headers
342
+ return '', 204
343
+
344
+ if request.method == 'GET':
345
+ try:
346
+ # ุงู„ุจุญุซ ุฃูˆ ุนุฑุถ ุฌู…ูŠุน ุงู„ู…ู„ุงุญุธุงุช
347
+ search_query = request.args.get('search', '')
348
+ subject = request.args.get('subject', '')
349
+ limit = int(request.args.get('limit', 20))
350
+
351
+ if search_query:
352
+ notes = db.search_notes(search_query)
353
+ elif subject:
354
+ notes = db.get_notes_by_subject(subject)
355
+ else:
356
+ notes = db.get_all_notes(limit)
357
+
358
+ # ุฅุญุตุงุฆูŠุงุช ุณุฑูŠุนุฉ
359
+ subjects = db.get_subjects()
360
+
361
+ response = jsonify({
362
+ 'success': True,
363
+ 'notes': notes,
364
+ 'subjects': subjects,
365
+ 'total': len(notes)
366
+ })
367
+ return response
368
+
369
+ except Exception as e:
370
+ response = jsonify({'success': False, 'error': str(e)})
371
+ return response, 500
372
+
373
+ if request.method == 'POST':
374
+ try:
375
+ data = request.get_json()
376
+
377
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุจูŠุงู†ุงุช ุงู„ู…ุทู„ูˆุจุฉ
378
+ if not data or 'original_text' not in data:
379
+ return jsonify({'success': False, 'error': 'ุงู„ุจูŠุงู†ุงุช ุบูŠุฑ ู…ูƒุชู…ู„ุฉ'})
380
+
381
+ # ุญูุธ ุงู„ู…ู„ุงุญุธุฉ
382
+ note_id = db.save_lecture_note(data)
383
+
384
+ logging.info(f"--- NOTES ENDPOINT: Note saved with ID {note_id} ---")
385
+
386
+ response = jsonify({
387
+ 'success': True,
388
+ 'note_id': note_id,
389
+ 'message': 'ุชู… ุญูุธ ุงู„ู…ู„ุงุญุธุฉ ุจู†ุฌุงุญ'
390
+ })
391
+ return response
392
+
393
+ except Exception as e:
394
+ logging.error(f"--- NOTES ENDPOINT ERROR: {traceback.format_exc()} ---")
395
+ response = jsonify({'success': False, 'error': str(e)})
396
+ return response, 500
397
+
398
+ @app.route('/notes/<int:note_id>', methods=['GET', 'PUT', 'DELETE', 'OPTIONS'])
399
+ def handle_single_note(note_id):
400
+ """ุงู„ุชุนุงู…ู„ ู…ุน ู…ู„ุงุญุธุฉ ูˆุงุญุฏุฉ - ุนุฑุถุŒ ุชุญุฏูŠุซุŒ ุญุฐู"""
401
+ if request.method == 'OPTIONS':
402
+ # flask-cors ุณุชุชูˆู„ู‰ ู…ุนุงู„ุฌุฉ CORS headers
403
+ return '', 204
404
+
405
+ if request.method == 'GET':
406
+ try:
407
+ note = db.get_note_by_id(note_id)
408
+ if note:
409
+ return jsonify({'success': True, 'note': note})
410
+ else:
411
+ return jsonify({'success': False, 'error': 'ุงู„ู…ู„ุงุญุธุฉ ุบูŠุฑ ู…ูˆุฌูˆุฏุฉ'}), 404
412
+
413
+ except Exception as e:
414
+ return jsonify({'success': False, 'error': str(e)}), 500
415
+
416
+ if request.method == 'PUT':
417
+ try:
418
+ data = request.get_json()
419
+ if not data:
420
+ return jsonify({'success': False, 'error': 'ู„ู… ูŠุชู… ุชูˆููŠุฑ ุจูŠุงู†ุงุช ู„ู„ุชุญุฏูŠุซ'})
421
+
422
+ success = db.update_note(note_id, data)
423
+ if success:
424
+ return jsonify({'success': True, 'message': 'ุชู… ุชุญุฏูŠุซ ุงู„ู…ู„ุงุญุธุฉ ุจู†ุฌุงุญ'})
425
+ else:
426
+ return jsonify({'success': False, 'error': 'ูุดู„ ููŠ ุชุญุฏูŠุซ ุงู„ู…ู„ุงุญุธุฉ'}), 400
427
+
428
+ except Exception as e:
429
+ return jsonify({'success': False, 'error': str(e)}), 500
430
+
431
+ if request.method == 'DELETE':
432
+ try:
433
+ success = db.delete_note(note_id)
434
+ if success:
435
+ return jsonify({'success': True, 'message': 'ุชู… ุญุฐู ุงู„ู…ู„ุงุญุธุฉ ุจู†ุฌุงุญ'})
436
+ else:
437
+ return jsonify({'success': False, 'error': 'ูุดู„ ููŠ ุญุฐู ุงู„ู…ู„ุงุญุธุฉ'}), 400
438
+
439
+ except Exception as e:
440
+ return jsonify({'success': False, 'error': str(e)}), 500
441
+
442
+ @app.route('/notes/subjects', methods=['GET'])
443
+ def get_subjects():
444
+ """ุงุณุชุฑุฌุงุน ู‚ุงุฆู…ุฉ ุงู„ู…ูˆุงุฏ ุงู„ุฏุฑุงุณูŠุฉ"""
445
+ try:
446
+ subjects = db.get_subjects()
447
+ return jsonify({'success': True, 'subjects': subjects})
448
+ except Exception as e:
449
+ return jsonify({'success': False, 'error': str(e)}), 500
450
+
451
+ if __name__ == '__main__':
452
+ app.run(port=5001)
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ google-generativeai>=0.8.0
3
+ librosa>=0.10.0
4
+ moviepy>=1.0.3
5
+ mutagen>=1.47.0
6
+ numpy>=1.24.0
7
+ streamlit>=1.28.0
8
+ altair>=5.0.0
9
+ python-dotenv>=1.0.0
10
+ Flask>=2.3.0
11
+ Flask-Cors>=4.0.0
12
+ soundfile>=0.12.0
13
+ # Additional dependencies for enhanced translation features
14
+ requests>=2.31.0
15
+ typing-extensions>=4.7.0
setup_enhanced.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SyncMaster Enhanced Setup Script
2
+ # ุชุดุบูŠู„ SyncMaster ุงู„ู…ุญุณู† ู…ุน ุฏุนู… ุงู„ุชุฑุฌู…ุฉ
3
+
4
+ import subprocess
5
+ import sys
6
+ import os
7
+ import time
8
+ from pathlib import Path
9
+
10
+ def print_header():
11
+ print("=" * 60)
12
+ print("๐ŸŽต SyncMaster Enhanced - AI-Powered Translation Setup")
13
+ print("ู…ู†ุตุฉ ุงู„ู…ุฒุงู…ู†ุฉ ุงู„ุฐูƒูŠุฉ ู…ุน ุฏุนู… ุงู„ุชุฑุฌู…ุฉ ุจุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ")
14
+ print("=" * 60)
15
+
16
+ def check_python_version():
17
+ """Check if Python version is compatible"""
18
+ if sys.version_info < (3, 8):
19
+ print("โŒ Error: Python 3.8 or higher is required.")
20
+ print("ุฎุทุฃ: ูŠุชุทู„ุจ Python 3.8 ุฃูˆ ุฃุญุฏุซ")
21
+ return False
22
+ print(f"โœ… Python version: {sys.version}")
23
+ return True
24
+
25
+ def install_requirements():
26
+ """Install required packages"""
27
+ print("\n๐Ÿ“ฆ Installing required packages...")
28
+ print("ุชุซุจูŠุช ุงู„ุญุฒู… ุงู„ู…ุทู„ูˆุจุฉ...")
29
+
30
+ try:
31
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
32
+ print("โœ… All packages installed successfully!")
33
+ print("ุชู… ุชุซุจูŠุช ุฌู…ูŠุน ุงู„ุญุฒู… ุจู†ุฌุงุญ!")
34
+ return True
35
+ except subprocess.CalledProcessError as e:
36
+ print(f"โŒ Error installing packages: {e}")
37
+ print(f"ุฎุทุฃ ููŠ ุชุซุจูŠุช ุงู„ุญุฒู…: {e}")
38
+ return False
39
+
40
+ def check_env_file():
41
+ """Check if .env file exists and has required keys"""
42
+ env_path = Path(".env")
43
+ if not env_path.exists():
44
+ print("โŒ .env file not found!")
45
+ print("ู…ู„ู .env ุบูŠุฑ ู…ูˆุฌูˆุฏ!")
46
+ create_env_file()
47
+ return False
48
+
49
+ with open(env_path, 'r') as f:
50
+ content = f.read()
51
+ if "GEMINI_API_KEY" not in content:
52
+ print("โŒ GEMINI_API_KEY not found in .env file!")
53
+ print("ู…ูุชุงุญ GEMINI_API_KEY ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ู…ู„ู .env!")
54
+ return False
55
+
56
+ print("โœ… Environment file configured correctly!")
57
+ print("ู…ู„ู ุงู„ุจูŠุฆุฉ ู…ูุนุฏู‘ ุจุดูƒู„ ุตุญูŠุญ!")
58
+ return True
59
+
60
+ def create_env_file():
61
+ """Create a template .env file"""
62
+ print("\n๐Ÿ“ Creating .env template file...")
63
+ print("ุฅู†ุดุงุก ู…ู„ู ู‚ุงู„ุจ .env...")
64
+
65
+ env_content = """# SyncMaster Configuration
66
+ # ุฅุนุฏุงุฏุงุช SyncMaster
67
+
68
+ # Gemini AI API Key (Required for transcription and translation)
69
+ # ู…ูุชุงุญ Gemini AI (ู…ุทู„ูˆุจ ู„ู„ู†ุณุฎ ูˆุงู„ุชุฑุฌู…ุฉ)
70
+ GEMINI_API_KEY=your_gemini_api_key_here
71
+
72
+ # Optional: Set default language (en/ar)
73
+ # ุงุฎุชูŠุงุฑูŠ: ุชุนูŠูŠู† ุงู„ู„ุบุฉ ุงู„ุงูุชุฑุงุถูŠุฉ
74
+ DEFAULT_LANGUAGE=en
75
+
76
+ # Optional: Enable translation by default
77
+ # ุงุฎุชูŠุงุฑูŠ: ุชูุนูŠู„ ุงู„ุชุฑุฌู…ุฉ ุงูุชุฑุงุถูŠุงู‹
78
+ ENABLE_TRANSLATION=true
79
+
80
+ # Optional: Default target language for translation
81
+ # ุงุฎุชูŠุงุฑูŠ: ุงู„ู„ุบุฉ ุงู„ู…ุณุชู‡ุฏูุฉ ู„ู„ุชุฑุฌู…ุฉ ุงูุชุฑุงุถูŠุงู‹
82
+ DEFAULT_TARGET_LANGUAGE=ar
83
+ """
84
+
85
+ with open(".env", "w", encoding="utf-8") as f:
86
+ f.write(env_content)
87
+
88
+ print("โœ… .env template created!")
89
+ print("ุชู… ุฅู†ุดุงุก ู‚ุงู„ุจ .env!")
90
+ print("\n๐Ÿ”‘ Please edit .env file and add your Gemini API key:")
91
+ print("ูŠุฑุฌู‰ ุชุญุฑูŠุฑ ู…ู„ู .env ูˆุฅุถุงูุฉ ู…ูุชุงุญ Gemini API ุงู„ุฎุงุต ุจูƒ:")
92
+ print("GEMINI_API_KEY=your_actual_api_key")
93
+
94
+ def start_recorder_server():
95
+ """Start the Flask recorder server"""
96
+ print("\n๐Ÿš€ Starting recorder server...")
97
+ print("ุจุฏุก ุชุดุบูŠู„ ุฎุงุฏู… ุงู„ุชุณุฌูŠู„...")
98
+
99
+ try:
100
+ # Start recorder server in background
101
+ process = subprocess.Popen([
102
+ sys.executable, "recorder_server.py"
103
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
104
+
105
+ print("โœ… Recorder server started on http://localhost:5001")
106
+ print("ุชู… ุจุฏุก ุชุดุบูŠู„ ุฎุงุฏู… ุงู„ุชุณุฌูŠู„ ุนู„ู‰ http://localhost:5001")
107
+ return process
108
+ except Exception as e:
109
+ print(f"โŒ Error starting recorder server: {e}")
110
+ print(f"ุฎุทุฃ ููŠ ุจุฏุก ุชุดุบูŠู„ ุฎุงุฏู… ุงู„ุชุณุฌูŠู„: {e}")
111
+ return None
112
+
113
+ def start_streamlit_app():
114
+ """Start the main Streamlit application"""
115
+ print("\n๐ŸŒŸ Starting SyncMaster main application...")
116
+ print("ุจุฏุก ุชุดุบูŠู„ ุชุทุจูŠู‚ SyncMaster ุงู„ุฑุฆูŠุณูŠ...")
117
+
118
+ try:
119
+ subprocess.run([
120
+ sys.executable, "-m", "streamlit", "run", "app.py",
121
+ "--server.port", "8501",
122
+ "--server.address", "localhost"
123
+ ])
124
+ except KeyboardInterrupt:
125
+ print("\n๐Ÿ‘‹ Application stopped by user.")
126
+ print("ุชู… ุฅูŠู‚ุงู ุงู„ุชุทุจูŠู‚ ุจูˆุงุณุทุฉ ุงู„ู…ุณุชุฎุฏู….")
127
+ except Exception as e:
128
+ print(f"โŒ Error starting Streamlit app: {e}")
129
+ print(f"ุฎุทุฃ ููŠ ุจุฏุก ุชุดุบูŠู„ ุชุทุจูŠู‚ Streamlit: {e}")
130
+
131
+ def print_usage_instructions():
132
+ """Print usage instructions"""
133
+ print("\n๐Ÿ“– Usage Instructions / ุชุนู„ูŠู…ุงุช ุงู„ุงุณุชุฎุฏุงู…:")
134
+ print("-" * 40)
135
+ print("1. Open your browser and go to: http://localhost:8501")
136
+ print(" ุงูุชุญ ู…ุชุตูุญูƒ ูˆุงุฐู‡ุจ ุฅู„ู‰: http://localhost:8501")
137
+ print("\n2. For recording, the recorder interface is at: http://localhost:5001")
138
+ print(" ู„ู„ุชุณุฌูŠู„ุŒ ูˆุงุฌู‡ุฉ ุงู„ุชุณุฌูŠู„ ู…ุชุงุญุฉ ุนู„ู‰: http://localhost:5001")
139
+ print("\n3. Choose your language (English/ุงู„ุนุฑุจูŠุฉ) from the sidebar")
140
+ print(" ุงุฎุชุฑ ู„ุบุชูƒ (English/ุงู„ุนุฑุจูŠุฉ) ู…ู† ุงู„ุดุฑูŠุท ุงู„ุฌุงู†ุจูŠ")
141
+ print("\n4. Enable translation and select target language")
142
+ print(" ูุนู‘ู„ ุงู„ุชุฑุฌู…ุฉ ูˆุงุฎุชุฑ ุงู„ู„ุบุฉ ุงู„ู…ุณุชู‡ุฏูุฉ")
143
+ print("\n5. Upload audio or record directly from microphone")
144
+ print(" ุงุฑูุน ู…ู„ู ุตูˆุชูŠ ุฃูˆ ุณุฌู„ ู…ุจุงุดุฑุฉ ู…ู† ุงู„ู…ูŠูƒุฑูˆููˆู†")
145
+ print("\n๐Ÿ“š For detailed instructions, see README_AR.md")
146
+ print("ู„ู„ุชุนู„ูŠู…ุงุช ุงู„ู…ูุตู„ุฉุŒ ุฑุงุฌุน ู…ู„ู README_AR.md")
147
+
148
+ def main():
149
+ """Main setup function"""
150
+ print_header()
151
+
152
+ # Check Python version
153
+ if not check_python_version():
154
+ return
155
+
156
+ # Install requirements
157
+ if not install_requirements():
158
+ return
159
+
160
+ # Check environment configuration
161
+ if not check_env_file():
162
+ print("\nโš ๏ธ Please configure your .env file before running the application.")
163
+ print("ูŠุฑุฌู‰ ุฅุนุฏุงุฏ ู…ู„ู .env ู‚ุจู„ ุชุดุบูŠู„ ุงู„ุชุทุจูŠู‚.")
164
+ return
165
+
166
+ print("\n๐ŸŽ‰ Setup completed successfully!")
167
+ print("ุชู… ุงู„ุฅุนุฏุงุฏ ุจู†ุฌุงุญ!")
168
+
169
+ print_usage_instructions()
170
+
171
+ # Ask user if they want to start the application
172
+ print("\n" + "=" * 60)
173
+ start_now = input("Start SyncMaster now? (y/n) / ุชุดุบูŠู„ SyncMaster ุงู„ุขู†ุŸ (y/n): ").lower().strip()
174
+
175
+ if start_now in ['y', 'yes', 'ู†ุนู…']:
176
+ # Start recorder server
177
+ recorder_process = start_recorder_server()
178
+
179
+ # Wait a moment for server to start
180
+ time.sleep(2)
181
+
182
+ # Start main application
183
+ try:
184
+ start_streamlit_app()
185
+ finally:
186
+ # Clean up recorder server
187
+ if recorder_process:
188
+ recorder_process.terminate()
189
+ print("\n๐Ÿงน Cleaning up processes...")
190
+ print("ุชู†ุธูŠู ุงู„ุนู…ู„ูŠุงุช...")
191
+ else:
192
+ print("\n๐Ÿ‘‹ Setup complete. Run 'python setup_enhanced.py' when ready.")
193
+ print("ุงู„ุฅุนุฏุงุฏ ู…ูƒุชู…ู„. ุดุบู‘ู„ 'python setup_enhanced.py' ุนู†ุฏู…ุง ุชูƒูˆู† ุฌุงู‡ุฒุงู‹.")
194
+
195
+ if __name__ == "__main__":
196
+ main()
start_debug.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enhanced startup script for SyncMaster with debugging capabilities
4
+ ู†ุต ุจุฏุก ุงู„ุชุดุบูŠู„ ุงู„ู…ุญุณู† ู„ู€ SyncMaster ู…ุน ู‚ุฏุฑุงุช ุงู„ุชุชุจุน
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import socket
11
+ import subprocess
12
+ import psutil
13
+ from pathlib import Path
14
+
15
+ def check_port_available(port):
16
+ """Check if a port is available"""
17
+ try:
18
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
19
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
20
+ s.bind(('localhost', port))
21
+ return True
22
+ except:
23
+ return False
24
+
25
+ def kill_processes_on_port(port):
26
+ """Kill processes using a specific port"""
27
+ try:
28
+ for proc in psutil.process_iter(['pid', 'name', 'connections']):
29
+ try:
30
+ connections = proc.info['connections']
31
+ if connections:
32
+ for conn in connections:
33
+ if conn.laddr.port == port:
34
+ print(f"๐Ÿ”„ Killing process {proc.info['name']} (PID: {proc.info['pid']}) using port {port}")
35
+ proc.kill()
36
+ time.sleep(1)
37
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
38
+ continue
39
+ except Exception as e:
40
+ print(f"โš ๏ธ Error killing processes on port {port}: {e}")
41
+
42
+ def check_dependencies():
43
+ """Check if required dependencies are installed"""
44
+ required_packages = [
45
+ 'streamlit', 'flask', 'librosa', 'soundfile',
46
+ 'google-generativeai', 'python-dotenv'
47
+ ]
48
+
49
+ missing_packages = []
50
+ for package in required_packages:
51
+ try:
52
+ __import__(package.replace('-', '_'))
53
+ except ImportError:
54
+ missing_packages.append(package)
55
+
56
+ if missing_packages:
57
+ print(f"โŒ Missing packages: {', '.join(missing_packages)}")
58
+ print("๐Ÿ“ฆ Installing missing packages...")
59
+ subprocess.run([sys.executable, '-m', 'pip', 'install'] + missing_packages)
60
+ return False
61
+ return True
62
+
63
+ def check_env_file():
64
+ """Check if .env file exists and has required keys"""
65
+ env_path = Path('.env')
66
+ if not env_path.exists():
67
+ print("โŒ .env file not found!")
68
+ print("๐Ÿ“ Creating sample .env file...")
69
+ with open('.env', 'w') as f:
70
+ f.write("GEMINI_API_KEY=your_api_key_here\n")
71
+ print("โœ… Please add your Gemini API key to .env file")
72
+ return False
73
+
74
+ # Check if API key is set
75
+ try:
76
+ from dotenv import load_dotenv
77
+ load_dotenv()
78
+ api_key = os.getenv("GEMINI_API_KEY")
79
+ if not api_key or api_key == "your_api_key_here":
80
+ print("โš ๏ธ GEMINI_API_KEY not properly set in .env file")
81
+ return False
82
+ except Exception as e:
83
+ print(f"โŒ Error reading .env file: {e}")
84
+ return False
85
+
86
+ return True
87
+
88
+ def start_recorder_server():
89
+ """Start the recorder server"""
90
+ print("๐ŸŽ™๏ธ Starting recorder server...")
91
+
92
+ # Kill any existing processes on port 5001
93
+ if not check_port_available(5001):
94
+ print("๐Ÿ”„ Port 5001 is busy, killing existing processes...")
95
+ kill_processes_on_port(5001)
96
+ time.sleep(2)
97
+
98
+ if check_port_available(5001):
99
+ try:
100
+ # Start recorder server
101
+ server_process = subprocess.Popen(
102
+ [sys.executable, 'recorder_server.py'],
103
+ stdout=subprocess.PIPE,
104
+ stderr=subprocess.PIPE,
105
+ creationflags=subprocess.CREATE_NEW_CONSOLE if os.name == 'nt' else 0
106
+ )
107
+
108
+ # Wait for server to start
109
+ time.sleep(3)
110
+
111
+ # Test server connection
112
+ import requests
113
+ try:
114
+ response = requests.get('http://localhost:5001/record', timeout=5)
115
+ if response.status_code == 200:
116
+ print("โœ… Recorder server started successfully on port 5001")
117
+ return server_process
118
+ else:
119
+ raise Exception(f"Server responded with status {response.status_code}")
120
+ except Exception as e:
121
+ print(f"โŒ Failed to connect to recorder server: {e}")
122
+ server_process.terminate()
123
+ return None
124
+
125
+ except Exception as e:
126
+ print(f"โŒ Failed to start recorder server: {e}")
127
+ return None
128
+ else:
129
+ print("โŒ Port 5001 is still not available")
130
+ return None
131
+
132
+ def start_main_app():
133
+ """Start the main Streamlit application"""
134
+ print("๐Ÿš€ Starting main SyncMaster application...")
135
+
136
+ # Find available port for Streamlit
137
+ streamlit_port = 8501
138
+ while not check_port_available(streamlit_port) and streamlit_port < 8510:
139
+ streamlit_port += 1
140
+
141
+ if streamlit_port >= 8510:
142
+ print("โŒ No available ports for Streamlit (tried 8501-8509)")
143
+ return None
144
+
145
+ try:
146
+ # Start Streamlit app
147
+ subprocess.run([
148
+ sys.executable, '-m', 'streamlit', 'run', 'app.py',
149
+ '--server.port', str(streamlit_port),
150
+ '--server.address', 'localhost',
151
+ '--browser.gatherUsageStats', 'false'
152
+ ])
153
+ except KeyboardInterrupt:
154
+ print("\n๐Ÿ›‘ Application stopped by user")
155
+ except Exception as e:
156
+ print(f"โŒ Failed to start main application: {e}")
157
+
158
+ def main():
159
+ """Main startup function"""
160
+ print("=" * 60)
161
+ print("๐ŸŽต SyncMaster Enhanced - Startup Script")
162
+ print("ู…ู†ุตุฉ ุงู„ู…ุฒุงู…ู†ุฉ ุงู„ุฐูƒูŠุฉ - ุณูƒุฑูŠุจุช ุงู„ุจุฏุก")
163
+ print("=" * 60)
164
+
165
+ # Change to script directory
166
+ script_dir = Path(__file__).parent
167
+ os.chdir(script_dir)
168
+ print(f"๐Ÿ“ Working directory: {script_dir}")
169
+
170
+ # Step 1: Check dependencies
171
+ print("\n๐Ÿ“ฆ Checking dependencies...")
172
+ if not check_dependencies():
173
+ print("โŒ Please restart after installing dependencies")
174
+ return
175
+ print("โœ… All dependencies available")
176
+
177
+ # Step 2: Check environment file
178
+ print("\n๐Ÿ”‘ Checking environment configuration...")
179
+ if not check_env_file():
180
+ print("โŒ Please configure .env file and restart")
181
+ return
182
+ print("โœ… Environment configuration OK")
183
+
184
+ # Step 3: Start recorder server
185
+ print("\n๐ŸŽ™๏ธ Starting recording server...")
186
+ server_process = start_recorder_server()
187
+ if not server_process:
188
+ print("โŒ Failed to start recorder server")
189
+ return
190
+
191
+ # Step 4: Start main application
192
+ print("\n๐ŸŒ Starting web interface...")
193
+ print("๐Ÿ“ฑ The application will open in your browser")
194
+ print("๐ŸŽ™๏ธ Recording interface: http://localhost:5001")
195
+ print("๐Ÿ’ป Main interface: http://localhost:8501")
196
+ print("\nPress Ctrl+C to stop all services")
197
+
198
+ try:
199
+ start_main_app()
200
+ finally:
201
+ # Cleanup
202
+ print("\n๐Ÿงน Cleaning up...")
203
+ if server_process:
204
+ server_process.terminate()
205
+ print("โœ… Recorder server stopped")
206
+ print("๐Ÿ‘‹ Goodbye!")
207
+
208
+ if __name__ == "__main__":
209
+ main()
start_enhanced.bat ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ echo ===============================================
3
+ echo SyncMaster Enhanced - Quick Start
4
+ echo ู…ู†ุตุฉ ุงู„ู…ุฒุงู…ู†ุฉ ุงู„ุฐูƒูŠุฉ - ุงู„ุจุฏุก ุงู„ุณุฑูŠุน
5
+ echo ===============================================
6
+ echo.
7
+
8
+ echo โœ… All system tests passed! / ุฌู…ูŠุน ุงู„ุงุฎุชุจุงุฑุงุช ู†ุฌุญุช!
9
+ echo ๐Ÿš€ Starting SyncMaster Enhanced...
10
+ echo.
11
+
12
+ REM Check if Python is installed
13
+ python --version >nul 2>&1
14
+ if errorlevel 1 (
15
+ echo โŒ ERROR: Python is not installed or not in PATH
16
+ echo ุฎุทุฃ: Python ุบูŠุฑ ู…ุซุจุช ุฃูˆ ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ PATH
17
+ echo Please install Python 3.8+ from python.org
18
+ pause
19
+ exit /b 1
20
+ )
21
+
22
+ REM Check if .env file exists
23
+ if not exist ".env" (
24
+ echo โš ๏ธ WARNING: .env file not found!
25
+ echo ุชุญุฐูŠุฑ: ู…ู„ู .env ุบูŠุฑ ู…ูˆุฌูˆุฏ!
26
+ echo Creating sample .env file...
27
+ echo GEMINI_API_KEY=your_api_key_here > .env
28
+ echo Please add your Gemini API key to .env file
29
+ echo ูŠุฑุฌู‰ ุฅุถุงูุฉ ู…ูุชุงุญ Gemini API ุฅู„ู‰ ู…ู„ู .env
30
+ pause
31
+ exit /b 1
32
+ )
33
+
34
+ echo ๐Ÿ” Running system test...
35
+ echo ุชุดุบูŠู„ ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู…...
36
+ python test_system.py
37
+ if errorlevel 1 (
38
+ echo โŒ System test failed! Please fix issues first.
39
+ echo ูุดู„ ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู…! ูŠุฑุฌู‰ ุฅุตู„ุงุญ ุงู„ู…ุดุงูƒู„ ุฃูˆู„ุงู‹.
40
+ pause
41
+ exit /b 1
42
+ )
43
+
44
+ echo.
45
+ echo โœ… System test passed! Starting application...
46
+ echo ู†ุฌุญ ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู…! ุจุฏุก ุชุดุบูŠู„ ุงู„ุชุทุจูŠู‚...
47
+ echo.
48
+
49
+ REM Use debug startup for better error handling
50
+ echo ๐Ÿš€ Starting with advanced debugging...
51
+ echo ุจุฏุก ุงู„ุชุดุบูŠู„ ู…ุน ุงู„ุชุดุฎูŠุต ุงู„ู…ุชู‚ุฏู…...
52
+ python start_debug.py
53
+
54
+ echo.
55
+ echo ๐Ÿ‘‹ Application stopped. Press any key to exit.
56
+ echo ุชู… ุฅูŠู‚ุงู ุงู„ุชุทุจูŠู‚. ุงุถุบุท ุฃูŠ ุฒุฑ ู„ู„ุฎุฑูˆุฌ.
57
+ pause
startup.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Startup Script for SyncMaster
4
+ ู†ู‚ุทุฉ ุฏุฎูˆู„ ู…ูˆุญุฏุฉ ุชุถู…ู† ุชุดุบูŠู„ ุฌู…ูŠุน ุงู„ู…ูƒูˆู†ุงุช ุงู„ู…ุทู„ูˆุจุฉ
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import logging
11
+ import subprocess
12
+ import signal
13
+ import atexit
14
+ from pathlib import Path
15
+
16
+ # Configure logging
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(levelname)s - %(message)s'
20
+ )
21
+
22
+ class SyncMasterLauncher:
23
+ def __init__(self):
24
+ self.recorder_process = None
25
+ self.streamlit_process = None
26
+ self.cleanup_registered = False
27
+
28
+ def setup_cleanup(self):
29
+ """Setup cleanup handlers"""
30
+ if not self.cleanup_registered:
31
+ atexit.register(self.cleanup)
32
+ signal.signal(signal.SIGINT, self.signal_handler)
33
+ signal.signal(signal.SIGTERM, self.signal_handler)
34
+ self.cleanup_registered = True
35
+
36
+ def signal_handler(self, signum, frame):
37
+ """Handle termination signals"""
38
+ logging.info(f"Received signal {signum}, cleaning up...")
39
+ self.cleanup()
40
+ sys.exit(0)
41
+
42
+ def cleanup(self):
43
+ """Clean up all processes"""
44
+ logging.info("๐Ÿงน Cleaning up processes...")
45
+
46
+ if self.recorder_process and self.recorder_process.poll() is None:
47
+ try:
48
+ self.recorder_process.terminate()
49
+ self.recorder_process.wait(timeout=5)
50
+ logging.info("โœ… Recorder server terminated")
51
+ except:
52
+ try:
53
+ self.recorder_process.kill()
54
+ logging.info("โš ๏ธ Recorder server killed")
55
+ except:
56
+ pass
57
+
58
+ if self.streamlit_process and self.streamlit_process.poll() is None:
59
+ try:
60
+ self.streamlit_process.terminate()
61
+ self.streamlit_process.wait(timeout=5)
62
+ logging.info("โœ… Streamlit server terminated")
63
+ except:
64
+ try:
65
+ self.streamlit_process.kill()
66
+ logging.info("โš ๏ธ Streamlit server killed")
67
+ except:
68
+ pass
69
+
70
+ def start_recorder_server(self):
71
+ """Start the recorder server"""
72
+ try:
73
+ logging.info("๐Ÿš€ Starting recorder server...")
74
+ self.recorder_process = subprocess.Popen(
75
+ [sys.executable, 'recorder_server.py'],
76
+ stdout=subprocess.PIPE,
77
+ stderr=subprocess.PIPE
78
+ )
79
+
80
+ # Wait for server to start
81
+ time.sleep(3)
82
+
83
+ # Check if process is running
84
+ if self.recorder_process.poll() is None:
85
+ # Verify server is responding
86
+ try:
87
+ import requests
88
+ response = requests.get('http://localhost:5001/record', timeout=5)
89
+ if response.status_code == 200:
90
+ logging.info("โœ… Recorder server started successfully on port 5001")
91
+ return True
92
+ else:
93
+ logging.warning(f"โš ๏ธ Recorder server responded with status: {response.status_code}")
94
+ except Exception as e:
95
+ logging.warning(f"โš ๏ธ Could not verify recorder server: {e}")
96
+
97
+ # Server process is running even if verification failed
98
+ return True
99
+ else:
100
+ logging.error("โŒ Recorder server process failed to start")
101
+ return False
102
+
103
+ except Exception as e:
104
+ logging.error(f"โŒ Failed to start recorder server: {e}")
105
+ return False
106
+
107
+ def start_streamlit_app(self, port=5050, host="0.0.0.0"):
108
+ """Start the Streamlit application"""
109
+ try:
110
+ logging.info(f"๐Ÿš€ Starting Streamlit app on {host}:{port}...")
111
+
112
+ cmd = [
113
+ sys.executable, '-m', 'streamlit', 'run', 'app.py',
114
+ '--server.port', str(port),
115
+ '--server.address', host,
116
+ '--server.headless', 'true',
117
+ '--browser.gatherUsageStats', 'false'
118
+ ]
119
+
120
+ self.streamlit_process = subprocess.Popen(cmd)
121
+
122
+ # Wait a bit for Streamlit to start
123
+ time.sleep(5)
124
+
125
+ if self.streamlit_process.poll() is None:
126
+ logging.info(f"โœ… Streamlit app started successfully on http://{host}:{port}")
127
+ return True
128
+ else:
129
+ logging.error("โŒ Streamlit app failed to start")
130
+ return False
131
+
132
+ except Exception as e:
133
+ logging.error(f"โŒ Failed to start Streamlit app: {e}")
134
+ return False
135
+
136
+ def launch_integrated(self):
137
+ """Launch with integrated server (recommended for HuggingFace)"""
138
+ logging.info("๐Ÿš€ Launching SyncMaster with integrated server...")
139
+ self.setup_cleanup()
140
+
141
+ # Start Streamlit with integrated server
142
+ try:
143
+ # Import to trigger integrated server startup
144
+ import app
145
+
146
+ # Run Streamlit
147
+ import streamlit.web.cli as stcli
148
+ import sys
149
+
150
+ # Set command line arguments for Streamlit
151
+ sys.argv = [
152
+ "streamlit", "run", "app.py",
153
+ "--server.port", "5050",
154
+ "--server.address", "0.0.0.0",
155
+ "--server.headless", "true",
156
+ "--browser.gatherUsageStats", "false"
157
+ ]
158
+
159
+ # Run Streamlit CLI
160
+ stcli.main()
161
+
162
+ except Exception as e:
163
+ logging.error(f"โŒ Failed to launch integrated mode: {e}")
164
+ return False
165
+
166
+ def launch_separate(self):
167
+ """Launch with separate processes (development mode)"""
168
+ logging.info("๐Ÿš€ Launching SyncMaster with separate processes...")
169
+ self.setup_cleanup()
170
+
171
+ # Start recorder server first
172
+ if not self.start_recorder_server():
173
+ logging.error("โŒ Failed to start recorder server, aborting...")
174
+ return False
175
+
176
+ # Start Streamlit app
177
+ if not self.start_streamlit_app():
178
+ logging.error("โŒ Failed to start Streamlit app, aborting...")
179
+ self.cleanup()
180
+ return False
181
+
182
+ logging.info("โœ… All services started successfully!")
183
+ logging.info("๐ŸŒ Access the application at: http://localhost:5050")
184
+ logging.info("๐ŸŽ™๏ธ Recorder API available at: http://localhost:5001")
185
+
186
+ try:
187
+ # Keep the main process alive
188
+ while True:
189
+ time.sleep(1)
190
+
191
+ # Check if processes are still running
192
+ if self.recorder_process and self.recorder_process.poll() is not None:
193
+ logging.error("โŒ Recorder server process died")
194
+ break
195
+
196
+ if self.streamlit_process and self.streamlit_process.poll() is not None:
197
+ logging.error("โŒ Streamlit app process died")
198
+ break
199
+
200
+ except KeyboardInterrupt:
201
+ logging.info("๐Ÿ‘‹ Shutting down...")
202
+ finally:
203
+ self.cleanup()
204
+
205
+ def main():
206
+ """Main entry point"""
207
+ launcher = SyncMasterLauncher()
208
+
209
+ # Check if running in HuggingFace or similar environment
210
+ if os.getenv('SPACE_ID') or '--integrated' in sys.argv:
211
+ # Use integrated mode for cloud deployments
212
+ launcher.launch_integrated()
213
+ else:
214
+ # Use separate processes for local development
215
+ launcher.launch_separate()
216
+
217
+ if __name__ == "__main__":
218
+ main()
summarizer.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # summarizer.py - ู†ุธุงู… ุงู„ู…ู„ุฎุต ุงู„ุฐูƒูŠ ู„ู„ู…ุญุงุถุฑุงุช
2
+
3
+ from translator import get_translator
4
+ from typing import Tuple, List, Dict, Optional
5
+ import re
6
+
7
+ class LectureSummarizer:
8
+ """ู†ุธุงู… ุงู„ู…ู„ุฎุต ุงู„ุฐูƒูŠ ู„ู„ู…ุญุงุถุฑุงุช ุงู„ุฏุฑุงุณูŠุฉ"""
9
+
10
+ def __init__(self):
11
+ self.translator = get_translator()
12
+
13
+ def generate_summary(self, text: str, language: str = 'ar') -> Tuple[Optional[str], Optional[str]]:
14
+ """
15
+ ุฅู†ุดุงุก ู…ู„ุฎุต ุฐูƒูŠ ู„ู„ู†ุต
16
+
17
+ Args:
18
+ text: ุงู„ู†ุต ุงู„ู…ุฑุงุฏ ุชู„ุฎูŠุตู‡
19
+ language: ู„ุบุฉ ุงู„ู…ู„ุฎุต ('ar' ู„ู„ุนุฑุจูŠุฉุŒ 'en' ู„ู„ุฅู†ุฌู„ูŠุฒูŠุฉ)
20
+
21
+ Returns:
22
+ Tuple of (summary, error_message)
23
+ """
24
+ if not self.translator or not self.translator.client:
25
+ return None, "ุฎุฏู…ุฉ ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ ุบูŠุฑ ู…ุชูˆูุฑุฉ"
26
+
27
+ if not text or len(text.strip()) < 50:
28
+ return None, "ุงู„ู†ุต ู‚ุตูŠุฑ ุฌุฏุงู‹ ู„ุฅู†ุดุงุก ู…ู„ุฎุต ู…ููŠุฏ"
29
+
30
+ try:
31
+ prompt = self._create_summary_prompt(text, language)
32
+
33
+ response = self.translator.client.models.generate_content(
34
+ model="gemini-2.5-flash",
35
+ contents=[prompt]
36
+ )
37
+
38
+ if response and response.text:
39
+ summary = response.text.strip()
40
+ summary = self._clean_summary_output(summary)
41
+ return summary, None
42
+ else:
43
+ return None, "ูุดู„ ููŠ ุฅู†ุดุงุก ุงู„ู…ู„ุฎุต"
44
+
45
+ except Exception as e:
46
+ return None, f"ุฎุทุฃ ููŠ ุฅู†ุดุงุก ุงู„ู…ู„ุฎุต: {str(e)}"
47
+
48
+ def extract_key_points(self, text: str, language: str = 'ar') -> Tuple[Optional[List[str]], Optional[str]]:
49
+ """
50
+ ุงุณุชุฎุฑุงุฌ ุงู„ู†ู‚ุงุท ุงู„ุฑุฆูŠุณูŠุฉ ู…ู† ุงู„ู†ุต
51
+
52
+ Args:
53
+ text: ุงู„ู†ุต ุงู„ู…ุฑุงุฏ ุงุณุชุฎุฑุงุฌ ุงู„ู†ู‚ุงุท ู…ู†ู‡
54
+ language: ู„ุบุฉ ุงู„ู†ู‚ุงุท
55
+
56
+ Returns:
57
+ Tuple of (key_points_list, error_message)
58
+ """
59
+ if not self.translator or not self.translator.client:
60
+ return None, "ุฎุฏู…ุฉ ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ ุบูŠุฑ ู…ุชูˆูุฑุฉ"
61
+
62
+ try:
63
+ prompt = self._create_key_points_prompt(text, language)
64
+
65
+ response = self.translator.client.models.generate_content(
66
+ model="gemini-2.5-flash",
67
+ contents=[prompt]
68
+ )
69
+
70
+ if response and response.text:
71
+ key_points_text = response.text.strip()
72
+ key_points = self._parse_key_points(key_points_text)
73
+ return key_points, None
74
+ else:
75
+ return None, "ูุดู„ ููŠ ุงุณุชุฎุฑุงุฌ ุงู„ู†ู‚ุงุท ุงู„ุฑุฆูŠุณูŠุฉ"
76
+
77
+ except Exception as e:
78
+ return None, f"ุฎุทุฃ ููŠ ุงุณุชุฎุฑุงุฌ ุงู„ู†ู‚ุงุท: {str(e)}"
79
+
80
+ def generate_study_notes(self, text: str, subject: str = "", language: str = 'ar') -> Tuple[Optional[Dict], Optional[str]]:
81
+ """
82
+ ุฅู†ุดุงุก ู…ุฐูƒุฑุฉ ุฏุฑุงุณูŠุฉ ุดุงู…ู„ุฉ
83
+
84
+ Args:
85
+ text: ุงู„ู†ุต ุงู„ุฃุตู„ูŠ
86
+ subject: ุงู„ู…ุงุฏุฉ ุงู„ุฏุฑุงุณูŠุฉ
87
+ language: ู„ุบุฉ ุงู„ู…ุฐูƒุฑุฉ
88
+
89
+ Returns:
90
+ Tuple of (study_notes_dict, error_message)
91
+ """
92
+ try:
93
+ # ุฅู†ุดุงุก ุงู„ู…ู„ุฎุต
94
+ summary, summary_error = self.generate_summary(text, language)
95
+ if summary_error and not summary:
96
+ return None, summary_error
97
+
98
+ # ุงุณุชุฎุฑุงุฌ ุงู„ู†ู‚ุงุท ุงู„ุฑุฆูŠุณูŠุฉ
99
+ key_points, points_error = self.extract_key_points(text, language)
100
+ if points_error and not key_points:
101
+ key_points = []
102
+
103
+ # ุฅู†ุดุงุก ุฃุณุฆู„ุฉ ู…ุฑุงุฌุนุฉ
104
+ review_questions, questions_error = self.generate_review_questions(text, language)
105
+ if questions_error and not review_questions:
106
+ review_questions = []
107
+
108
+ study_notes = {
109
+ 'summary': summary or "ู„ู… ูŠุชู… ุฅู†ุดุงุก ู…ู„ุฎุต",
110
+ 'key_points': key_points or [],
111
+ 'review_questions': review_questions or [],
112
+ 'subject': subject,
113
+ 'word_count': len(text.split()),
114
+ 'estimated_reading_time': max(1, len(text.split()) // 200) # ุฏู‚ุงุฆู‚ ุชู‚ุฑูŠุจูŠุฉ
115
+ }
116
+
117
+ return study_notes, None
118
+
119
+ except Exception as e:
120
+ return None, f"ุฎุทุฃ ููŠ ุฅู†ุดุงุก ุงู„ู…ุฐูƒุฑุฉ: {str(e)}"
121
+
122
+ def generate_review_questions(self, text: str, language: str = 'ar') -> Tuple[Optional[List[str]], Optional[str]]:
123
+ """
124
+ ุฅู†ุดุงุก ุฃุณุฆู„ุฉ ู…ุฑุงุฌุนุฉ ู…ู† ุงู„ู†ุต
125
+
126
+ Args:
127
+ text: ุงู„ู†ุต ุงู„ู…ุฑุงุฏ ุฅู†ุดุงุก ุฃุณุฆู„ุฉ ู…ู†ู‡
128
+ language: ู„ุบุฉ ุงู„ุฃุณุฆู„ุฉ
129
+
130
+ Returns:
131
+ Tuple of (questions_list, error_message)
132
+ """
133
+ if not self.translator or not self.translator.client:
134
+ return None, "ุฎุฏู…ุฉ ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ ุบูŠุฑ ู…ุชูˆูุฑุฉ"
135
+
136
+ try:
137
+ prompt = self._create_questions_prompt(text, language)
138
+
139
+ response = self.translator.client.models.generate_content(
140
+ model="gemini-2.5-flash",
141
+ contents=[prompt]
142
+ )
143
+
144
+ if response and response.text:
145
+ questions_text = response.text.strip()
146
+ questions = self._parse_questions(questions_text)
147
+ return questions, None
148
+ else:
149
+ return None, "ูุดู„ ููŠ ุฅู†ุดุงุก ุฃุณุฆู„ุฉ ุงู„ู…ุฑุงุฌุนุฉ"
150
+
151
+ except Exception as e:
152
+ return None, f"ุฎุทุฃ ููŠ ุฅู†ุดุงุก ุงู„ุฃุณุฆู„ุฉ: {str(e)}"
153
+
154
+ def _create_summary_prompt(self, text: str, language: str) -> str:
155
+ """ุฅู†ุดุงุก prompt ู„ู„ู…ู„ุฎุต"""
156
+ if language == 'ar':
157
+ return f"""
158
+ ู‚ู… ุจุฅู†ุดุงุก ู…ู„ุฎุต ุดุงู…ู„ ูˆู…ููŠุฏ ู„ู‡ุฐุง ุงู„ู†ุต ู…ู† ู…ุญุงุถุฑุฉ ุฏุฑุงุณูŠุฉ:
159
+
160
+ ู…ุชุทู„ุจุงุช ุงู„ู…ู„ุฎุต:
161
+ 1. ุงูƒุชุจ ุจุงู„ุนุฑุจูŠุฉ ุงู„ูุตุญู‰ ุงู„ูˆุงุถุญุฉ
162
+ 2. ุงุฐูƒุฑ ุงู„ู…ูˆุถูˆุน ุงู„ุฑุฆูŠุณูŠ ูˆุงู„ุฃููƒุงุฑ ุงู„ู…ู‡ู…ุฉ
163
+ 3. ุฑุชุจ ุงู„ู…ุนู„ูˆู…ุงุช ุจุดูƒู„ ู…ู†ุทู‚ูŠ
164
+ 4. ุงุฌุนู„ ุงู„ู…ู„ุฎุต ู…ู†ุงุณุจ ู„ู„ุทู„ุงุจ ุงู„ุฌุงู…ุนูŠูŠู†
165
+ 5. ู„ุง ุชุชุฌุงูˆุฒ 200 ูƒู„ู…ุฉ
166
+ 6. ุฃุถู ุงู„ุนู†ุงูˆูŠู† ุงู„ูุฑุนูŠุฉ ุฅุฐุง ู„ุฒู… ุงู„ุฃู…ุฑ
167
+
168
+ ุงู„ู†ุต:
169
+ {text}
170
+
171
+ ุงู„ู…ู„ุฎุต:
172
+ """
173
+ else:
174
+ return f"""
175
+ Create a comprehensive and useful summary of this lecture text:
176
+
177
+ Requirements:
178
+ 1. Write in clear, academic English
179
+ 2. Mention the main topic and important ideas
180
+ 3. Organize information logically
181
+ 4. Make it suitable for university students
182
+ 5. Don't exceed 200 words
183
+ 6. Add subheadings if necessary
184
+
185
+ Text:
186
+ {text}
187
+
188
+ Summary:
189
+ """
190
+
191
+ def _create_key_points_prompt(self, text: str, language: str) -> str:
192
+ """ุฅู†ุดุงุก prompt ู„ู„ู†ู‚ุงุท ุงู„ุฑุฆูŠุณูŠุฉ"""
193
+ if language == 'ar':
194
+ return f"""
195
+ ุงุณุชุฎุฑุฌ ุฃู‡ู… ุงู„ู†ู‚ุงุท ุงู„ุฑุฆูŠุณูŠุฉ ู…ู† ู‡ุฐุง ุงู„ู†ุต:
196
+
197
+ ู…ุชุทู„ุจุงุช:
198
+ 1. ุงูƒุชุจ ูƒู„ ู†ู‚ุทุฉ ููŠ ุณุทุฑ ู…ู†ูุตู„
199
+ 2. ุงุจุฏุฃ ูƒู„ ู†ู‚ุทุฉ ุจู€ "โ€ข"
200
+ 3. ุงุฌุนู„ ูƒู„ ู†ู‚ุทุฉ ูˆุงุถุญุฉ ูˆู…ููŠุฏุฉ ู„ู„ุฏุฑุงุณุฉ
201
+ 4. ู„ุง ุชุฒูŠุฏ ุนู† 8 ู†ู‚ุงุท
202
+ 5. ุฑุชุจ ุงู„ู†ู‚ุงุท ุญุณุจ ุงู„ุฃู‡ู…ูŠุฉ
203
+
204
+ ุงู„ู†ุต:
205
+ {text}
206
+
207
+ ุงู„ู†ู‚ุงุท ุงู„ุฑุฆูŠุณูŠุฉ:
208
+ """
209
+ else:
210
+ return f"""
211
+ Extract the most important key points from this text:
212
+
213
+ Requirements:
214
+ 1. Write each point on a separate line
215
+ 2. Start each point with "โ€ข"
216
+ 3. Make each point clear and useful for studying
217
+ 4. No more than 8 points
218
+ 5. Order points by importance
219
+
220
+ Text:
221
+ {text}
222
+
223
+ Key Points:
224
+ """
225
+
226
+ def _create_questions_prompt(self, text: str, language: str) -> str:
227
+ """ุฅู†ุดุงุก prompt ู„ุฃุณุฆู„ุฉ ุงู„ู…ุฑุงุฌุนุฉ"""
228
+ if language == 'ar':
229
+ return f"""
230
+ ุฃู†ุดุฆ ุฃุณุฆู„ุฉ ู…ุฑุงุฌุนุฉ ู…ููŠุฏุฉ ู…ู† ู‡ุฐุง ุงู„ู†ุต:
231
+
232
+ ู…ุชุทู„ุจุงุช:
233
+ 1. ุงูƒุชุจ ูƒู„ ุณุคุงู„ ููŠ ุณุทุฑ ู…ู†ูุตู„
234
+ 2. ุงุจุฏุฃ ูƒู„ ุณุคุงู„ ุจุฑู‚ู… (1ุŒ 2ุŒ 3...)
235
+ 3. ุงุฌุนู„ ุงู„ุฃุณุฆู„ุฉ ุชุบุทูŠ ุงู„ู…ูุงู‡ูŠู… ุงู„ู…ู‡ู…ุฉ
236
+ 4. ุชู†ูˆุน ููŠ ุฃู†ูˆุงุน ุงู„ุฃุณุฆู„ุฉ (ู…ุงุŒ ูƒูŠูุŒ ู„ู…ุงุฐุงุŒ ุงุดุฑุญ)
237
+ 5. ู„ุง ุชุฒูŠุฏ ุนู† 6 ุฃุณุฆู„ุฉ
238
+ 6. ุงุฌุนู„ ุงู„ุฃุณุฆู„ุฉ ู…ู†ุงุณุจุฉ ู„ู„ุงู…ุชุญุงู†ุงุช
239
+
240
+ ุงู„ู†ุต:
241
+ {text}
242
+
243
+ ุฃุณุฆู„ุฉ ุงู„ู…ุฑุงุฌุนุฉ:
244
+ """
245
+ else:
246
+ return f"""
247
+ Create useful review questions from this text:
248
+
249
+ Requirements:
250
+ 1. Write each question on a separate line
251
+ 2. Start each question with a number (1, 2, 3...)
252
+ 3. Make questions cover important concepts
253
+ 4. Vary question types (what, how, why, explain)
254
+ 5. No more than 6 questions
255
+ 6. Make questions suitable for exams
256
+
257
+ Text:
258
+ {text}
259
+
260
+ Review Questions:
261
+ """
262
+
263
+ def _clean_summary_output(self, text: str) -> str:
264
+ """ุชู†ุธูŠู ู†ุต ุงู„ู…ู„ุฎุต"""
265
+ # ุฅุฒุงู„ุฉ ุงู„ุฑู…ูˆุฒ ุบูŠุฑ ุงู„ู…ุฑุบูˆุจุฉ
266
+ text = re.sub(r'\*+', '', text)
267
+ text = re.sub(r'#+', '', text)
268
+ text = text.strip()
269
+
270
+ # ุชู†ุธูŠู ุงู„ุฃุณุทุฑ ุงู„ูุงุฑุบุฉ ุงู„ุฒุงุฆุฏุฉ
271
+ text = re.sub(r'\n\s*\n', '\n\n', text)
272
+
273
+ return text
274
+
275
+ def _parse_key_points(self, text: str) -> List[str]:
276
+ """ุชุญู„ูŠู„ ุงู„ู†ู‚ุงุท ุงู„ุฑุฆูŠุณูŠุฉ ู…ู† ุงู„ู†ุต"""
277
+ points = []
278
+ lines = text.split('\n')
279
+
280
+ for line in lines:
281
+ line = line.strip()
282
+ if line and (line.startswith('โ€ข') or line.startswith('-') or line.startswith('*')):
283
+ # ุฅุฒุงู„ุฉ ุงู„ุฑู…ุฒ ู…ู† ุงู„ุจุฏุงูŠุฉ
284
+ point = re.sub(r'^[โ€ข\-\*]\s*', '', line)
285
+ if point:
286
+ points.append(point)
287
+ elif line and re.match(r'^\d+\.', line):
288
+ # ู†ู‚ุงุท ู…ุฑู‚ู…ุฉ
289
+ point = re.sub(r'^\d+\.\s*', '', line)
290
+ if point:
291
+ points.append(point)
292
+
293
+ return points[:8] # ุญุฏ ุฃู‚ุตู‰ 8 ู†ู‚ุงุท
294
+
295
+ def _parse_questions(self, text: str) -> List[str]:
296
+ """ุชุญู„ูŠู„ ุงู„ุฃุณุฆู„ุฉ ู…ู† ุงู„ู†ุต"""
297
+ questions = []
298
+ lines = text.split('\n')
299
+
300
+ for line in lines:
301
+ line = line.strip()
302
+ if line and ('ุŸ' in line or '?' in line):
303
+ # ุฅุฒุงู„ุฉ ุงู„ุชุฑู‚ูŠู… ู…ู† ุงู„ุจุฏุงูŠุฉ
304
+ question = re.sub(r'^\d+[\.\-\)]\s*', '', line)
305
+ if question:
306
+ questions.append(question)
307
+
308
+ return questions[:6] # ุญุฏ ุฃู‚ุตู‰ 6 ุฃุณุฆู„ุฉ
templates/recorder.html ADDED
@@ -0,0 +1,1958 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" dir="ltr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Lecture Recorder - SyncMaster</title>
7
+ <style>
8
+ :root {
9
+ --primary-color: #2563eb;
10
+ --primary-dark: #1d4ed8;
11
+ --success-color: #16a34a;
12
+ --danger-color: #dc2626;
13
+ --warning-color: #ea580c;
14
+ --neutral-100: #f5f5f5;
15
+ --neutral-200: #e5e5e5;
16
+ --neutral-300: #d4d4d8;
17
+ --neutral-600: #525252;
18
+ --neutral-700: #404040;
19
+ --neutral-800: #262626;
20
+ --neutral-900: #171717;
21
+ --white: #ffffff;
22
+ --text-primary: #0f172a;
23
+ --text-secondary: #64748b;
24
+ --border-radius: 8px;
25
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
26
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
27
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
28
+ }
29
+
30
+ /* RTL Support for Arabic */
31
+ html[dir="rtl"] {
32
+ direction: rtl;
33
+ }
34
+
35
+ html[dir="rtl"] .language-selector {
36
+ left: 20px;
37
+ right: auto;
38
+ }
39
+
40
+ * {
41
+ box-sizing: border-box;
42
+ }
43
+
44
+ body {
45
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
46
+ margin: 0;
47
+ padding: 20px;
48
+ min-height: 100vh;
49
+ background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
50
+ color: var(--text-primary);
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ }
55
+
56
+ .recorder-container {
57
+ background: var(--white);
58
+ border-radius: 16px;
59
+ box-shadow: var(--shadow-lg);
60
+ width: 100%;
61
+ max-width: 640px;
62
+ padding: 32px;
63
+ text-align: center;
64
+ position: relative;
65
+ }
66
+
67
+ /* Language Selector */
68
+ .language-selector {
69
+ position: absolute;
70
+ top: 20px;
71
+ right: 20px;
72
+ z-index: 100;
73
+ }
74
+
75
+ .language-selector select {
76
+ padding: 8px 12px;
77
+ border: 1px solid var(--neutral-300);
78
+ border-radius: var(--border-radius);
79
+ background: var(--white);
80
+ color: var(--text-primary);
81
+ font-size: 14px;
82
+ font-weight: 500;
83
+ cursor: pointer;
84
+ outline: none;
85
+ }
86
+
87
+ .language-selector select:focus {
88
+ border-color: var(--primary-color);
89
+ box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
90
+ }
91
+
92
+ /* Translation Controls */
93
+ .translation-controls {
94
+ margin: 24px 0;
95
+ padding: 20px;
96
+ background: var(--neutral-100);
97
+ border-radius: var(--border-radius);
98
+ border: 1px solid var(--neutral-200);
99
+ }
100
+
101
+ .translation-toggle {
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ gap: 16px;
106
+ margin-bottom: 12px;
107
+ flex-wrap: wrap;
108
+ }
109
+
110
+ .toggle-switch {
111
+ position: relative;
112
+ width: 48px;
113
+ height: 24px;
114
+ }
115
+
116
+ .toggle-switch input {
117
+ opacity: 0;
118
+ width: 0;
119
+ height: 0;
120
+ }
121
+
122
+ .slider {
123
+ position: absolute;
124
+ cursor: pointer;
125
+ top: 0;
126
+ left: 0;
127
+ right: 0;
128
+ bottom: 0;
129
+ background-color: var(--neutral-300);
130
+ border-radius: 24px;
131
+ transition: background-color 0.3s ease;
132
+ }
133
+
134
+ .slider:before {
135
+ position: absolute;
136
+ content: "";
137
+ height: 18px;
138
+ width: 18px;
139
+ left: 3px;
140
+ bottom: 3px;
141
+ background-color: var(--white);
142
+ border-radius: 50%;
143
+ transition: transform 0.3s ease;
144
+ box-shadow: var(--shadow-sm);
145
+ }
146
+
147
+ input:checked + .slider {
148
+ background-color: var(--primary-color);
149
+ }
150
+
151
+ input:checked + .slider:before {
152
+ transform: translateX(24px);
153
+ }
154
+
155
+ .target-language-select {
156
+ padding: 8px 12px;
157
+ border: 1px solid var(--neutral-300);
158
+ border-radius: var(--border-radius);
159
+ background: var(--white);
160
+ min-width: 140px;
161
+ font-size: 14px;
162
+ color: var(--text-primary);
163
+ }
164
+
165
+ .target-language-select:focus {
166
+ border-color: var(--primary-color);
167
+ outline: none;
168
+ box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
169
+ }
170
+
171
+ h2 {
172
+ margin-top: 0;
173
+ margin-bottom: 8px;
174
+ color: var(--text-primary);
175
+ font-size: 28px;
176
+ font-weight: 700;
177
+ letter-spacing: -0.025em;
178
+ }
179
+
180
+ .subtitle {
181
+ color: var(--text-secondary);
182
+ margin-bottom: 32px;
183
+ font-size: 16px;
184
+ line-height: 1.5;
185
+ }
186
+
187
+ .button-group {
188
+ margin-top: 24px;
189
+ display: grid;
190
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
191
+ gap: 12px;
192
+ }
193
+
194
+ .button {
195
+ color: var(--white);
196
+ border: none;
197
+ padding: 12px 20px;
198
+ font-size: 14px;
199
+ font-weight: 600;
200
+ border-radius: var(--border-radius);
201
+ cursor: pointer;
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ gap: 8px;
206
+ min-height: 44px;
207
+ text-transform: none;
208
+ letter-spacing: normal;
209
+ transition: none;
210
+ }
211
+
212
+ .button:disabled {
213
+ opacity: 0.5;
214
+ cursor: not-allowed;
215
+ }
216
+
217
+ .button:focus {
218
+ outline: none;
219
+ box-shadow: 0 0 0 3px rgb(0 0 0 / 0.1);
220
+ }
221
+
222
+ #recordButton {
223
+ background: var(--success-color);
224
+ }
225
+
226
+ #stopButton {
227
+ background: var(--danger-color);
228
+ }
229
+
230
+ #pauseButton {
231
+ background: var(--warning-color);
232
+ }
233
+
234
+ #markButton {
235
+ background: var(--primary-color);
236
+ }
237
+
238
+ #extractButton {
239
+ background: var(--primary-color);
240
+ }
241
+
242
+ #rerecordButton {
243
+ background: var(--neutral-600);
244
+ }
245
+
246
+ .status-display {
247
+ margin: 24px 0;
248
+ font-size: 16px;
249
+ color: var(--text-secondary);
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ gap: 12px;
254
+ font-weight: 500;
255
+ }
256
+
257
+ .recording-indicator {
258
+ width: 10px;
259
+ height: 10px;
260
+ background-color: var(--danger-color);
261
+ border-radius: 50%;
262
+ animation: pulse 2s infinite;
263
+ }
264
+
265
+ @keyframes pulse {
266
+ 0%, 100% {
267
+ opacity: 1;
268
+ transform: scale(1);
269
+ }
270
+ 50% {
271
+ opacity: 0.5;
272
+ transform: scale(1.1);
273
+ }
274
+ }
275
+
276
+ #timer {
277
+ font-size: 36px;
278
+ font-weight: 300;
279
+ margin: 20px 0;
280
+ color: var(--primary-color);
281
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
282
+ letter-spacing: 0.05em;
283
+ }
284
+
285
+ #audio-level-container {
286
+ width: 100%;
287
+ height: 6px;
288
+ background-color: var(--neutral-200);
289
+ border-radius: 3px;
290
+ overflow: hidden;
291
+ margin: 20px 0;
292
+ }
293
+
294
+ #audio-level-indicator {
295
+ width: 0%;
296
+ height: 100%;
297
+ background: linear-gradient(90deg, var(--success-color), var(--warning-color), var(--danger-color));
298
+ transition: width 0.1s ease;
299
+ }
300
+
301
+ #review-section, #transcription-container {
302
+ margin-top: 32px;
303
+ text-align: left;
304
+ }
305
+
306
+ .section-title {
307
+ font-size: 18px;
308
+ font-weight: 600;
309
+ margin-bottom: 16px;
310
+ color: var(--text-primary);
311
+ border-bottom: 2px solid var(--primary-color);
312
+ padding-bottom: 8px;
313
+ }
314
+
315
+ textarea {
316
+ width: 100%;
317
+ border-radius: var(--border-radius);
318
+ border: 1px solid var(--neutral-300);
319
+ padding: 16px;
320
+ font-family: inherit;
321
+ resize: vertical;
322
+ font-size: 14px;
323
+ line-height: 1.6;
324
+ color: var(--text-primary);
325
+ background: var(--white);
326
+ }
327
+
328
+ textarea:focus {
329
+ outline: none;
330
+ border-color: var(--primary-color);
331
+ box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
332
+ }
333
+
334
+ .translation-result {
335
+ margin-top: 20px;
336
+ padding: 20px;
337
+ background: var(--neutral-100);
338
+ border-radius: var(--border-radius);
339
+ border: 1px solid var(--neutral-200);
340
+ }
341
+
342
+ .translation-header {
343
+ font-weight: 600;
344
+ color: var(--primary-color);
345
+ margin-bottom: 12px;
346
+ font-size: 16px;
347
+ }
348
+
349
+ .language-info {
350
+ margin-top: 12px;
351
+ font-size: 13px;
352
+ color: var(--text-secondary);
353
+ padding: 8px 12px;
354
+ background: var(--white);
355
+ border-radius: var(--border-radius);
356
+ border: 1px solid var(--neutral-200);
357
+ }
358
+
359
+ /* Loading Spinner */
360
+ .loading-spinner {
361
+ display: inline-block;
362
+ width: 20px;
363
+ height: 20px;
364
+ border: 2px solid var(--neutral-300);
365
+ border-top: 2px solid var(--primary-color);
366
+ border-radius: 50%;
367
+ animation: spin 1s linear infinite;
368
+ }
369
+
370
+ @keyframes spin {
371
+ 0% { transform: rotate(0deg); }
372
+ 100% { transform: rotate(360deg); }
373
+ }
374
+
375
+ /* Messages */
376
+ .error-message {
377
+ background: #fef2f2;
378
+ color: var(--danger-color);
379
+ padding: 12px 16px;
380
+ border-radius: var(--border-radius);
381
+ margin: 12px 0;
382
+ border: 1px solid #fecaca;
383
+ font-size: 14px;
384
+ }
385
+
386
+ .success-message {
387
+ background: #f0fdf4;
388
+ color: var(--success-color);
389
+ padding: 12px 16px;
390
+ border-radius: var(--border-radius);
391
+ margin: 12px 0;
392
+ border: 1px solid #bbf7d0;
393
+ font-size: 14px;
394
+ }
395
+
396
+ .translation-status {
397
+ font-size: 12px;
398
+ padding: 6px 10px;
399
+ border-radius: 4px;
400
+ margin-top: 8px;
401
+ font-weight: 500;
402
+ }
403
+
404
+ /* Responsive Design */
405
+ @media (max-width: 768px) {
406
+ body {
407
+ padding: 12px;
408
+ }
409
+
410
+ .recorder-container {
411
+ padding: 24px 20px;
412
+ }
413
+
414
+ .button-group {
415
+ grid-template-columns: 1fr;
416
+ gap: 8px;
417
+ }
418
+
419
+ #timer {
420
+ font-size: 28px;
421
+ }
422
+
423
+ h2 {
424
+ font-size: 24px;
425
+ }
426
+
427
+ .translation-toggle {
428
+ flex-direction: column;
429
+ gap: 12px;
430
+ }
431
+
432
+ .language-selector {
433
+ position: static;
434
+ margin-bottom: 16px;
435
+ text-align: center;
436
+ }
437
+ }
438
+
439
+ @media (max-width: 480px) {
440
+ .recorder-container {
441
+ padding: 20px 16px;
442
+ }
443
+
444
+ h2 {
445
+ font-size: 20px;
446
+ }
447
+
448
+ .subtitle {
449
+ font-size: 14px;
450
+ }
451
+
452
+ #timer {
453
+ font-size: 24px;
454
+ }
455
+ }
456
+
457
+ /* Focus indicators for accessibility */
458
+ .button:focus,
459
+ select:focus,
460
+ input:focus {
461
+ box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
462
+ }
463
+
464
+ /* Summary and Notes Styles */
465
+ .note-item {
466
+ background: var(--white);
467
+ border: 1px solid var(--neutral-200);
468
+ border-radius: var(--border-radius);
469
+ padding: 20px;
470
+ margin-bottom: 16px;
471
+ transition: all 0.2s ease;
472
+ }
473
+
474
+ .note-item:hover {
475
+ border-color: var(--primary-color);
476
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
477
+ }
478
+
479
+ .note-header {
480
+ display: flex;
481
+ justify-content: space-between;
482
+ align-items: flex-start;
483
+ margin-bottom: 12px;
484
+ }
485
+
486
+ .note-header h4 {
487
+ margin: 0;
488
+ color: var(--text-primary);
489
+ font-size: 16px;
490
+ font-weight: 600;
491
+ }
492
+
493
+ .note-date {
494
+ font-size: 12px;
495
+ color: var(--text-secondary);
496
+ white-space: nowrap;
497
+ }
498
+
499
+ .note-summary {
500
+ color: var(--text-secondary);
501
+ font-size: 14px;
502
+ line-height: 1.5;
503
+ margin-bottom: 12px;
504
+ }
505
+
506
+ .note-tags {
507
+ margin-bottom: 12px;
508
+ }
509
+
510
+ .tag {
511
+ display: inline-block;
512
+ background: var(--primary-color);
513
+ color: white;
514
+ padding: 4px 8px;
515
+ border-radius: 12px;
516
+ font-size: 12px;
517
+ margin-right: 6px;
518
+ margin-bottom: 4px;
519
+ }
520
+
521
+ .note-actions {
522
+ display: flex;
523
+ gap: 8px;
524
+ }
525
+
526
+ .btn-secondary {
527
+ background: var(--neutral-200);
528
+ color: var(--text-primary);
529
+ border: 1px solid var(--neutral-300);
530
+ padding: 6px 12px;
531
+ border-radius: 4px;
532
+ font-size: 12px;
533
+ cursor: pointer;
534
+ transition: all 0.2s ease;
535
+ }
536
+
537
+ .btn-secondary:hover {
538
+ background: var(--neutral-300);
539
+ }
540
+
541
+ .btn-danger {
542
+ background: #dc3545;
543
+ color: white;
544
+ border: 1px solid #dc3545;
545
+ padding: 6px 12px;
546
+ border-radius: 4px;
547
+ font-size: 12px;
548
+ cursor: pointer;
549
+ transition: all 0.2s ease;
550
+ }
551
+
552
+ .btn-danger:hover {
553
+ background: #c82333;
554
+ border-color: #c82333;
555
+ }
556
+
557
+ /* Modal Styles */
558
+ .note-modal {
559
+ position: fixed;
560
+ top: 0;
561
+ left: 0;
562
+ width: 100%;
563
+ height: 100%;
564
+ background: rgba(0, 0, 0, 0.5);
565
+ display: flex;
566
+ justify-content: center;
567
+ align-items: center;
568
+ z-index: 1000;
569
+ padding: 20px;
570
+ }
571
+
572
+ .note-modal-content {
573
+ background: var(--white);
574
+ border-radius: var(--border-radius);
575
+ max-width: 800px;
576
+ width: 100%;
577
+ max-height: 90vh;
578
+ overflow-y: auto;
579
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
580
+ }
581
+
582
+ .note-modal-header {
583
+ display: flex;
584
+ justify-content: space-between;
585
+ align-items: center;
586
+ padding: 20px 24px;
587
+ border-bottom: 1px solid var(--neutral-200);
588
+ position: sticky;
589
+ top: 0;
590
+ background: var(--white);
591
+ z-index: 1;
592
+ }
593
+
594
+ .note-modal-header h2 {
595
+ margin: 0;
596
+ color: var(--text-primary);
597
+ font-size: 20px;
598
+ }
599
+
600
+ .close-btn {
601
+ background: none;
602
+ border: none;
603
+ font-size: 24px;
604
+ cursor: pointer;
605
+ color: var(--text-secondary);
606
+ padding: 0;
607
+ width: 30px;
608
+ height: 30px;
609
+ display: flex;
610
+ align-items: center;
611
+ justify-content: center;
612
+ border-radius: 50%;
613
+ transition: all 0.2s ease;
614
+ }
615
+
616
+ .close-btn:hover {
617
+ background: var(--neutral-100);
618
+ color: var(--text-primary);
619
+ }
620
+
621
+ .note-modal-body {
622
+ padding: 24px;
623
+ }
624
+
625
+ .note-section {
626
+ margin-bottom: 24px;
627
+ }
628
+
629
+ .note-section h3 {
630
+ color: var(--primary-color);
631
+ font-size: 16px;
632
+ font-weight: 600;
633
+ margin-bottom: 12px;
634
+ border-bottom: 1px solid var(--neutral-200);
635
+ padding-bottom: 8px;
636
+ }
637
+
638
+ .note-section p {
639
+ color: var(--text-primary);
640
+ line-height: 1.6;
641
+ margin: 0;
642
+ }
643
+
644
+ .note-section ul {
645
+ margin: 0;
646
+ padding-left: 20px;
647
+ }
648
+
649
+ .note-section li {
650
+ color: var(--text-primary);
651
+ line-height: 1.6;
652
+ margin-bottom: 8px;
653
+ }
654
+
655
+ /* Search Styles */
656
+ .search-container {
657
+ margin-bottom: 20px;
658
+ }
659
+
660
+ .search-container input {
661
+ width: 100%;
662
+ padding: 12px 16px;
663
+ border: 1px solid var(--neutral-300);
664
+ border-radius: var(--border-radius);
665
+ font-size: 14px;
666
+ background: var(--white);
667
+ color: var(--text-primary);
668
+ }
669
+
670
+ .search-container input:focus {
671
+ outline: none;
672
+ border-color: var(--primary-color);
673
+ box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
674
+ }
675
+
676
+ /* Empty state */
677
+ .text-center {
678
+ text-align: center;
679
+ }
680
+
681
+ .text-muted {
682
+ color: var(--text-secondary);
683
+ }
684
+
685
+ /* Form Styles for Summary Section */
686
+ .form-group {
687
+ margin-bottom: 16px;
688
+ }
689
+
690
+ .form-group label {
691
+ display: block;
692
+ margin-bottom: 6px;
693
+ font-weight: 500;
694
+ color: var(--text-primary);
695
+ }
696
+
697
+ .form-group input,
698
+ .form-group textarea {
699
+ width: 100%;
700
+ padding: 10px 12px;
701
+ border: 1px solid var(--neutral-300);
702
+ border-radius: 4px;
703
+ font-size: 14px;
704
+ background: var(--white);
705
+ color: var(--text-primary);
706
+ }
707
+
708
+ .form-group input:focus,
709
+ .form-group textarea:focus {
710
+ outline: none;
711
+ border-color: var(--primary-color);
712
+ box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
713
+ }
714
+
715
+ /* Summary Content Styling */
716
+ .summary-content {
717
+ background: var(--neutral-50);
718
+ border: 1px solid var(--neutral-200);
719
+ border-radius: var(--border-radius);
720
+ padding: 16px;
721
+ margin-bottom: 16px;
722
+ }
723
+
724
+ .summary-content h4 {
725
+ color: var(--primary-color);
726
+ margin-bottom: 12px;
727
+ font-size: 16px;
728
+ }
729
+
730
+ .summary-content ul {
731
+ margin: 0;
732
+ padding-left: 20px;
733
+ }
734
+
735
+ .summary-content li {
736
+ margin-bottom: 8px;
737
+ line-height: 1.5;
738
+ }
739
+ </style>
740
+ </head>
741
+ <body>
742
+ <div class="recorder-container">
743
+ <!-- Language Selector -->
744
+ <div class="language-selector">
745
+ <select id="languageSelect" aria-label="Select Language">
746
+ <option value="en">English</option>
747
+ <option value="ar">ุงู„ุนุฑุจูŠุฉ</option>
748
+ </select>
749
+ </div>
750
+
751
+ <h2 id="main-title">Lecture Recorder</h2>
752
+ <p class="subtitle" id="subtitle">Record and transcribe your lectures with AI-powered translation</p>
753
+
754
+ <!-- Translation Controls -->
755
+ <div class="translation-controls">
756
+ <div class="translation-toggle">
757
+ <label for="translationToggle" id="translation-label">Enable Translation:</label>
758
+ <label class="toggle-switch">
759
+ <input type="checkbox" id="translationToggle" checked>
760
+ <span class="slider"></span>
761
+ </label>
762
+ <select id="targetLanguage" class="target-language-select">
763
+ <option value="ar">ุงู„ุนุฑุจูŠุฉ (Arabic)</option>
764
+ <option value="en">English</option>
765
+ <option value="fr">Franรงais</option>
766
+ <option value="es">Espaรฑol</option>
767
+ </select>
768
+ </div>
769
+ </div>
770
+
771
+ <div id="recording-controls">
772
+ <div class="status-display">
773
+ <div id="recording-indicator" class="recording-indicator" style="display: none;"></div>
774
+ <span id="status">Ready to Record</span>
775
+ </div>
776
+ <div id="timer">00:00:00</div>
777
+ <div id="audio-level-container">
778
+ <div id="audio-level-indicator"></div>
779
+ </div>
780
+ <div class="button-group">
781
+ <button id="recordButton" class="button" aria-label="Start Recording">
782
+ <span>๐ŸŽ™๏ธ</span>
783
+ <span id="record-text">Start</span>
784
+ </button>
785
+ <button id="pauseButton" class="button" disabled aria-label="Pause Recording">
786
+ <span>โธ๏ธ</span>
787
+ <span id="pause-text">Pause</span>
788
+ </button>
789
+ <button id="stopButton" class="button" disabled aria-label="Stop Recording">
790
+ <span>โน๏ธ</span>
791
+ <span id="stop-text">Stop</span>
792
+ </button>
793
+ <button id="markButton" class="button" disabled aria-label="Mark Important Point">
794
+ <span>๐Ÿ“Œ</span>
795
+ <span id="mark-text">Mark Important</span>
796
+ </button>
797
+ </div>
798
+ </div>
799
+
800
+ <div id="review-section" style="display: none;">
801
+ <h3 class="section-title" id="review-title">Review Recording</h3>
802
+ <audio id="audio-playback" controls style="width: 100%;"></audio>
803
+ <div class="button-group">
804
+ <button id="extractButton" class="button" aria-label="Extract Text">
805
+ <span>๐Ÿ“„</span>
806
+ <span id="extract-text">Extract Text</span>
807
+ </button>
808
+ <button id="rerecordButton" class="button" aria-label="Record Again">
809
+ <span>๐Ÿ”„</span>
810
+ <span id="rerecord-text">Re-record</span>
811
+ </button>
812
+ </div>
813
+ </div>
814
+
815
+ <div id="transcription-container" style="display: none;">
816
+ <h3 class="section-title" id="transcription-title">Processing Result</h3>
817
+
818
+ <!-- Original Transcription -->
819
+ <div class="transcription-section">
820
+ <h4 id="original-text-label">Original Text:</h4>
821
+ <textarea id="original-transcription-box" rows="6" readonly></textarea>
822
+ </div>
823
+
824
+ <!-- Translation Result -->
825
+ <div id="translation-section" class="translation-result" style="display: none;">
826
+ <h4 class="translation-header" id="translated-text-label">Arabic Translation:</h4>
827
+ <textarea id="translated-transcription-box" rows="6" readonly></textarea>
828
+
829
+ <!-- Language Info -->
830
+ <div class="language-info" style="margin-top: 10px; font-size: 0.9em; color: #6c757d;">
831
+ <span id="detected-language">Detected Language: Unknown</span> โ†’
832
+ <span id="target-language-info">Arabic</span>
833
+ </div>
834
+ </div>
835
+
836
+ <!-- Markers Section -->
837
+ <div id="markers-section" style="display: none; margin-top: 20px;">
838
+ <h4 id="markers-label">Important Markers:</h4>
839
+ <div id="markers-list" style="background: #f8f9fa; padding: 10px; border-radius: 6px;"></div>
840
+ </div>
841
+
842
+ <!-- Summary Action Button (Always Visible) -->
843
+ <div id="summary-action-section" style="display: none; margin-top: 24px; text-align: center;">
844
+ <button id="generateSummaryBtn" class="button" style="padding: 12px 24px; font-size: 16px; min-height: auto; background: var(--primary-color); color: white;">
845
+ <span>๐Ÿค–</span>
846
+ <span>Generate Smart Lecture Summary</span>
847
+ </button>
848
+ </div>
849
+
850
+ <!-- Smart Summary Section -->
851
+ <div id="summary-section" style="display: none; margin-top: 24px;">
852
+ <div class="section-title">
853
+ <span>๐Ÿ“ Smart Summary</span>
854
+ </div>
855
+
856
+ <!-- Summary Content -->
857
+ <div id="summary-content" style="background: var(--neutral-100); border-radius: var(--border-radius); padding: 16px; margin-top: 12px;">
858
+
859
+ <!-- Summary Text -->
860
+ <div id="summary-text-section" style="display: none;">
861
+ <h5 style="color: var(--primary-color); margin: 0 0 8px 0; font-size: 14px;">๐Ÿ“‹ Summary:</h5>
862
+ <div id="summary-text" style="background: var(--white); padding: 12px; border-radius: 6px; border: 1px solid var(--neutral-200); line-height: 1.6; font-size: 14px;"></div>
863
+ </div>
864
+
865
+ <!-- Key Points -->
866
+ <div id="key-points-section" style="display: none; margin-top: 16px;">
867
+ <h5 style="color: var(--primary-color); margin: 0 0 8px 0; font-size: 14px;">๐ŸŽฏ Key Points:</h5>
868
+ <ul id="key-points-list" style="background: var(--white); padding: 12px; border-radius: 6px; border: 1px solid var(--neutral-200); margin: 0;"></ul>
869
+ </div>
870
+
871
+ <!-- Review Questions -->
872
+ <div id="questions-section" style="display: none; margin-top: 16px;">
873
+ <h5 style="color: var(--primary-color); margin: 0 0 8px 0; font-size: 14px;">โ“ Review Questions:</h5>
874
+ <ol id="questions-list" style="background: var(--white); padding: 12px; border-radius: 6px; border: 1px solid var(--neutral-200); margin: 0;"></ol>
875
+ </div>
876
+
877
+ <!-- Save Note Section -->
878
+ <div id="save-note-section" style="display: none; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--neutral-200);">
879
+ <h5 style="color: var(--primary-color); margin: 0 0 8px 0; font-size: 14px;">๐Ÿ’พ Save to Notes:</h5>
880
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px;">
881
+ <input type="text" id="noteTitle" placeholder="Note title..." style="padding: 8px; border: 1px solid var(--neutral-300); border-radius: 4px; font-size: 14px;">
882
+ <input type="text" id="noteTags" placeholder="Tags (comma separated)..." style="padding: 8px; border: 1px solid var(--neutral-300); border-radius: 4px; font-size: 14px;">
883
+ </div>
884
+ <button id="saveNoteBtn" class="button" style="width: 100%; padding: 8px; font-size: 14px; min-height: auto;">
885
+ <span>๐Ÿ’พ</span>
886
+ <span>Save Note</span>
887
+ </button>
888
+ </div>
889
+ </div>
890
+ </div>
891
+
892
+ <!-- Notes Library Section -->
893
+ <div id="notes-library-section" style="display: none; margin-top: 24px;">
894
+ <div class="section-title" style="display: flex; align-items: center; justify-content: space-between;">
895
+ <span>๐Ÿ“š ู…ูƒุชุจุฉ ุงู„ู…ุฐูƒุฑุงุช</span>
896
+ <div style="display: flex; gap: 8px;">
897
+ <button id="refreshNotesBtn" class="button" style="padding: 6px 12px; font-size: 12px; min-height: auto;">
898
+ <span>๐Ÿ”„</span>
899
+ <span>ุชุญุฏูŠุซ</span>
900
+ </button>
901
+ <button id="toggleNotesBtn" class="button" style="padding: 6px 12px; font-size: 12px; min-height: auto;">
902
+ <span>๐Ÿ“–</span>
903
+ <span>ุนุฑุถ ุงู„ู…ุฐูƒุฑุงุช</span>
904
+ </button>
905
+ </div>
906
+ </div>
907
+
908
+ <!-- Search and Filter -->
909
+ <div id="notes-controls" style="display: none; margin-top: 12px; padding: 16px; background: var(--neutral-100); border-radius: var(--border-radius);">
910
+ <div style="display: grid; grid-template-columns: 1fr auto; gap: 8px; margin-bottom: 12px;">
911
+ <input type="text" id="notes-search" placeholder="ุงู„ุจุญุซ ููŠ ุงู„ู…ุฐูƒุฑุงุช..." style="padding: 8px; border: 1px solid var(--neutral-300); border-radius: 4px; font-size: 14px;">
912
+ <select id="subject-filter" style="padding: 8px; border: 1px solid var(--neutral-300); border-radius: 4px; font-size: 14px; min-width: 120px;">
913
+ <option value="">ุฌู…ูŠุน ุงู„ู…ูˆุงุฏ</option>
914
+ </select>
915
+ </div>
916
+ <button id="searchNotesBtn" class="button" style="padding: 6px 16px; font-size: 12px; min-height: auto;">
917
+ <span>๐Ÿ”</span>
918
+ <span>ุจุญุซ</span>
919
+ </button>
920
+ </div>
921
+
922
+ <!-- Notes List -->
923
+ <div id="notes-list" style="display: none; margin-top: 12px; max-height: 400px; overflow-y: auto;">
924
+ <!-- Notes will be loaded here -->
925
+ </div>
926
+ </div>
927
+ </div>
928
+
929
+ <!-- Loading Indicator -->
930
+ <div id="loading-indicator" style="display: none; margin: 20px 0;">
931
+ <div class="loading-spinner"></div>
932
+ <span id="loading-text" style="margin-left: 10px;">Processing...</span>
933
+ </div>
934
+
935
+ <!-- Error/Success Messages -->
936
+ <div id="message-container"></div>
937
+ </div>
938
+
939
+ <script>
940
+ // --- Global Variables ---
941
+ let currentLanguage = 'en';
942
+ let translations = {};
943
+
944
+ // --- DOM Elements ---
945
+ const elements = {
946
+ recordButton: document.getElementById('recordButton'),
947
+ stopButton: document.getElementById('stopButton'),
948
+ pauseButton: document.getElementById('pauseButton'),
949
+ markButton: document.getElementById('markButton'),
950
+ extractButton: document.getElementById('extractButton'),
951
+ rerecordButton: document.getElementById('rerecordButton'),
952
+ status: document.getElementById('status'),
953
+ timer: document.getElementById('timer'),
954
+ recordingIndicator: document.getElementById('recording-indicator'),
955
+ audioLevelIndicator: document.getElementById('audio-level-indicator'),
956
+ reviewSection: document.getElementById('review-section'),
957
+ audioPlayback: document.getElementById('audio-playback'),
958
+ transcriptionContainer: document.getElementById('transcription-container'),
959
+ originalTranscriptionBox: document.getElementById('original-transcription-box'),
960
+ translatedTranscriptionBox: document.getElementById('translated-transcription-box'),
961
+ recordingControls: document.getElementById('recording-controls'),
962
+ languageSelect: document.getElementById('languageSelect'),
963
+ translationToggle: document.getElementById('translationToggle'),
964
+ targetLanguage: document.getElementById('targetLanguage'),
965
+ translationSection: document.getElementById('translation-section'),
966
+ markersSection: document.getElementById('markers-section'),
967
+ markersList: document.getElementById('markers-list'),
968
+ loadingIndicator: document.getElementById('loading-indicator'),
969
+ messageContainer: document.getElementById('message-container')
970
+ };
971
+
972
+ // --- State ---
973
+ let state = {
974
+ mediaRecorder: null,
975
+ audioChunks: [],
976
+ audioBlob: null,
977
+ timerInterval: null,
978
+ seconds: 0,
979
+ microphoneStream: null,
980
+ markers: [],
981
+ currentResult: null
982
+ };
983
+
984
+ // --- Event Listeners ---
985
+ elements.recordButton.addEventListener('click', startRecording);
986
+ elements.stopButton.addEventListener('click', stopRecording);
987
+ elements.pauseButton.addEventListener('click', togglePause);
988
+ elements.markButton.addEventListener('click', markTimestamp);
989
+ elements.extractButton.addEventListener('click', processAudio);
990
+ elements.rerecordButton.addEventListener('click', resetRecorder);
991
+ elements.languageSelect.addEventListener('change', changeLanguage);
992
+ elements.translationToggle.addEventListener('change', toggleTranslationUI);
993
+ elements.targetLanguage.addEventListener('change', updateTargetLanguageInfo);
994
+
995
+ // --- Translation System ---
996
+ async function loadTranslations(language) {
997
+ try {
998
+ const response = await fetch(`http://localhost:5001/ui-translations/${language}`);
999
+ if (response.ok) {
1000
+ const data = await response.json();
1001
+ if (data.success) {
1002
+ translations = data.translations;
1003
+ applyTranslations();
1004
+
1005
+ // Update HTML direction for RTL languages
1006
+ if (language === 'ar') {
1007
+ document.documentElement.setAttribute('dir', 'rtl');
1008
+ document.documentElement.setAttribute('lang', 'ar');
1009
+ } else {
1010
+ document.documentElement.setAttribute('dir', 'ltr');
1011
+ document.documentElement.setAttribute('lang', language);
1012
+ }
1013
+ }
1014
+ }
1015
+ } catch (error) {
1016
+ console.error('Failed to load translations:', error);
1017
+ // Fallback to default English
1018
+ translations = getDefaultTranslations();
1019
+ applyTranslations();
1020
+ }
1021
+ }
1022
+
1023
+ function getDefaultTranslations() {
1024
+ return {
1025
+ 'start_recording': 'Start Recording',
1026
+ 'stop_recording': 'Stop Recording',
1027
+ 'pause_recording': 'Pause Recording',
1028
+ 'resume_recording': 'Resume Recording',
1029
+ 'mark_important': 'Mark Important',
1030
+ 'extract_text': 'Extract Text',
1031
+ 'rerecord': 'Re-record',
1032
+ 'processing': 'Processing...',
1033
+ 'ready_to_record': 'Ready to Record',
1034
+ 'recording': 'Recording...',
1035
+ 'paused': 'Paused',
1036
+ 'review_recording': 'Review your recording',
1037
+ 'processing_complete': 'Processing Complete!',
1038
+ 'microphone_permission': 'Microphone permission denied.',
1039
+ 'browser_not_supported': 'Your browser does not support audio recording.',
1040
+ 'transcription': 'Transcription',
1041
+ 'translation': 'Translation',
1042
+ 'markers': 'Important Markers',
1043
+ 'error_occurred': 'An error occurred',
1044
+ 'success': 'Success'
1045
+ };
1046
+ }
1047
+
1048
+ function applyTranslations() {
1049
+ // Update text elements
1050
+ const textMappings = {
1051
+ 'main-title': 'Lecture Recorder',
1052
+ 'subtitle': 'Record and transcribe your lectures with AI-powered translation',
1053
+ 'translation-label': 'Enable Translation:',
1054
+ 'record-text': translations.start_recording || 'Start',
1055
+ 'pause-text': translations.pause_recording || 'Pause',
1056
+ 'stop-text': translations.stop_recording || 'Stop',
1057
+ 'mark-text': translations.mark_important || 'Mark Important',
1058
+ 'extract-text': translations.extract_text || 'Extract Text',
1059
+ 'rerecord-text': translations.rerecord || 'Re-record',
1060
+ 'review-title': translations.review_recording || 'Review Recording',
1061
+ 'transcription-title': translations.processing_complete || 'Processing Result',
1062
+ 'original-text-label': 'Original Text:',
1063
+ 'translated-text-label': getTargetLanguageName() + ' Translation:',
1064
+ 'markers-label': translations.markers || 'Important Markers'
1065
+ };
1066
+
1067
+ Object.entries(textMappings).forEach(([id, text]) => {
1068
+ const element = document.getElementById(id);
1069
+ if (element) {
1070
+ element.textContent = text;
1071
+ }
1072
+ });
1073
+ }
1074
+
1075
+ function getTargetLanguageName() {
1076
+ const langMap = {
1077
+ 'ar': 'Arabic (ุงู„ุนุฑุจูŠุฉ)',
1078
+ 'en': 'English',
1079
+ 'fr': 'French (Franรงais)',
1080
+ 'es': 'Spanish (Espaรฑol)'
1081
+ };
1082
+ return langMap[elements.targetLanguage.value] || 'Translation';
1083
+ }
1084
+
1085
+ async function changeLanguage() {
1086
+ currentLanguage = elements.languageSelect.value;
1087
+ await loadTranslations(currentLanguage);
1088
+ }
1089
+
1090
+ function toggleTranslationUI() {
1091
+ const isEnabled = elements.translationToggle.checked;
1092
+ elements.targetLanguage.disabled = !isEnabled;
1093
+ updateTargetLanguageInfo();
1094
+
1095
+ // Show/hide translation section preview
1096
+ updateTranslationSectionVisibility();
1097
+ }
1098
+
1099
+ function updateTranslationSectionVisibility() {
1100
+ // Always prepare translation section when translation is enabled
1101
+ if (elements.translationToggle.checked) {
1102
+ // Make sure translation section is ready to be shown
1103
+ const translatedLabelElement = document.getElementById('translated-text-label');
1104
+ if (translatedLabelElement) {
1105
+ translatedLabelElement.textContent = getTargetLanguageName() + ' Translation:';
1106
+ }
1107
+ }
1108
+ }
1109
+
1110
+ function updateTargetLanguageInfo() {
1111
+ const targetLangElement = document.getElementById('target-language-info');
1112
+ if (targetLangElement) {
1113
+ targetLangElement.textContent = getTargetLanguageName();
1114
+ }
1115
+
1116
+ const translatedLabelElement = document.getElementById('translated-text-label');
1117
+ if (translatedLabelElement) {
1118
+ translatedLabelElement.textContent = getTargetLanguageName() + ' Translation:';
1119
+ }
1120
+ }
1121
+ // --- Core Recording Functions ---
1122
+ async function startRecording() {
1123
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
1124
+ showMessage(translations.browser_not_supported || 'Your browser does not support audio recording.', 'error');
1125
+ return;
1126
+ }
1127
+ try {
1128
+ state.microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });
1129
+ updateUIMode('recording');
1130
+
1131
+ state.audioChunks = [];
1132
+ state.markers = [];
1133
+ state.mediaRecorder = new MediaRecorder(state.microphoneStream);
1134
+ state.mediaRecorder.ondataavailable = event => state.audioChunks.push(event.data);
1135
+ state.mediaRecorder.onstop = handleRecordingStop;
1136
+
1137
+ state.mediaRecorder.start();
1138
+ startTimer();
1139
+ visualizeAudio(state.microphoneStream);
1140
+ } catch (error) {
1141
+ showMessage(translations.microphone_permission || 'Microphone permission denied.', 'error');
1142
+ console.error('Error accessing microphone:', error);
1143
+ }
1144
+ }
1145
+
1146
+ function stopRecording() {
1147
+ if (state.mediaRecorder && state.mediaRecorder.state !== 'inactive') {
1148
+ state.mediaRecorder.stop();
1149
+ }
1150
+ }
1151
+
1152
+ function handleRecordingStop() {
1153
+ state.audioBlob = new Blob(state.audioChunks, { type: 'audio/wav' });
1154
+ elements.audioPlayback.src = URL.createObjectURL(state.audioBlob);
1155
+ state.microphoneStream.getTracks().forEach(track => track.stop());
1156
+ clearInterval(state.timerInterval);
1157
+ updateUIMode('review');
1158
+ }
1159
+
1160
+ function togglePause() {
1161
+ if (!state.mediaRecorder) return;
1162
+ if (state.mediaRecorder.state === 'recording') {
1163
+ state.mediaRecorder.pause();
1164
+ updateUIMode('paused');
1165
+ } else if (state.mediaRecorder.state === 'paused') {
1166
+ state.mediaRecorder.resume();
1167
+ updateUIMode('recording');
1168
+ }
1169
+ }
1170
+
1171
+ function markTimestamp() {
1172
+ state.markers.push(state.seconds);
1173
+ const markCount = state.markers.length;
1174
+ elements.markButton.innerHTML = `<span>๐Ÿ“Œ</span><span>Marked (${markCount})</span>`;
1175
+ setTimeout(() => {
1176
+ elements.markButton.innerHTML = `<span>๐Ÿ“Œ</span><span>${translations.mark_important || 'Mark Important'}</span>`;
1177
+ }, 1000);
1178
+ }
1179
+
1180
+ async function processAudio() {
1181
+ if (!state.audioBlob) return;
1182
+ updateUIMode('processing');
1183
+
1184
+ const formData = new FormData();
1185
+ formData.append('audio_data', state.audioBlob, 'recording.wav');
1186
+ formData.append('markers', JSON.stringify(state.markers));
1187
+ formData.append('enable_translation', elements.translationToggle.checked.toString());
1188
+ formData.append('target_language', elements.targetLanguage.value);
1189
+
1190
+ try {
1191
+ // Try multiple server URLs in case of port conflicts
1192
+ const serverUrls = [
1193
+ 'http://localhost:5001/record',
1194
+ 'http://127.0.0.1:5001/record',
1195
+ 'http://localhost:5000/record'
1196
+ ];
1197
+
1198
+ let response = null;
1199
+ let lastError = null;
1200
+
1201
+ for (const url of serverUrls) {
1202
+ try {
1203
+ console.log(`Trying server URL: ${url}`);
1204
+ response = await fetch(url, {
1205
+ method: 'POST',
1206
+ body: formData,
1207
+ timeout: 30000 // 30 seconds timeout
1208
+ });
1209
+
1210
+ if (response.ok) {
1211
+ console.log(`Successfully connected to: ${url}`);
1212
+ break;
1213
+ }
1214
+ } catch (error) {
1215
+ console.log(`Failed to connect to ${url}:`, error.message);
1216
+ lastError = error;
1217
+ continue;
1218
+ }
1219
+ }
1220
+
1221
+ if (!response || !response.ok) {
1222
+ throw new Error(`Server connection failed. Last error: ${lastError?.message || 'Unknown error'}`);
1223
+ }
1224
+
1225
+ const result = await response.json();
1226
+
1227
+ // Debug logging for translation analysis
1228
+ console.log('๐Ÿ” SERVER RESPONSE ANALYSIS:');
1229
+ console.log('- Success:', result.success);
1230
+ console.log('- Translation enabled:', result.translation_enabled);
1231
+ console.log('- Translation success:', result.translation_success);
1232
+ console.log('- Original text:', result.original_text?.substring(0, 50) + '...');
1233
+ console.log('- Translated text:', result.translated_text?.substring(0, 50) + '...');
1234
+ console.log('- Target language:', result.target_language);
1235
+ console.log('- Detected language:', result.language_detected);
1236
+ console.log('- Are texts different?:', result.translated_text !== result.original_text);
1237
+
1238
+ if (result.success) {
1239
+ state.currentResult = result;
1240
+ console.log('๐Ÿ’พ STORED RESULT IN STATE:');
1241
+ console.log('- Complete result object:', result);
1242
+ console.log('- Available keys:', Object.keys(result));
1243
+ console.log('- state.currentResult set to:', state.currentResult);
1244
+ displayResults(result);
1245
+ updateUIMode('result');
1246
+ showMessage(translations.success || 'Success!', 'success');
1247
+ } else {
1248
+ throw new Error(result.error || 'Processing failed');
1249
+ }
1250
+ } catch (error) {
1251
+ updateUIMode('review');
1252
+ const errorMessage = `${translations.error_occurred || 'Error'}: ${error.message}`;
1253
+ showMessage(errorMessage, 'error');
1254
+ console.error("Processing Error:", error);
1255
+
1256
+ // Show detailed error for debugging
1257
+ const detailedError = document.createElement('div');
1258
+ detailedError.className = 'error-message';
1259
+ detailedError.innerHTML = `
1260
+ <strong>Connection Error Details:</strong><br>
1261
+ โ€ข Make sure the recorder server is running on port 5001<br>
1262
+ โ€ข Try running: <code>python recorder_server.py</code><br>
1263
+ โ€ข Check if port 5001 is available<br>
1264
+ โ€ข Error: ${error.message}
1265
+ `;
1266
+ elements.messageContainer.appendChild(detailedError);
1267
+ }
1268
+ }
1269
+
1270
+ function displayResults(result) {
1271
+ console.log('๐ŸŽฏ DISPLAY RESULTS FUNCTION:');
1272
+ console.log('- Translation enabled:', result.translation_enabled);
1273
+ console.log('- Has translated text:', !!result.translated_text);
1274
+ console.log('- Translation section element:', !!elements.translationSection);
1275
+
1276
+ // Display original transcription
1277
+ elements.originalTranscriptionBox.value = result.original_text || result.text || '';
1278
+
1279
+ // Always show translation section if translation toggle is enabled or we have translated text
1280
+ const shouldShowTranslation = (
1281
+ elements.translationToggle.checked ||
1282
+ (result.translated_text && result.translated_text.trim())
1283
+ );
1284
+
1285
+ console.log('- Should show translation:', shouldShowTranslation);
1286
+ console.log('- Translation toggle checked:', elements.translationToggle.checked);
1287
+ console.log('- Has translated text content:', !!(result.translated_text && result.translated_text.trim()));
1288
+
1289
+ if (shouldShowTranslation) {
1290
+ console.log('โœ… Showing translation section');
1291
+
1292
+ // Set translated text or show placeholder if no translation
1293
+ if (result.translated_text && result.translated_text.trim()) {
1294
+ elements.translatedTranscriptionBox.value = result.translated_text;
1295
+ } else {
1296
+ elements.translatedTranscriptionBox.value = result.original_text || 'Translation in progress...';
1297
+ }
1298
+
1299
+ elements.translationSection.style.display = 'block';
1300
+
1301
+ // Update language info
1302
+ const detectedLangElement = document.getElementById('detected-language');
1303
+ const targetLangElement = document.getElementById('target-language-info');
1304
+ if (detectedLangElement) {
1305
+ const detectedLang = result.language_detected || 'Unknown';
1306
+ detectedLangElement.textContent = `Detected Language: ${detectedLang}`;
1307
+ }
1308
+ if (targetLangElement) {
1309
+ targetLangElement.textContent = getTargetLanguageName();
1310
+ }
1311
+
1312
+ // Add status indicator for translation quality
1313
+ const translationStatus = document.createElement('div');
1314
+ translationStatus.style.cssText = 'margin-top: 5px; font-size: 0.8em; padding: 3px 8px; border-radius: 4px;';
1315
+
1316
+ if (result.translation_success === true) {
1317
+ translationStatus.style.backgroundColor = '#d4edda';
1318
+ translationStatus.style.color = '#155724';
1319
+ translationStatus.textContent = 'โœ“ Translation successful';
1320
+ } else if (!result.translated_text || result.translated_text === result.original_text) {
1321
+ translationStatus.style.backgroundColor = '#fff3cd';
1322
+ translationStatus.style.color = '#856404';
1323
+ translationStatus.textContent = 'โš  Translation failed - showing original text';
1324
+ } else {
1325
+ translationStatus.style.backgroundColor = '#cce7ff';
1326
+ translationStatus.style.color = '#004085';
1327
+ translationStatus.textContent = '๐Ÿ”„ Translation completed';
1328
+ }
1329
+
1330
+ // Remove existing status if any
1331
+ const existingStatus = elements.translationSection.querySelector('.translation-status');
1332
+ if (existingStatus) {
1333
+ existingStatus.remove();
1334
+ }
1335
+
1336
+ // Add status indicator
1337
+ translationStatus.className = 'translation-status';
1338
+ elements.translationSection.appendChild(translationStatus);
1339
+
1340
+ } else {
1341
+ console.log('โŒ Hiding translation section - conditions not met');
1342
+ elements.translationSection.style.display = 'none';
1343
+ }
1344
+
1345
+ // Display markers if available
1346
+ if (result.markers && result.markers.length > 0) {
1347
+ displayMarkers(result.markers);
1348
+ elements.markersSection.style.display = 'block';
1349
+ } else {
1350
+ elements.markersSection.style.display = 'none';
1351
+ }
1352
+
1353
+ // Show summary button after successful transcription
1354
+ const summaryActionSection = document.getElementById('summary-action-section');
1355
+ if (summaryActionSection) {
1356
+ summaryActionSection.style.display = 'block';
1357
+ }
1358
+ }
1359
+
1360
+ function displayMarkers(markers) {
1361
+ const markersList = elements.markersList;
1362
+ markersList.innerHTML = '';
1363
+
1364
+ markers.forEach((timestamp, index) => {
1365
+ const markerElement = document.createElement('span');
1366
+ markerElement.style.cssText = 'display: inline-block; margin: 2px 5px; padding: 2px 8px; background: #007bff; color: white; border-radius: 12px; font-size: 0.85em;';
1367
+ markerElement.textContent = `${index + 1}: ${formatTime(timestamp)}`;
1368
+ markersList.appendChild(markerElement);
1369
+ });
1370
+ }
1371
+
1372
+ function resetRecorder() {
1373
+ state = {
1374
+ ...state,
1375
+ audioChunks: [],
1376
+ audioBlob: null,
1377
+ seconds: 0,
1378
+ markers: [],
1379
+ currentResult: null,
1380
+ currentSummary: null
1381
+ };
1382
+ clearInterval(state.timerInterval);
1383
+ elements.translationSection.style.display = 'none';
1384
+ elements.markersSection.style.display = 'none';
1385
+
1386
+ // Hide summary sections
1387
+ const summaryActionSection = document.getElementById('summary-action-section');
1388
+ const summarySection = document.getElementById('summary-section');
1389
+ if (summaryActionSection) summaryActionSection.style.display = 'none';
1390
+ if (summarySection) summarySection.style.display = 'none';
1391
+
1392
+ updateUIMode('idle');
1393
+ }
1394
+
1395
+ function updateUIMode(mode) {
1396
+ const isRecording = mode === 'recording';
1397
+ const isPaused = mode === 'paused';
1398
+ const isReview = mode === 'review';
1399
+ const isProcessing = mode === 'processing';
1400
+ const isResult = mode === 'result';
1401
+ const isIdle = mode === 'idle';
1402
+
1403
+ // Button states
1404
+ elements.recordButton.disabled = !isIdle;
1405
+ elements.stopButton.disabled = !isRecording && !isPaused;
1406
+ elements.pauseButton.disabled = !isRecording && !isPaused;
1407
+ elements.markButton.disabled = !isRecording && !isPaused;
1408
+ elements.extractButton.disabled = !isReview;
1409
+ elements.rerecordButton.disabled = !isReview && !isResult;
1410
+
1411
+ // UI Visibility
1412
+ elements.recordingControls.style.display = (isReview || isResult || isProcessing) ? 'none' : 'block';
1413
+ elements.reviewSection.style.display = (isReview || isResult) ? 'block' : 'none';
1414
+ elements.transcriptionContainer.style.display = isResult ? 'block' : 'none';
1415
+ elements.loadingIndicator.style.display = isProcessing ? 'block' : 'none';
1416
+
1417
+ // Recording indicator
1418
+ elements.recordingIndicator.style.display = isRecording ? 'block' : 'none';
1419
+
1420
+ // Button text updates
1421
+ const pauseTextElement = document.getElementById('pause-text');
1422
+ if (pauseTextElement) {
1423
+ pauseTextElement.textContent = isPaused ?
1424
+ (translations.resume_recording || 'Resume') :
1425
+ (translations.pause_recording || 'Pause');
1426
+ }
1427
+
1428
+ // Status updates
1429
+ if (isIdle) {
1430
+ elements.status.textContent = translations.ready_to_record || 'Ready to Record';
1431
+ elements.timer.textContent = '00:00:00';
1432
+ elements.audioLevelIndicator.style.width = '0%';
1433
+ } else if (isRecording) {
1434
+ elements.status.textContent = translations.recording || 'Recording...';
1435
+ } else if (isPaused) {
1436
+ elements.status.textContent = translations.paused || 'Paused';
1437
+ } else if (isReview) {
1438
+ elements.status.textContent = translations.review_recording || 'Review your recording';
1439
+ } else if (isProcessing) {
1440
+ elements.status.textContent = translations.processing || 'Processing... please wait';
1441
+ const loadingTextElement = document.getElementById('loading-text');
1442
+ if (loadingTextElement) {
1443
+ loadingTextElement.textContent = translations.processing || 'Processing...';
1444
+ }
1445
+ } else if (isResult) {
1446
+ elements.status.textContent = translations.processing_complete || 'Processing Complete!';
1447
+ }
1448
+ }
1449
+
1450
+ function startTimer() {
1451
+ state.seconds = 0;
1452
+ state.timerInterval = setInterval(() => {
1453
+ state.seconds++;
1454
+ elements.timer.textContent = formatTime(state.seconds);
1455
+ }, 1000);
1456
+ }
1457
+
1458
+ function formatTime(totalSeconds) {
1459
+ const hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0');
1460
+ const minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0');
1461
+ const seconds = (totalSeconds % 60).toString().padStart(2, '0');
1462
+ return `${hours}:${minutes}:${seconds}`;
1463
+ }
1464
+
1465
+ function visualizeAudio(stream) {
1466
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
1467
+ const analyser = audioContext.createAnalyser();
1468
+ const microphone = audioContext.createMediaStreamSource(stream);
1469
+ microphone.connect(analyser);
1470
+ analyser.fftSize = 256;
1471
+ const dataArray = new Uint8Array(analyser.frequencyBinCount);
1472
+
1473
+ function draw() {
1474
+ if (state.mediaRecorder && state.mediaRecorder.state === 'inactive') return;
1475
+ requestAnimationFrame(draw);
1476
+ analyser.getByteFrequencyData(dataArray);
1477
+ const avg = dataArray.reduce((a, b) => a + b, 0) / dataArray.length;
1478
+ elements.audioLevelIndicator.style.width = `${(avg / 128) * 100}%`;
1479
+ }
1480
+ draw();
1481
+ }
1482
+
1483
+ function showMessage(message, type = 'info') {
1484
+ const messageElement = document.createElement('div');
1485
+ messageElement.className = `${type}-message`;
1486
+ messageElement.textContent = message;
1487
+
1488
+ elements.messageContainer.innerHTML = '';
1489
+ elements.messageContainer.appendChild(messageElement);
1490
+
1491
+ // Auto-hide after 5 seconds
1492
+ setTimeout(() => {
1493
+ if (elements.messageContainer.contains(messageElement)) {
1494
+ elements.messageContainer.removeChild(messageElement);
1495
+ }
1496
+ }, 5000);
1497
+ }
1498
+
1499
+ // --- Keyboard Shortcuts ---
1500
+ document.addEventListener('keydown', function(event) {
1501
+ if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') return;
1502
+
1503
+ switch(event.code) {
1504
+ case 'Space':
1505
+ event.preventDefault();
1506
+ if (!elements.recordButton.disabled) {
1507
+ startRecording();
1508
+ } else if (!elements.stopButton.disabled) {
1509
+ stopRecording();
1510
+ }
1511
+ break;
1512
+ case 'KeyM':
1513
+ if (!elements.markButton.disabled) {
1514
+ markTimestamp();
1515
+ }
1516
+ break;
1517
+ case 'KeyP':
1518
+ if (!elements.pauseButton.disabled) {
1519
+ togglePause();
1520
+ }
1521
+ break;
1522
+ case 'KeyR':
1523
+ if (!elements.rerecordButton.disabled) {
1524
+ resetRecorder();
1525
+ }
1526
+ break;
1527
+ }
1528
+ });
1529
+
1530
+ // --- Server Status Check ---
1531
+ async function checkServerStatus() {
1532
+ const statusIndicator = document.createElement('div');
1533
+ statusIndicator.id = 'server-status';
1534
+ statusIndicator.style.cssText = `
1535
+ position: fixed;
1536
+ top: 10px;
1537
+ left: 10px;
1538
+ padding: 8px 12px;
1539
+ border-radius: 6px;
1540
+ font-size: 12px;
1541
+ font-weight: bold;
1542
+ z-index: 1000;
1543
+ transition: all 0.3s;
1544
+ `;
1545
+ document.body.appendChild(statusIndicator);
1546
+
1547
+ try {
1548
+ const response = await fetch('http://localhost:5001/record', { method: 'GET' });
1549
+ if (response.ok) {
1550
+ statusIndicator.textContent = '๐ŸŸข Server Connected';
1551
+ statusIndicator.style.background = '#d4edda';
1552
+ statusIndicator.style.color = '#155724';
1553
+ statusIndicator.style.border = '1px solid #c3e6cb';
1554
+ } else {
1555
+ throw new Error('Server not responding');
1556
+ }
1557
+ } catch (error) {
1558
+ statusIndicator.textContent = '๐Ÿ”ด Server Disconnected';
1559
+ statusIndicator.style.background = '#f8d7da';
1560
+ statusIndicator.style.color = '#721c24';
1561
+ statusIndicator.style.border = '1px solid #f5c6cb';
1562
+
1563
+ // Show connection instructions
1564
+ setTimeout(() => {
1565
+ if (statusIndicator.parentNode) {
1566
+ statusIndicator.innerHTML = `
1567
+ ๐Ÿ”ด Server Offline<br>
1568
+ <small>Run: python recorder_server.py</small>
1569
+ `;
1570
+ }
1571
+ }, 2000);
1572
+ }
1573
+ }
1574
+
1575
+ // --- Summary and Notes System ---
1576
+ async function generateSummary() {
1577
+ console.log('๐Ÿค– GENERATE SUMMARY DEBUG:');
1578
+ console.log('- state.currentResult:', state.currentResult);
1579
+ console.log('- original_text:', state.currentResult?.original_text);
1580
+ console.log('- text:', state.currentResult?.text);
1581
+ console.log('- translated_text:', state.currentResult?.translated_text);
1582
+
1583
+ if (!state.currentResult) {
1584
+ console.log('โŒ No currentResult found');
1585
+ showMessage('ู„ุง ุชูˆุฌุฏ ู†ุชุงุฆุฌ ู…ุชุงุญุฉ ู„ู„ู…ู„ุฎุต', 'error');
1586
+ return;
1587
+ }
1588
+
1589
+ const textToSummarize = state.currentResult.original_text || state.currentResult.text || state.currentResult.translated_text;
1590
+
1591
+ if (!textToSummarize || textToSummarize.trim() === '') {
1592
+ console.log('โŒ No text available for summarization');
1593
+ console.log('Available properties:', Object.keys(state.currentResult));
1594
+ showMessage('ู„ุง ูŠูˆุฌุฏ ู†ุต ู…ุชุงุญ ู„ู„ู…ู„ุฎุต', 'error');
1595
+ return;
1596
+ }
1597
+
1598
+ console.log('โœ… Text to summarize:', textToSummarize.substring(0, 100) + '...');
1599
+
1600
+ const summaryBtn = document.getElementById('generateSummaryBtn');
1601
+ const originalContent = summaryBtn.innerHTML;
1602
+ summaryBtn.disabled = true;
1603
+ summaryBtn.innerHTML = '<span>โณ</span><span>ุฌุงุฑูŠ ุฅู†ุดุงุก ุงู„ู…ู„ุฎุต...</span>';
1604
+
1605
+ try {
1606
+ console.log('๐Ÿ“ค Sending summarization request...');
1607
+ console.log('๐Ÿ“ค Request payload:', {
1608
+ text: textToSummarize.substring(0, 100) + '...',
1609
+ language: elements.targetLanguage.value || 'arabic'
1610
+ });
1611
+
1612
+ const response = await fetch('http://localhost:5001/summarize', {
1613
+ method: 'POST',
1614
+ headers: {
1615
+ 'Content-Type': 'application/json',
1616
+ 'Accept': 'application/json'
1617
+ },
1618
+ body: JSON.stringify({
1619
+ text: textToSummarize,
1620
+ language: elements.targetLanguage.value || 'arabic',
1621
+ type: 'full'
1622
+ })
1623
+ });
1624
+
1625
+ console.log('๐Ÿ“ฅ Response status:', response.status);
1626
+ console.log('๐Ÿ“ฅ Response headers:', response.headers);
1627
+
1628
+ if (response.ok) {
1629
+ const data = await response.json();
1630
+ console.log('๐Ÿ“ฅ Summarization response:', data);
1631
+
1632
+ if (data.success) {
1633
+ // ุงู„ุชุนุงู…ู„ ู…ุน ุงู„ุงุณุชุฌุงุจุฉ ุงู„ุฌุฏูŠุฏุฉ
1634
+ if (data.summary) {
1635
+ displaySummaryResults(data.summary);
1636
+ showMessage('ุชู… ุฅู†ุดุงุก ุงู„ู…ู„ุฎุต ุจู†ุฌุงุญ!', 'success');
1637
+ } else {
1638
+ console.error('โŒ No summary in response:', data);
1639
+ showMessage('ู„ู… ูŠุชู… ุงู„ุนุซูˆุฑ ุนู„ู‰ ู…ู„ุฎุต ููŠ ุงู„ุงุณุชุฌุงุจุฉ', 'error');
1640
+ }
1641
+ } else {
1642
+ throw new Error(data.error || 'ูุดู„ ููŠ ุฅู†ุดุงุก ุงู„ู…ู„ุฎุต');
1643
+ }
1644
+ } else {
1645
+ const errorText = await response.text();
1646
+ console.error('โŒ HTTP Error:', response.status, errorText);
1647
+ throw new Error(`ุฎุทุฃ ููŠ ุงู„ุดุจูƒุฉ: ${response.status} - ${errorText}`);
1648
+ }
1649
+ } catch (error) {
1650
+ console.error('โŒ Summary generation error:', error);
1651
+
1652
+ // ุฑุณุงุฆู„ ุฎุทุฃ ู…ุญุฏุฏุฉ
1653
+ let errorMessage = 'ูุดู„ ููŠ ุฅู†ุดุงุก ุงู„ู…ู„ุฎุต';
1654
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
1655
+ errorMessage = 'ู„ุง ูŠู…ูƒู† ุงู„ุงุชุตุงู„ ุจุฎุงุฏู… ุงู„ุชู„ุฎูŠุต - ุชุฃูƒุฏ ู…ู† ุชุดุบูŠู„ ุงู„ุฎุงุฏู…';
1656
+ } else if (error.message.includes('CORS')) {
1657
+ errorMessage = 'ู…ุดูƒู„ุฉ ููŠ ุฅุนุฏุงุฏุงุช CORS - ูŠุฌุจ ุฅุนุงุฏุฉ ุชุดุบูŠู„ ุงู„ุฎุงุฏู…';
1658
+ } else {
1659
+ errorMessage = error.message;
1660
+ }
1661
+
1662
+ showMessage(errorMessage, 'error');
1663
+ } finally {
1664
+ summaryBtn.disabled = false;
1665
+ summaryBtn.innerHTML = originalContent;
1666
+ }
1667
+ }
1668
+
1669
+ function displaySummaryResults(summary) {
1670
+ console.log('๐Ÿ“ Displaying summary results:', summary);
1671
+
1672
+ // Show the summary section
1673
+ document.getElementById('summary-section').style.display = 'block';
1674
+
1675
+ // ุงู„ุชุนุงู…ู„ ู…ุน ุชู†ุณูŠู‚ ุงู„ุงุณุชุฌุงุจุฉ - ู‚ุฏ ูŠูƒูˆู† ุงู„ู†ุต ู…ุจุงุดุฑุฉ ุฃูˆ ูƒุงุฆู†
1676
+ let summaryText = '';
1677
+ if (typeof summary === 'string') {
1678
+ summaryText = summary;
1679
+ } else if (summary && summary.main_summary) {
1680
+ summaryText = summary.main_summary;
1681
+ } else if (summary && typeof summary === 'object') {
1682
+ // ุฅุฐุง ูƒุงู†ุช ุงู„ุงุณุชุฌุงุจุฉ ูƒุงุฆู†ุŒ ู†ุฃุฎุฐ ุงู„ู…ุญุชูˆู‰ ุงู„ู†ุตูŠ
1683
+ summaryText = JSON.stringify(summary, null, 2);
1684
+ }
1685
+
1686
+ // Display main summary
1687
+ const summaryTextSection = document.getElementById('summary-text-section');
1688
+ const summaryTextElement = document.getElementById('summary-text');
1689
+ if (summaryTextSection && summaryTextElement && summaryText) {
1690
+ summaryTextElement.innerHTML = summaryText.replace(/\n/g, '<br>');
1691
+ summaryTextSection.style.display = 'block';
1692
+ }
1693
+
1694
+ // Display key points (ุฅุฐุง ูƒุงู†ุช ู…ุชูˆูุฑุฉ)
1695
+ const keyPointsSection = document.getElementById('key-points-section');
1696
+ const keyPointsList = document.getElementById('key-points-list');
1697
+ if (keyPointsSection && keyPointsList && summary && summary.key_points && Array.isArray(summary.key_points)) {
1698
+ keyPointsList.innerHTML = summary.key_points.map(point => `<li>${point}</li>`).join('');
1699
+ keyPointsSection.style.display = 'block';
1700
+ }
1701
+
1702
+ // Display review questions (ุฅุฐุง ูƒุงู†ุช ู…ุชูˆูุฑุฉ)
1703
+ const questionsSection = document.getElementById('questions-section');
1704
+ const questionsList = document.getElementById('questions-list');
1705
+ if (questionsSection && questionsList && summary && summary.review_questions && Array.isArray(summary.review_questions)) {
1706
+ questionsList.innerHTML = summary.review_questions.map(question => `<li>${question}</li>`).join('');
1707
+ questionsSection.style.display = 'block';
1708
+ }
1709
+
1710
+ // Show save note section
1711
+ const saveNoteSection = document.getElementById('save-note-section');
1712
+ if (saveNoteSection) {
1713
+ saveNoteSection.style.display = 'block';
1714
+ }
1715
+
1716
+ // Store current summary in state for saving
1717
+ state.currentSummary = summary;
1718
+ }
1719
+
1720
+ async function saveNote() {
1721
+ if (!state.currentSummary) {
1722
+ showMessage('ู„ุง ูŠูˆุฌุฏ ู…ู„ุฎุต ู…ุชุงุญ ู„ู„ุญูุธ', 'error');
1723
+ return;
1724
+ }
1725
+
1726
+ const title = document.getElementById('noteTitle').value.trim();
1727
+ const tags = document.getElementById('noteTags').value.trim();
1728
+
1729
+ if (!title) {
1730
+ showMessage('ูŠุฑุฌู‰ ุฅุฏุฎุงู„ ุนู†ูˆุงู† ู„ู„ู…ุฐูƒุฑุฉ', 'error');
1731
+ return;
1732
+ }
1733
+
1734
+ const saveBtn = document.getElementById('saveNoteBtn');
1735
+ const originalText = saveBtn.textContent;
1736
+ saveBtn.disabled = true;
1737
+ saveBtn.textContent = 'ุฌุงุฑูŠ ุงู„ุญูุธ...';
1738
+
1739
+ try {
1740
+ const contentToSave = state.currentResult.original_text || state.currentResult.text;
1741
+ const response = await fetch('http://localhost:5001/notes', {
1742
+ method: 'POST',
1743
+ headers: { 'Content-Type': 'application/json' },
1744
+ body: JSON.stringify({
1745
+ title: title,
1746
+ content: contentToSave,
1747
+ summary: state.currentSummary,
1748
+ tags: tags ? tags.split(',').map(tag => tag.trim()) : [],
1749
+ markers: state.markers
1750
+ })
1751
+ });
1752
+
1753
+ if (response.ok) {
1754
+ const data = await response.json();
1755
+ if (data.success) {
1756
+ showMessage('ุชู… ุญูุธ ุงู„ู…ุฐูƒุฑุฉ ุจู†ุฌุงุญ!', 'success');
1757
+ // Clear form
1758
+ document.getElementById('noteTitle').value = '';
1759
+ document.getElementById('noteTags').value = '';
1760
+ // Refresh notes list
1761
+ loadNotesList();
1762
+ } else {
1763
+ throw new Error(data.error || 'Failed to save note');
1764
+ }
1765
+ } else {
1766
+ throw new Error('Network error while saving note');
1767
+ }
1768
+ } catch (error) {
1769
+ console.error('Note saving error:', error);
1770
+ showMessage('ูุดู„ ููŠ ุญูุธ ุงู„ู…ุฐูƒุฑุฉ: ' + error.message, 'error');
1771
+ } finally {
1772
+ saveBtn.disabled = false;
1773
+ saveBtn.textContent = originalText;
1774
+ }
1775
+ }
1776
+
1777
+ async function loadNotesList() {
1778
+ try {
1779
+ const response = await fetch('http://localhost:5001/notes');
1780
+ if (response.ok) {
1781
+ const data = await response.json();
1782
+ if (data.success) {
1783
+ displayNotesList(data.notes);
1784
+ }
1785
+ }
1786
+ } catch (error) {
1787
+ console.error('Failed to load notes:', error);
1788
+ }
1789
+ }
1790
+
1791
+ function displayNotesList(notes) {
1792
+ const notesContainer = document.getElementById('notesContainer');
1793
+
1794
+ if (notes.length === 0) {
1795
+ notesContainer.innerHTML = '<p class="text-center text-muted">No saved notes yet</p>';
1796
+ return;
1797
+ }
1798
+
1799
+ notesContainer.innerHTML = notes.map(note => `
1800
+ <div class="note-item" data-note-id="${note.id}">
1801
+ <div class="note-header">
1802
+ <h4>${note.title}</h4>
1803
+ <span class="note-date">${new Date(note.created_at).toLocaleDateString()}</span>
1804
+ </div>
1805
+ <div class="note-summary">
1806
+ ${note.summary ? note.summary.main_summary.substring(0, 150) + '...' : 'No summary available'}
1807
+ </div>
1808
+ <div class="note-tags">
1809
+ ${note.tags ? note.tags.map(tag => `<span class="tag">${tag}</span>`).join('') : ''}
1810
+ </div>
1811
+ <div class="note-actions">
1812
+ <button onclick="viewNote(${note.id})" class="btn-secondary">View</button>
1813
+ <button onclick="deleteNote(${note.id})" class="btn-danger">Delete</button>
1814
+ </div>
1815
+ </div>
1816
+ `).join('');
1817
+ }
1818
+
1819
+ async function viewNote(noteId) {
1820
+ try {
1821
+ const response = await fetch(`http://localhost:5001/notes/${noteId}`);
1822
+ if (response.ok) {
1823
+ const data = await response.json();
1824
+ if (data.success) {
1825
+ displayNoteDetails(data.note);
1826
+ }
1827
+ }
1828
+ } catch (error) {
1829
+ console.error('Failed to load note:', error);
1830
+ showMessage('Failed to load note details', 'error');
1831
+ }
1832
+ }
1833
+
1834
+ function displayNoteDetails(note) {
1835
+ // Create modal or expand view for note details
1836
+ const modal = document.createElement('div');
1837
+ modal.className = 'note-modal';
1838
+ modal.innerHTML = `
1839
+ <div class="note-modal-content">
1840
+ <div class="note-modal-header">
1841
+ <h2>${note.title}</h2>
1842
+ <button onclick="closeNoteModal()" class="close-btn">&times;</button>
1843
+ </div>
1844
+ <div class="note-modal-body">
1845
+ <div class="note-section">
1846
+ <h3>Summary</h3>
1847
+ <p>${note.summary ? note.summary.main_summary : 'No summary available'}</p>
1848
+ </div>
1849
+ ${note.summary && note.summary.key_points ? `
1850
+ <div class="note-section">
1851
+ <h3>Key Points</h3>
1852
+ <ul>${note.summary.key_points.map(point => `<li>${point}</li>`).join('')}</ul>
1853
+ </div>
1854
+ ` : ''}
1855
+ <div class="note-section">
1856
+ <h3>Full Transcription</h3>
1857
+ <p>${note.content}</p>
1858
+ </div>
1859
+ ${note.tags && note.tags.length > 0 ? `
1860
+ <div class="note-section">
1861
+ <h3>Tags</h3>
1862
+ <div class="note-tags">
1863
+ ${note.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
1864
+ </div>
1865
+ </div>
1866
+ ` : ''}
1867
+ </div>
1868
+ </div>
1869
+ `;
1870
+ document.body.appendChild(modal);
1871
+ }
1872
+
1873
+ function closeNoteModal() {
1874
+ const modal = document.querySelector('.note-modal');
1875
+ if (modal) {
1876
+ modal.remove();
1877
+ }
1878
+ }
1879
+
1880
+ async function deleteNote(noteId) {
1881
+ if (!confirm('Are you sure you want to delete this note?')) {
1882
+ return;
1883
+ }
1884
+
1885
+ try {
1886
+ const response = await fetch(`http://localhost:5001/notes/${noteId}`, {
1887
+ method: 'DELETE'
1888
+ });
1889
+
1890
+ if (response.ok) {
1891
+ const data = await response.json();
1892
+ if (data.success) {
1893
+ showMessage('Note deleted successfully', 'success');
1894
+ loadNotesList();
1895
+ } else {
1896
+ throw new Error(data.error || 'Failed to delete note');
1897
+ }
1898
+ } else {
1899
+ throw new Error('Network error while deleting note');
1900
+ }
1901
+ } catch (error) {
1902
+ console.error('Delete note error:', error);
1903
+ showMessage('Failed to delete note: ' + error.message, 'error');
1904
+ }
1905
+ }
1906
+
1907
+ function searchNotes() {
1908
+ const searchTerm = document.getElementById('notesSearch').value.toLowerCase();
1909
+ const noteItems = document.querySelectorAll('.note-item');
1910
+
1911
+ noteItems.forEach(item => {
1912
+ const title = item.querySelector('h4').textContent.toLowerCase();
1913
+ const summary = item.querySelector('.note-summary').textContent.toLowerCase();
1914
+ const tags = item.querySelector('.note-tags').textContent.toLowerCase();
1915
+
1916
+ if (title.includes(searchTerm) || summary.includes(searchTerm) || tags.includes(searchTerm)) {
1917
+ item.style.display = 'block';
1918
+ } else {
1919
+ item.style.display = 'none';
1920
+ }
1921
+ });
1922
+ }
1923
+
1924
+ // --- Initialization ---
1925
+ async function initialize() {
1926
+ await loadTranslations(currentLanguage);
1927
+ await checkServerStatus();
1928
+ toggleTranslationUI();
1929
+ updateTargetLanguageInfo();
1930
+ resetRecorder();
1931
+
1932
+ // Load existing notes
1933
+ loadNotesList();
1934
+
1935
+ // Add event listeners for summary and notes
1936
+ const generateSummaryBtn = document.getElementById('generateSummaryBtn');
1937
+ const saveNoteBtn = document.getElementById('saveNoteBtn');
1938
+ const notesSearch = document.getElementById('notesSearch');
1939
+
1940
+ if (generateSummaryBtn) {
1941
+ generateSummaryBtn.addEventListener('click', generateSummary);
1942
+ }
1943
+ if (saveNoteBtn) {
1944
+ saveNoteBtn.addEventListener('click', saveNote);
1945
+ }
1946
+ if (notesSearch) {
1947
+ notesSearch.addEventListener('input', searchNotes);
1948
+ }
1949
+
1950
+ // Check server status every 30 seconds
1951
+ setInterval(checkServerStatus, 30000);
1952
+ }
1953
+
1954
+ // Start the application
1955
+ initialize();
1956
+ </script>
1957
+ </body>
1958
+ </html>
test_recording.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ ุงุฎุชุจุงุฑ ุฒุฑ ุงู„ุชุณุฌูŠู„ - ู…ุญุงูƒุงุฉ ู…ู„ู ุตูˆุชูŠ
5
+ """
6
+
7
+ import requests
8
+ import io
9
+ import wave
10
+ import struct
11
+ import random
12
+
13
+ def create_test_audio():
14
+ """ุฅู†ุดุงุก ู…ู„ู ุตูˆุชูŠ ุชุฌุฑูŠุจูŠ"""
15
+ # ุฅู†ุดุงุก ุตูˆุช ุชุฌุฑูŠุจูŠ (silence ู…ุน ุจุนุถ ุงู„ุถูˆุถุงุก)
16
+ duration = 2 # ุซุงู†ูŠุชูŠู†
17
+ sample_rate = 44100
18
+ samples = duration * sample_rate
19
+
20
+ # ุฅู†ุดุงุก ุจูŠุงู†ุงุช ุตูˆุชูŠุฉ ุจุณูŠุทุฉ
21
+ audio_data = []
22
+ for i in range(samples):
23
+ # ุถูˆุถุงุก ุจุณูŠุทุฉ
24
+ value = int(random.uniform(-1000, 1000))
25
+ audio_data.append(value)
26
+
27
+ # ุฅู†ุดุงุก ู…ู„ู WAV ููŠ ุงู„ุฐุงูƒุฑุฉ
28
+ buffer = io.BytesIO()
29
+ with wave.open(buffer, 'wb') as wav_file:
30
+ wav_file.setnchannels(1) # Mono
31
+ wav_file.setsampwidth(2) # 16-bit
32
+ wav_file.setframerate(sample_rate)
33
+
34
+ # ูƒุชุงุจุฉ ุงู„ุจูŠุงู†ุงุช
35
+ for sample in audio_data:
36
+ wav_file.writeframes(struct.pack('<h', sample))
37
+
38
+ buffer.seek(0)
39
+ return buffer
40
+
41
+ def test_recording_endpoint():
42
+ """ุงุฎุชุจุงุฑ endpoint ุงู„ุชุณุฌูŠู„"""
43
+ print("๐ŸŽ™๏ธ ุงุฎุชุจุงุฑ endpoint ุงู„ุชุณุฌูŠู„...")
44
+
45
+ try:
46
+ # ุฅู†ุดุงุก ู…ู„ู ุตูˆุชูŠ ุชุฌุฑูŠุจูŠ
47
+ audio_buffer = create_test_audio()
48
+
49
+ # ุฅุนุฏุงุฏ ุงู„ุจูŠุงู†ุงุช
50
+ files = {
51
+ 'audio_data': ('test_audio.wav', audio_buffer, 'audio/wav')
52
+ }
53
+
54
+ data = {
55
+ 'markers': '[]',
56
+ 'target_language': 'ar',
57
+ 'enable_translation': 'true'
58
+ }
59
+
60
+ print("๐Ÿ“ค ุฅุฑุณุงู„ ุทู„ุจ ุงู„ุชุณุฌูŠู„...")
61
+ response = requests.post(
62
+ 'http://localhost:5001/record',
63
+ files=files,
64
+ data=data,
65
+ timeout=30
66
+ )
67
+
68
+ print(f"Status Code: {response.status_code}")
69
+
70
+ if response.status_code == 200:
71
+ result = response.json()
72
+ print(f"Success: {result.get('success')}")
73
+
74
+ if result.get('success'):
75
+ print("โœ… ุงู„ุชุณุฌูŠู„ ู†ุฌุญ!")
76
+ print(f"Original Text: {result.get('original_text', 'ุบูŠุฑ ู…ูˆุฌูˆุฏ')}")
77
+ print(f"Translation Enabled: {result.get('translation_enabled')}")
78
+ print(f"Target Language: {result.get('target_language')}")
79
+ return True
80
+ else:
81
+ print(f"โŒ ูุดู„ ุงู„ุชุณุฌูŠู„: {result.get('error')}")
82
+ return False
83
+ else:
84
+ print(f"โŒ ุฎุทุฃ HTTP: {response.status_code}")
85
+ print(f"Response: {response.text}")
86
+ return False
87
+
88
+ except Exception as e:
89
+ print(f"โŒ ุฎุทุฃ ููŠ ุงุฎุชุจุงุฑ ุงู„ุชุณุฌูŠู„: {e}")
90
+ return False
91
+
92
+ def test_options_request():
93
+ """ุงุฎุชุจุงุฑ ุทู„ุจ OPTIONS ู„ู„ุชุณุฌูŠู„"""
94
+ print("\n๐Ÿ”ง ุงุฎุชุจุงุฑ ุทู„ุจ OPTIONS ู„ู„ุชุณุฌูŠู„...")
95
+
96
+ try:
97
+ response = requests.options('http://localhost:5001/record', timeout=5)
98
+ print(f"Status Code: {response.status_code}")
99
+
100
+ if response.status_code == 204:
101
+ print("โœ… ุทู„ุจ OPTIONS ู†ุฌุญ")
102
+ return True
103
+ else:
104
+ print(f"โŒ ู…ุดูƒู„ุฉ ููŠ ุทู„ุจ OPTIONS: {response.status_code}")
105
+ return False
106
+
107
+ except Exception as e:
108
+ print(f"โŒ ุฎุทุฃ ููŠ ุทู„ุจ OPTIONS: {e}")
109
+ return False
110
+
111
+ def main():
112
+ """ุงู„ุฏุงู„ุฉ ุงู„ุฑุฆูŠุณูŠุฉ ู„ุงุฎุชุจุงุฑ ุงู„ุชุณุฌูŠู„"""
113
+ print("๐Ÿš€ ุงุฎุชุจุงุฑ ูˆุธูŠูุฉ ุงู„ุชุณุฌูŠู„")
114
+ print("=" * 50)
115
+
116
+ # ุงุฎุชุจุงุฑ OPTIONS ุฃูˆู„ุงู‹
117
+ options_ok = test_options_request()
118
+
119
+ if options_ok:
120
+ # ุงุฎุชุจุงุฑ ุงู„ุชุณุฌูŠู„ ุงู„ูุนู„ูŠ
121
+ recording_ok = test_recording_endpoint()
122
+
123
+ if recording_ok:
124
+ print("\n๐ŸŽ‰ ุฌู…ูŠุน ุงุฎุชุจุงุฑุงุช ุงู„ุชุณุฌูŠู„ ู†ุฌุญุช!")
125
+ print("โœ… ุฒุฑ ุงู„ุชุณุฌูŠู„ ูŠุนู…ู„ ุจุดูƒู„ ู…ุซุงู„ูŠ")
126
+ else:
127
+ print("\nโŒ ู‡ู†ุงูƒ ู…ุดูƒู„ุฉ ููŠ ูˆุธูŠูุฉ ุงู„ุชุณุฌูŠู„")
128
+ else:
129
+ print("\nโŒ ู…ุดูƒู„ุฉ ููŠ CORS ู„ู„ุชุณุฌูŠู„")
130
+
131
+ print("=" * 50)
132
+
133
+ if __name__ == "__main__":
134
+ main()
test_server_translation.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # test_server_translation.py - Test server translation response
2
+
3
+ import requests
4
+ import json
5
+ import tempfile
6
+ import wave
7
+ import numpy as np
8
+
9
+ def create_test_audio():
10
+ """Create a simple test audio file"""
11
+ sample_rate = 16000
12
+ duration = 2 # seconds
13
+ frequency = 440 # Hz (A note)
14
+
15
+ # Generate sine wave
16
+ t = np.linspace(0, duration, int(sample_rate * duration))
17
+ audio_data = (0.3 * np.sin(2 * np.pi * frequency * t) * 32767).astype(np.int16)
18
+
19
+ # Create temporary WAV file
20
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp_file:
21
+ with wave.open(tmp_file.name, 'w') as wav_file:
22
+ wav_file.setnchannels(1) # Mono
23
+ wav_file.setsampwidth(2) # 2 bytes per sample
24
+ wav_file.setframerate(sample_rate)
25
+ wav_file.writeframes(audio_data.tobytes())
26
+ return tmp_file.name
27
+
28
+ def test_server_translation():
29
+ print("๐Ÿ” Testing Server Translation Response...")
30
+
31
+ # Create test audio
32
+ audio_file = create_test_audio()
33
+ print(f"๐Ÿ“ Created test audio: {audio_file}")
34
+
35
+ try:
36
+ # Prepare request data
37
+ with open(audio_file, 'rb') as f:
38
+ files = {'audio': f}
39
+ data = {
40
+ 'target_language': 'ar',
41
+ 'enable_translation': 'true',
42
+ 'markers': '[]'
43
+ }
44
+
45
+ # Send request to server
46
+ response = requests.post('http://localhost:5001/record', files=files, data=data)
47
+
48
+ if response.status_code == 200:
49
+ result = response.json()
50
+ print(f"โœ… Server Response Received")
51
+ print(f"๐Ÿ“Š Success: {result.get('success')}")
52
+ print(f"๐Ÿ”ค Original text: {result.get('original_text', 'None')}")
53
+ print(f"๐Ÿ”„ Translated text: {result.get('translated_text', 'None')}")
54
+ print(f"๐ŸŒ Translation enabled: {result.get('translation_enabled')}")
55
+ print(f"โœ… Translation success: {result.get('translation_success')}")
56
+ print(f"๐ŸŒ Target language: {result.get('target_language')}")
57
+ print(f"๐Ÿ” Detected language: {result.get('language_detected')}")
58
+
59
+ # Check if texts are different
60
+ original = result.get('original_text', '')
61
+ translated = result.get('translated_text', '')
62
+ print(f"๐Ÿ”„ Texts are different: {original != translated}")
63
+
64
+ else:
65
+ print(f"โŒ Server error: {response.status_code}")
66
+ print(f"Response: {response.text}")
67
+
68
+ except Exception as e:
69
+ print(f"โŒ Test failed: {str(e)}")
70
+
71
+ finally:
72
+ # Clean up
73
+ import os
74
+ if os.path.exists(audio_file):
75
+ os.unlink(audio_file)
76
+
77
+ if __name__ == "__main__":
78
+ test_server_translation()
test_servers.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ print('๐Ÿงช Testing servers...')
4
+
5
+ try:
6
+ r1 = requests.get('http://localhost:5050', timeout=5)
7
+ print(f'โœ… Streamlit: OK - {r1.status_code}')
8
+ except Exception as e:
9
+ print(f'โŒ Streamlit: FAILED - {e}')
10
+
11
+ try:
12
+ r2 = requests.get('http://localhost:5001/record', timeout=5)
13
+ print(f'โœ… Recorder: OK - {r2.status_code}')
14
+ except Exception as e:
15
+ print(f'โŒ Recorder: FAILED - {e}')
16
+
17
+ print('๐Ÿ Test completed')
test_summarize.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ ุงุฎุชุจุงุฑ ู…ุดูƒู„ุฉ endpoint ุงู„ุชู„ุฎูŠุต
5
+ """
6
+
7
+ import requests
8
+ import json
9
+
10
+ def test_cors_preflight():
11
+ """ุงุฎุชุจุงุฑ ุทู„ุจ OPTIONS (CORS preflight)"""
12
+ print("๐Ÿ” ุงุฎุชุจุงุฑ CORS preflight...")
13
+
14
+ try:
15
+ response = requests.options('http://localhost:5001/summarize')
16
+ print(f"Status Code: {response.status_code}")
17
+ print(f"Headers: {dict(response.headers)}")
18
+
19
+ # ุชุญู‚ู‚ ู…ู† CORS headers
20
+ cors_origin = response.headers.get('Access-Control-Allow-Origin')
21
+ print(f"CORS Origin: '{cors_origin}'")
22
+
23
+ if cors_origin and ',' in cors_origin:
24
+ print("โŒ ู…ุดูƒู„ุฉ: CORS header ูŠุญุชูˆูŠ ุนู„ู‰ ู‚ูŠู… ู…ุชุนุฏุฏุฉ!")
25
+ return False
26
+ elif cors_origin == '*':
27
+ print("โœ… CORS header ุตุญูŠุญ")
28
+ return True
29
+ else:
30
+ print(f"โš ๏ธ CORS header ุบูŠุฑ ู…ุชูˆู‚ุน: {cors_origin}")
31
+ return False
32
+
33
+ except Exception as e:
34
+ print(f"โŒ ุฎุทุฃ ููŠ ุทู„ุจ OPTIONS: {e}")
35
+ return False
36
+
37
+ def test_summarize_endpoint():
38
+ """ุงุฎุชุจุงุฑ endpoint ุงู„ุชู„ุฎูŠุต"""
39
+ print("\n๐Ÿค– ุงุฎุชุจุงุฑ endpoint ุงู„ุชู„ุฎูŠุต...")
40
+
41
+ test_data = {
42
+ "text": "Hello, how are you? What are you doing today? Tell me.",
43
+ "language": "arabic"
44
+ }
45
+
46
+ try:
47
+ response = requests.post(
48
+ 'http://localhost:5001/summarize',
49
+ json=test_data,
50
+ headers={'Content-Type': 'application/json'}
51
+ )
52
+
53
+ print(f"Status Code: {response.status_code}")
54
+ print(f"Response: {response.text}")
55
+
56
+ if response.status_code == 200:
57
+ data = response.json()
58
+ if data.get('success'):
59
+ print("โœ… ุงู„ุชู„ุฎูŠุต ู†ุฌุญ!")
60
+ return True
61
+ else:
62
+ print(f"โŒ ูุดู„ ุงู„ุชู„ุฎูŠุต: {data.get('error')}")
63
+ return False
64
+ else:
65
+ print(f"โŒ ุฎุทุฃ HTTP: {response.status_code}")
66
+ return False
67
+
68
+ except Exception as e:
69
+ print(f"โŒ ุฎุทุฃ ููŠ ุทู„ุจ POST: {e}")
70
+ return False
71
+
72
+ def test_server_status():
73
+ """ุงุฎุชุจุงุฑ ุญุงู„ุฉ ุงู„ุฎุงุฏู…"""
74
+ print("๐ŸŒ ุงุฎุชุจุงุฑ ุญุงู„ุฉ ุงู„ุฎุงุฏู…...")
75
+
76
+ try:
77
+ response = requests.get('http://localhost:5001/record')
78
+ print(f"Status Code: {response.status_code}")
79
+
80
+ if response.status_code == 200:
81
+ data = response.json()
82
+ print(f"Server Status: {data.get('status')}")
83
+ return True
84
+ else:
85
+ print(f"โŒ ุงู„ุฎุงุฏู… ุบูŠุฑ ู…ุชุงุญ: {response.status_code}")
86
+ return False
87
+
88
+ except Exception as e:
89
+ print(f"โŒ ู„ุง ูŠู…ูƒู† ุงู„ูˆุตูˆู„ ู„ู„ุฎุงุฏู…: {e}")
90
+ return False
91
+
92
+ if __name__ == "__main__":
93
+ print("=" * 50)
94
+ print("๐Ÿš€ ุจุฏุก ุงุฎุชุจุงุฑ ู…ุดูƒู„ุฉ ุงู„ุชู„ุฎูŠุต")
95
+ print("=" * 50)
96
+
97
+ # ุงุฎุชุจุงุฑ ุญุงู„ุฉ ุงู„ุฎุงุฏู…
98
+ server_ok = test_server_status()
99
+
100
+ if server_ok:
101
+ # ุงุฎุชุจุงุฑ CORS
102
+ cors_ok = test_cors_preflight()
103
+
104
+ if cors_ok:
105
+ # ุงุฎุชุจุงุฑ ุงู„ุชู„ุฎูŠุต
106
+ summarize_ok = test_summarize_endpoint()
107
+
108
+ if summarize_ok:
109
+ print("\n๐ŸŽ‰ ุฌู…ูŠุน ุงู„ุงุฎุชุจุงุฑุงุช ู†ุฌุญุช!")
110
+ else:
111
+ print("\nโŒ ูุดู„ ููŠ ุงุฎุชุจุงุฑ ุงู„ุชู„ุฎูŠุต")
112
+ else:
113
+ print("\nโŒ ู…ุดูƒู„ุฉ ููŠ CORS - ูŠุฌุจ ุฅุนุงุฏุฉ ุชุดุบูŠู„ ุงู„ุฎุงุฏู…")
114
+ else:
115
+ print("\nโŒ ุงู„ุฎุงุฏู… ุบูŠุฑ ู…ุชุงุญ - ูŠุฌุจ ุชุดุบูŠู„ู‡ ุฃูˆู„ุงู‹")
116
+
117
+ print("=" * 50)
test_summary_button.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ ุงุฎุชุจุงุฑ ุณุฑูŠุน ูˆู…ุจุณุท ู„ุฒุฑ ุงู„ุชู„ุฎูŠุต
5
+ """
6
+
7
+ import requests
8
+ import json
9
+
10
+ def test_summary_button():
11
+ """ู…ุญุงูƒุงุฉ ู†ูุณ ุงู„ุทู„ุจ ุงู„ุฐูŠ ูŠุฑุณู„ู‡ ุฒุฑ ุงู„ุชู„ุฎูŠุต"""
12
+
13
+ print("๐Ÿค– ุงุฎุชุจุงุฑ ุฒุฑ ุงู„ุชู„ุฎูŠุต...")
14
+ print("=" * 50)
15
+
16
+ # ู†ูุณ ุงู„ุจูŠุงู†ุงุช ุงู„ุชูŠ ุชุฑุณู„ู‡ุง ุงู„ูˆุงุฌู‡ุฉ
17
+ test_data = {
18
+ "text": "Hello, how are you? What are you doing today? Tell me.",
19
+ "language": "arabic",
20
+ "type": "full"
21
+ }
22
+
23
+ try:
24
+ print("๐Ÿ“ค ุฅุฑุณุงู„ ุทู„ุจ ุงู„ุชู„ุฎูŠุต...")
25
+ print(f"ุงู„ุจูŠุงู†ุงุช: {test_data}")
26
+
27
+ response = requests.post(
28
+ 'http://localhost:5001/summarize',
29
+ json=test_data,
30
+ headers={
31
+ 'Content-Type': 'application/json',
32
+ 'Accept': 'application/json'
33
+ },
34
+ timeout=30
35
+ )
36
+
37
+ print(f"\n๐Ÿ“ฅ ุงุณุชู„ุงู… ุงู„ุฑุฏ:")
38
+ print(f"Status Code: {response.status_code}")
39
+ print(f"Headers: {dict(response.headers)}")
40
+
41
+ if response.status_code == 200:
42
+ data = response.json()
43
+ print(f"\nโœ… ู†ุฌุญ ุงู„ุชู„ุฎูŠุต!")
44
+ print(f"Keys ููŠ ุงู„ุฑุฏ: {list(data.keys())}")
45
+
46
+ if data.get('success'):
47
+ print(f"ู†ูˆุน ุงู„ุชู„ุฎูŠุต: {data.get('type')}")
48
+
49
+ if 'summary' in data:
50
+ summary = data['summary']
51
+ print(f"\nุงู„ู…ู„ุฎุต:")
52
+ print("-" * 30)
53
+ print(summary)
54
+ print("-" * 30)
55
+ return True
56
+ else:
57
+ print("โš ๏ธ ู„ุง ูŠูˆุฌุฏ ู…ู„ุฎุต ููŠ ุงู„ุฑุฏ")
58
+ return False
59
+ else:
60
+ print(f"โŒ ูุดู„ ุงู„ุชู„ุฎูŠุต: {data.get('error')}")
61
+ return False
62
+ else:
63
+ print(f"โŒ ุฎุทุฃ HTTP: {response.status_code}")
64
+ print(f"ุงู„ุฑุฏ: {response.text}")
65
+ return False
66
+
67
+ except Exception as e:
68
+ print(f"โŒ ุฎุทุฃ ููŠ ุงู„ุทู„ุจ: {e}")
69
+ return False
70
+
71
+ def test_cors_manually():
72
+ """ุงุฎุชุจุงุฑ CORS ูŠุฏูˆูŠุงู‹"""
73
+ print("\n๐Ÿ”ง ุงุฎุชุจุงุฑ CORS...")
74
+
75
+ try:
76
+ # ุทู„ุจ OPTIONS ุฃูˆู„ุงู‹
77
+ options_response = requests.options('http://localhost:5001/summarize')
78
+ print(f"OPTIONS Status: {options_response.status_code}")
79
+
80
+ cors_origin = options_response.headers.get('Access-Control-Allow-Origin')
81
+ print(f"CORS Origin: '{cors_origin}'")
82
+
83
+ if cors_origin == '*':
84
+ print("โœ… CORS ุตุญูŠุญ")
85
+ return True
86
+ else:
87
+ print(f"โŒ ู…ุดูƒู„ุฉ CORS: {cors_origin}")
88
+ return False
89
+
90
+ except Exception as e:
91
+ print(f"โŒ ุฎุทุฃ ููŠ ุงุฎุชุจุงุฑ CORS: {e}")
92
+ return False
93
+
94
+ if __name__ == "__main__":
95
+ print("๐Ÿš€ ุงุฎุชุจุงุฑ ุฒุฑ ุงู„ุชู„ุฎูŠุต ุงู„ู…ุญุฏุซ")
96
+ print("=" * 50)
97
+
98
+ # ุงุฎุชุจุงุฑ CORS
99
+ cors_ok = test_cors_manually()
100
+
101
+ if cors_ok:
102
+ # ุงุฎุชุจุงุฑ ุงู„ุชู„ุฎูŠุต
103
+ summary_ok = test_summary_button()
104
+
105
+ if summary_ok:
106
+ print("\n๐ŸŽ‰ ุฌู…ูŠุน ุงู„ุงุฎุชุจุงุฑุงุช ู†ุฌุญุช!")
107
+ print("โœ… ุฒุฑ ุงู„ุชู„ุฎูŠุต ูŠุนู…ู„ ุจุดูƒู„ ุตุญูŠุญ")
108
+ else:
109
+ print("\nโŒ ู‡ู†ุงูƒ ู…ุดูƒู„ุฉ ููŠ ุงู„ุชู„ุฎูŠุต")
110
+ else:
111
+ print("\nโŒ ู…ุดูƒู„ุฉ CORS ุชู…ู†ุน ุงู„ุชู„ุฎูŠุต")
112
+
113
+ print("=" * 50)
test_system.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ System Test Script for SyncMaster Enhanced
4
+ ุณูƒุฑูŠุจุช ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู… ู„ู€ SyncMaster ุงู„ู…ุญุณู†
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import traceback
10
+ from pathlib import Path
11
+
12
+ def test_imports():
13
+ """Test all required imports"""
14
+ print("๐Ÿ” Testing imports...")
15
+
16
+ tests = [
17
+ ('google.genai', 'Google Generative AI'),
18
+ ('librosa', 'LibROSA'),
19
+ ('soundfile', 'SoundFile'),
20
+ ('streamlit', 'Streamlit'),
21
+ ('flask', 'Flask'),
22
+ ('dotenv', 'Python-dotenv'),
23
+ ]
24
+
25
+ failed_imports = []
26
+
27
+ for module, name in tests:
28
+ try:
29
+ __import__(module)
30
+ print(f" โœ… {name}")
31
+ except ImportError as e:
32
+ print(f" โŒ {name}: {e}")
33
+ failed_imports.append(name)
34
+
35
+ return len(failed_imports) == 0
36
+
37
+ def test_env_file():
38
+ """Test environment configuration"""
39
+ print("\n๐Ÿ”‘ Testing environment...")
40
+
41
+ if not Path('.env').exists():
42
+ print(" โŒ .env file not found")
43
+ return False
44
+
45
+ try:
46
+ from dotenv import load_dotenv
47
+ load_dotenv()
48
+
49
+ api_key = os.getenv("GEMINI_API_KEY")
50
+ if not api_key:
51
+ print(" โŒ GEMINI_API_KEY not found in .env")
52
+ return False
53
+ elif api_key == "your_api_key_here":
54
+ print(" โš ๏ธ GEMINI_API_KEY needs to be replaced with actual key")
55
+ return False
56
+ else:
57
+ print(f" โœ… GEMINI_API_KEY configured (length: {len(api_key)})")
58
+ return True
59
+
60
+ except Exception as e:
61
+ print(f" โŒ Error reading .env: {e}")
62
+ return False
63
+
64
+ def test_translator():
65
+ """Test translator initialization"""
66
+ print("\n๐ŸŒ Testing translator...")
67
+
68
+ try:
69
+ from translator import AITranslator, get_translator
70
+
71
+ # Test direct initialization
72
+ translator = AITranslator()
73
+ if translator.init_error:
74
+ print(f" โŒ Translator init error: {translator.init_error}")
75
+ return False
76
+
77
+ print(" โœ… Translator initialized successfully")
78
+
79
+ # Test simple translation
80
+ test_text = "Hello, this is a test"
81
+ translated, error = translator.translate_text(test_text, 'ar')
82
+
83
+ if error:
84
+ print(f" โŒ Translation test failed: {error}")
85
+ return False
86
+ elif translated:
87
+ print(f" โœ… Translation test successful: '{test_text}' โ†’ '{translated}'")
88
+ return True
89
+ else:
90
+ print(" โš ๏ธ Translation returned empty result")
91
+ return False
92
+
93
+ except Exception as e:
94
+ print(f" โŒ Translator test failed: {e}")
95
+ print(f" ๐Ÿ” Traceback: {traceback.format_exc()}")
96
+ return False
97
+
98
+ def test_audio_processor():
99
+ """Test audio processor initialization"""
100
+ print("\n๐ŸŽต Testing audio processor...")
101
+
102
+ try:
103
+ from audio_processor import AudioProcessor
104
+
105
+ processor = AudioProcessor()
106
+ if processor.init_error:
107
+ print(f" โŒ Audio processor init error: {processor.init_error}")
108
+ return False
109
+
110
+ print(" โœ… Audio processor initialized successfully")
111
+
112
+ # Check if translator is available
113
+ if processor.translator:
114
+ print(" โœ… Translator integration available")
115
+ else:
116
+ print(" โš ๏ธ Translator integration not available")
117
+
118
+ return True
119
+
120
+ except Exception as e:
121
+ print(f" โŒ Audio processor test failed: {e}")
122
+ print(f" ๐Ÿ” Traceback: {traceback.format_exc()}")
123
+ return False
124
+
125
+ def test_server_components():
126
+ """Test server components"""
127
+ print("\n๐Ÿ–ฅ๏ธ Testing server components...")
128
+
129
+ try:
130
+ from recorder_server import app
131
+ print(" โœ… Recorder server imports OK")
132
+
133
+ # Test routes
134
+ with app.test_client() as client:
135
+ # Test health check
136
+ response = client.get('/record')
137
+ if response.status_code == 200:
138
+ print(" โœ… Health check endpoint working")
139
+ else:
140
+ print(f" โš ๏ธ Health check returned {response.status_code}")
141
+
142
+ # Test languages endpoint
143
+ response = client.get('/languages')
144
+ if response.status_code == 200:
145
+ print(" โœ… Languages endpoint working")
146
+ else:
147
+ print(f" โš ๏ธ Languages endpoint returned {response.status_code}")
148
+
149
+ return True
150
+
151
+ except Exception as e:
152
+ print(f" โŒ Server test failed: {e}")
153
+ print(f" ๐Ÿ” Traceback: {traceback.format_exc()}")
154
+ return False
155
+
156
+ def test_ui_translations():
157
+ """Test UI translations"""
158
+ print("\n๐ŸŽจ Testing UI translations...")
159
+
160
+ try:
161
+ from translator import UI_TRANSLATIONS, get_translation
162
+
163
+ # Test English translations
164
+ en_keys = len(UI_TRANSLATIONS.get('en', {}))
165
+ ar_keys = len(UI_TRANSLATIONS.get('ar', {}))
166
+
167
+ print(f" โœ… English translations: {en_keys} keys")
168
+ print(f" โœ… Arabic translations: {ar_keys} keys")
169
+
170
+ if en_keys != ar_keys:
171
+ print(f" โš ๏ธ Translation key count mismatch: EN={en_keys}, AR={ar_keys}")
172
+
173
+ # Test translation function
174
+ test_key = 'start_recording'
175
+ en_text = get_translation(test_key, 'en')
176
+ ar_text = get_translation(test_key, 'ar')
177
+
178
+ print(f" โœ… Test translation: '{en_text}' โ†’ '{ar_text}'")
179
+
180
+ return True
181
+
182
+ except Exception as e:
183
+ print(f" โŒ UI translations test failed: {e}")
184
+ return False
185
+
186
+ def main():
187
+ """Run all tests"""
188
+ print("=" * 60)
189
+ print("๐Ÿงช SyncMaster Enhanced - System Test")
190
+ print("ุงุฎุชุจุงุฑ ุงู„ู†ุธุงู… ุงู„ุดุงู…ู„ ู„ู€ SyncMaster ุงู„ู…ุญุณู†")
191
+ print("=" * 60)
192
+
193
+ # Change to script directory
194
+ script_dir = Path(__file__).parent
195
+ os.chdir(script_dir)
196
+ print(f"๐Ÿ“ Working directory: {script_dir}")
197
+
198
+ tests = [
199
+ ("Import Test", test_imports),
200
+ ("Environment Test", test_env_file),
201
+ ("Translator Test", test_translator),
202
+ ("Audio Processor Test", test_audio_processor),
203
+ ("Server Components Test", test_server_components),
204
+ ("UI Translations Test", test_ui_translations),
205
+ ]
206
+
207
+ results = {}
208
+
209
+ for test_name, test_func in tests:
210
+ try:
211
+ results[test_name] = test_func()
212
+ except Exception as e:
213
+ print(f"\nโŒ {test_name} crashed: {e}")
214
+ results[test_name] = False
215
+
216
+ # Summary
217
+ print("\n" + "=" * 60)
218
+ print("๐Ÿ“Š TEST SUMMARY / ู…ู„ุฎุต ุงู„ุงุฎุชุจุงุฑ")
219
+ print("=" * 60)
220
+
221
+ passed = sum(results.values())
222
+ total = len(results)
223
+
224
+ for test_name, result in results.items():
225
+ status = "โœ… PASS" if result else "โŒ FAIL"
226
+ print(f" {status} {test_name}")
227
+
228
+ print(f"\n๐ŸŽฏ Results: {passed}/{total} tests passed")
229
+
230
+ if passed == total:
231
+ print("๐ŸŽ‰ All tests passed! System is ready to use.")
232
+ print("๐Ÿš€ You can now run: python start_debug.py")
233
+ else:
234
+ print("โš ๏ธ Some tests failed. Please fix the issues before running the application.")
235
+ print("๐Ÿ“ Check the error messages above for details.")
236
+
237
+ return passed == total
238
+
239
+ if __name__ == "__main__":
240
+ success = main()
241
+ sys.exit(0 if success else 1)
test_translation.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # test_translation.py - Quick translation test
2
+
3
+ from translator import get_translator
4
+ import json
5
+
6
+ def test_translation():
7
+ print("๐Ÿ” Testing AI Translation System...")
8
+
9
+ # Get translator instance
10
+ translator = get_translator()
11
+
12
+ if translator.init_error:
13
+ print(f"โŒ Translation Error: {translator.init_error}")
14
+ return
15
+
16
+ print("โœ… Translator initialized successfully")
17
+
18
+ # Test translation
19
+ test_text = "Hello, this is a test for the translation system"
20
+ print(f"\n๐Ÿ”ค Original text: {test_text}")
21
+
22
+ translated_text, error = translator.translate_text(test_text, target_language='ar')
23
+
24
+ if translated_text:
25
+ print(f"โœ… Arabic translation: {translated_text}")
26
+ print(f"๐Ÿ“Š Translation success: True")
27
+ print(f"๐Ÿ”„ Texts are different: {translated_text != test_text}")
28
+ else:
29
+ print(f"โŒ Translation failed: {error}")
30
+
31
+ # Test language detection
32
+ detected_lang, detect_error = translator.detect_language(test_text)
33
+ if detected_lang:
34
+ print(f"๐ŸŒ Detected language: {detected_lang}")
35
+ else:
36
+ print(f"โŒ Language detection failed: {detect_error}")
37
+
38
+ if __name__ == "__main__":
39
+ test_translation()
tmp1dop8abr.json ADDED
The diff for this file is too large to render. See raw diff
 
tmp1g8szy_i.json ADDED
The diff for this file is too large to render. See raw diff
 
tmp43q9lzdr.json ADDED
The diff for this file is too large to render. See raw diff
 
tmp52qryepl.json ADDED
The diff for this file is too large to render. See raw diff
 
tmp6i84ktkz.json ADDED
The diff for this file is too large to render. See raw diff