lattifai 1.2.1__py3-none-any.whl → 1.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. lattifai/alignment/__init__.py +10 -1
  2. lattifai/alignment/lattice1_aligner.py +66 -58
  3. lattifai/alignment/punctuation.py +38 -0
  4. lattifai/alignment/sentence_splitter.py +152 -21
  5. lattifai/alignment/text_align.py +440 -0
  6. lattifai/alignment/tokenizer.py +82 -40
  7. lattifai/caption/__init__.py +82 -6
  8. lattifai/caption/caption.py +335 -1141
  9. lattifai/caption/formats/__init__.py +199 -0
  10. lattifai/caption/formats/base.py +211 -0
  11. lattifai/caption/{gemini_reader.py → formats/gemini.py} +320 -60
  12. lattifai/caption/formats/json.py +194 -0
  13. lattifai/caption/formats/lrc.py +309 -0
  14. lattifai/caption/formats/nle/__init__.py +9 -0
  15. lattifai/caption/formats/nle/audition.py +561 -0
  16. lattifai/caption/formats/nle/avid.py +423 -0
  17. lattifai/caption/formats/nle/fcpxml.py +549 -0
  18. lattifai/caption/formats/nle/premiere.py +589 -0
  19. lattifai/caption/formats/pysubs2.py +642 -0
  20. lattifai/caption/formats/sbv.py +147 -0
  21. lattifai/caption/formats/tabular.py +338 -0
  22. lattifai/caption/formats/textgrid.py +193 -0
  23. lattifai/caption/formats/ttml.py +652 -0
  24. lattifai/caption/formats/vtt.py +469 -0
  25. lattifai/caption/parsers/__init__.py +9 -0
  26. lattifai/caption/{text_parser.py → parsers/text_parser.py} +4 -2
  27. lattifai/caption/standardize.py +636 -0
  28. lattifai/caption/utils.py +474 -0
  29. lattifai/cli/__init__.py +2 -1
  30. lattifai/cli/caption.py +108 -1
  31. lattifai/cli/transcribe.py +1 -1
  32. lattifai/cli/youtube.py +4 -1
  33. lattifai/client.py +33 -113
  34. lattifai/config/__init__.py +11 -1
  35. lattifai/config/alignment.py +7 -0
  36. lattifai/config/caption.py +267 -23
  37. lattifai/config/media.py +20 -0
  38. lattifai/diarization/__init__.py +41 -1
  39. lattifai/mixin.py +27 -15
  40. lattifai/transcription/base.py +6 -1
  41. lattifai/transcription/lattifai.py +19 -54
  42. lattifai/utils.py +7 -13
  43. lattifai/workflow/__init__.py +28 -4
  44. lattifai/workflow/file_manager.py +2 -5
  45. lattifai/youtube/__init__.py +43 -0
  46. lattifai/youtube/client.py +1170 -0
  47. lattifai/youtube/types.py +23 -0
  48. lattifai-1.2.2.dist-info/METADATA +615 -0
  49. lattifai-1.2.2.dist-info/RECORD +76 -0
  50. {lattifai-1.2.1.dist-info → lattifai-1.2.2.dist-info}/entry_points.txt +1 -2
  51. lattifai/caption/gemini_writer.py +0 -173
  52. lattifai/cli/app_installer.py +0 -142
  53. lattifai/cli/server.py +0 -44
  54. lattifai/server/app.py +0 -427
  55. lattifai/workflow/youtube.py +0 -577
  56. lattifai-1.2.1.dist-info/METADATA +0 -1134
  57. lattifai-1.2.1.dist-info/RECORD +0 -58
  58. {lattifai-1.2.1.dist-info → lattifai-1.2.2.dist-info}/WHEEL +0 -0
  59. {lattifai-1.2.1.dist-info → lattifai-1.2.2.dist-info}/licenses/LICENSE +0 -0
  60. {lattifai-1.2.1.dist-info → lattifai-1.2.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,76 @@
1
+ lattifai/__init__.py,sha256=RXa1IK8Qt6jsAnLlxecOCZmREqv2naXx6T1Fy0g6pqU,1953
2
+ lattifai/audio2.py,sha256=P3N8_BwiscbetzDbkbj-n8BcMu2vWD6-MvtQvGwWWf0,17448
3
+ lattifai/client.py,sha256=pTtpOZRpc3weXkjKZ_-FZLsbbs1CrzVqM4fVqRjiYTc,17179
4
+ lattifai/errors.py,sha256=LyWRGVhQ6Ak2CYn9FBYAPRgQ7_VHpxzNsXI31HXD--s,11291
5
+ lattifai/logging.py,sha256=MbUEeOUFlF92pA9v532DiPPWKl03S7UHCJ6Z652cf0w,2860
6
+ lattifai/mixin.py,sha256=0I-rwvZumaYt8KFTfiVPT2wpXs-JfTEnLOPTdI5r-bM,26115
7
+ lattifai/types.py,sha256=SjYBfwrCBOXlICvH04niFQJ7OzTx7oTaa_npfRkB67U,659
8
+ lattifai/utils.py,sha256=5LeunAN0OQ1jWoKMIThpXSEOxFYD2dCRTdsglosodUU,7963
9
+ lattifai/alignment/__init__.py,sha256=ggOF4MlbnBD7U9yrcyRb1caBR3se_KGA87cfYlyX8RY,450
10
+ lattifai/alignment/lattice1_aligner.py,sha256=WG3mJM4fGyYkY7FdqhPE10yXwBzhdj2TkS-6LF8F_9k,6463
11
+ lattifai/alignment/lattice1_worker.py,sha256=hQbZTgncPq3n-b_l-gUPDPfm460EwuZTKveErgWLWNk,10891
12
+ lattifai/alignment/phonemizer.py,sha256=fbhN2DOl39lW4nQWKzyUUTMUabg7v61lB1kj8SKK-Sw,1761
13
+ lattifai/alignment/punctuation.py,sha256=qLcvuXhBzoEa6bznWZiAB5TAxR6eLr_ZV-PnnCY90UA,1218
14
+ lattifai/alignment/segmenter.py,sha256=0s0eABe0rLAo7eNfl0l5e_knxmZba_BjabPdqsRD45E,6284
15
+ lattifai/alignment/sentence_splitter.py,sha256=2ORvfAgW9yQaqHjts2zlSFjTiNDZF5Fhd5KZX19QWe0,14781
16
+ lattifai/alignment/text_align.py,sha256=PN7RNL5d6jim96zeCUdfdFEdGw--I8zc0kcgWIFJIXU,14910
17
+ lattifai/alignment/tokenizer.py,sha256=AQzXbJ_AW8cg4CAd5TVl1Qd3zH56uy9whX9LVFQ4AaA,17835
18
+ lattifai/caption/__init__.py,sha256=tyIsUvCbImw_qrhp0Nxxrk4vt9szJIPlRcTBviOQkuI,2641
19
+ lattifai/caption/caption.py,sha256=2PHLRDG0Ks4JMl6jNDeXlrI1kpYinektbZ15GwwTcFI,23479
20
+ lattifai/caption/standardize.py,sha256=1pAB8BmziTqYkgj7abCXUcNmNwSV1EAR0PrmbpAEipU,21491
21
+ lattifai/caption/supervision.py,sha256=DRrM8lfKU_x9aVBcLG6xnT0xIJrnc8jzHpzcSwQOg8c,905
22
+ lattifai/caption/utils.py,sha256=YOdJCXhy-6DdrZUkdrJHuPE9sbEHsE9Z7-Vdo4Z5lLY,14406
23
+ lattifai/caption/formats/__init__.py,sha256=UGl7Y0ybMf_F4hiNMMwoKOrpWNxs5m2tiD5zkbwjokY,5240
24
+ lattifai/caption/formats/base.py,sha256=gGeKLKEAB2Hs05R09QMkq5KlXMIQ7bbkUhLct40IcU8,6314
25
+ lattifai/caption/formats/gemini.py,sha256=zIxK7Vxo2YB1eXFiWnsNrz9WSx69lMN0rL-Sd3r57iI,29389
26
+ lattifai/caption/formats/json.py,sha256=s3tFWMUzkWx_IL46phPJnFbJW476Yh_GsxcwD7Q_Mfw,6416
27
+ lattifai/caption/formats/lrc.py,sha256=CWS9wD3Tp6xuvF5gP1nTlVBsBXYnu59_4m4zNRem-c0,11084
28
+ lattifai/caption/formats/pysubs2.py,sha256=eOTQKRbsFStW9gTHaxuAtD7ha1OnrFdqcNLsjdxpHRY,22591
29
+ lattifai/caption/formats/sbv.py,sha256=QUgm5lfRSc2IGSX955yQ7rPiSlaYrOHvniUigr2sF7Y,4520
30
+ lattifai/caption/formats/tabular.py,sha256=HHoiif2yIkMjO9f1bRNAk5Pc0CfkA1mtCFHk5sdLocM,11701
31
+ lattifai/caption/formats/textgrid.py,sha256=m2jMTwLhQa8gbm0Fs1XyEUdiHJaSfCxB9jrYsdk8j7Q,6659
32
+ lattifai/caption/formats/ttml.py,sha256=pJ_wd9pX-MwOhDFMeAHnCpbDiLtIhs888rkW26T7w9Y,23236
33
+ lattifai/caption/formats/vtt.py,sha256=f5OWqsr-2-ddW3CnMtiiqYKQz-hLYRn2B9WM_eT4-AM,17102
34
+ lattifai/caption/formats/nle/__init__.py,sha256=DPBnWPtxEKCC0J_8DCeTyXULPgkrqFT2jbKvkazAx0s,257
35
+ lattifai/caption/formats/nle/audition.py,sha256=65ipbUPdwgvNcUA--dQuisWCbmlt6nHPRbSdl4UUF2Q,18076
36
+ lattifai/caption/formats/nle/avid.py,sha256=UQwFlN4-Myly-kXZxuJTu-7IunEN2_PtAcK9YGQVpMA,14403
37
+ lattifai/caption/formats/nle/fcpxml.py,sha256=76NL6PeIR3KAG1BZscAZdoFJr5wcNdoS4j3VZsOxFV8,18317
38
+ lattifai/caption/formats/nle/premiere.py,sha256=Y2nXSWxI0J0YhV3iHJ9jDrFs0S_5sX32_fEi9SJyVt0,21319
39
+ lattifai/caption/parsers/__init__.py,sha256=z1JMr47FVl7CGbBDg92PKj9RabKktJIUv9iTmmKfEes,227
40
+ lattifai/caption/parsers/text_parser.py,sha256=rQv-aedTWowBe7crvYEOrHqrgKdpNBPcM8HeU-jElHY,4793
41
+ lattifai/cli/__init__.py,sha256=PdqoCTqRSFSWrqL3FjBTa5VzJy_e6Rq0OzyT7YkyHpc,541
42
+ lattifai/cli/alignment.py,sha256=06em-Uaf6NhSz1ce4dwT2r8n56NrtibR7ZsSkmc18Kc,5954
43
+ lattifai/cli/caption.py,sha256=b2mSVFVgL76b4FB5UoJ7AW5iGzPfKiWiLhbM96z_QoA,10371
44
+ lattifai/cli/diarization.py,sha256=GTd2vnTm6cJN6Q3mFP-ShY9bZBl1_zKzWFu-4HHcMzk,4075
45
+ lattifai/cli/transcribe.py,sha256=YhEalG3TQRK7esAN5SOZUQPwIk3TAI9ZknO8cW8C21Q,8038
46
+ lattifai/cli/youtube.py,sha256=CqAxSC_sErslnrnx2RSwAHc7INKET0wLG9Mf_144O-A,6238
47
+ lattifai/config/__init__.py,sha256=JOOn2WbvWXBN6a_3fSNt24W7xnJY7wn8RyNLa0XIY3s,724
48
+ lattifai/config/alignment.py,sha256=ObWf896GGLfP4jsxJaSk6nUyzeF4MvW-ULoPYa8kd9w,4987
49
+ lattifai/config/caption.py,sha256=D4sKNUestwFessU1nZrUqCTsIzYPgpTg12SZlm0HzbQ,15200
50
+ lattifai/config/client.py,sha256=46b816MiYja3Uan_3wjnhtqDr0M6T-FqEygJ3e50IZc,1664
51
+ lattifai/config/diarization.py,sha256=cIkwCfsYqfMns3i6tKWcwBBBkdnhhmB_Eo0TuOPCw9o,2484
52
+ lattifai/config/media.py,sha256=nxvgC7zeLsthCARPPUbnK2eMJY8R1d-1XgiAsy8kroA,15568
53
+ lattifai/config/transcription.py,sha256=_gPJD6cob_jWNdf841nBHhAqJGCxS6PfSyvx2W_vPcM,3082
54
+ lattifai/diarization/__init__.py,sha256=-ZZ_a5hIQgnlHIOehCTtmVmWOWC2H6eOhSs4AcVtRtk,1782
55
+ lattifai/diarization/lattifai.py,sha256=tCnFL6ywITqeKR8YoCsYvyJxNoIwoC6GsnI9zkXNB-Q,3128
56
+ lattifai/transcription/__init__.py,sha256=vMHciyCEPKhhfM3KjMCeDqnyxU1oghF8g5o5SvpnT_4,2669
57
+ lattifai/transcription/base.py,sha256=A2qnocdRCCbvy8mKP0f3K3mx3ZvYyxVXir3aJ2iU19s,4592
58
+ lattifai/transcription/gemini.py,sha256=LJSQt9nGqQdEG6ZFXoHWltumyMEM7-Ezy8ss0iPJb7k,12414
59
+ lattifai/transcription/lattifai.py,sha256=Sik4IyvzdqIMCvgkaxCzqvo-j7u0MfX045z8AJunjhg,3556
60
+ lattifai/transcription/prompts/README.md,sha256=X49KWSQVdjWxxWUp4R2w3ZqKrAOi6_kDNHh1hMaQ4PE,694
61
+ lattifai/transcription/prompts/__init__.py,sha256=G9b42COaCYv3sPPNkHsGDLOMBuVGKt4mXGYal_BYtYQ,1351
62
+ lattifai/transcription/prompts/gemini/README.md,sha256=rt7f7yDGtaobKBo95LG3u56mqa3ABOXQd0UVgJYtYuo,781
63
+ lattifai/transcription/prompts/gemini/transcription_gem.txt,sha256=cljzZ--BDgnnKzqVCakr-fTp2Xk38UOsUquvruNX-LU,4600
64
+ lattifai/workflow/__init__.py,sha256=INpQgc9gZ2Fp-aTHcpR3TEHGtEtPzjOB8T7-jLzVM0E,1547
65
+ lattifai/workflow/agents.py,sha256=yEOnxnhcTvr1iOhCorNvp8B76P6nQsLRXJCu_rCYFfM,38
66
+ lattifai/workflow/base.py,sha256=8QoVIBZwJfr5mppJbtUFafHv5QR9lL-XrULjTWD0oBg,6257
67
+ lattifai/workflow/file_manager.py,sha256=yc29Vb7JNUMJ9rwM_YjkAHfDInl8HMVAl9A7z7XiIOU,32974
68
+ lattifai/youtube/__init__.py,sha256=_uO3KCx-t6I-JaYFpcYLYpvkbmEOOni3xBqGEbExg68,1587
69
+ lattifai/youtube/client.py,sha256=aEOnd8jp4w1ZZkTfxppl7yz2TVdxMTkb8lGCqQxLqxE,47128
70
+ lattifai/youtube/types.py,sha256=80RgBmvM4tRbxqyNv9GU6hr9vPp_yhKrK0RJ_vG2h4E,472
71
+ lattifai-1.2.2.dist-info/licenses/LICENSE,sha256=xGMLmdFJy6Jkz3Hd0znyQLmcxC93FSZB5isKnEDMoQQ,1066
72
+ lattifai-1.2.2.dist-info/METADATA,sha256=NncEA5sSiDyj2DfZCt251tLSranIOn2Gd4KD2D0Q118,19757
73
+ lattifai-1.2.2.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
74
+ lattifai-1.2.2.dist-info/entry_points.txt,sha256=MfoqXNjXrhD7VMApHgaHmAECTcGVUMUiR0uqnTg7Ads,502
75
+ lattifai-1.2.2.dist-info/top_level.txt,sha256=tHSoXF26r-IGfbIP_JoYATqbmf14h5NrnNJGH4j5reI,9
76
+ lattifai-1.2.2.dist-info/RECORD,,
@@ -1,11 +1,10 @@
1
1
  [console_scripts]
2
2
  lai-align = lattifai.cli.alignment:main
3
- lai-app-install = lattifai.cli.app_installer:main
4
3
  lai-diarize = lattifai.cli.diarization:main
5
- lai-server = lattifai.cli.server:main
6
4
  lai-transcribe = lattifai.cli.transcribe:main
7
5
  lai-youtube = lattifai.cli.youtube:main
8
6
  laicap-convert = lattifai.cli.caption:main_convert
7
+ laicap-diff = lattifai.cli.caption:main_diff
9
8
  laicap-normalize = lattifai.cli.caption:main_normalize
10
9
  laicap-shift = lattifai.cli.caption:main_shift
11
10
 
@@ -1,173 +0,0 @@
1
- """Writer for YouTube transcript files with corrected timestamps from alignment."""
2
-
3
- import re
4
- from pathlib import Path
5
- from typing import Dict, List, Optional
6
-
7
- from lhotse.utils import Pathlike
8
-
9
- from .gemini_reader import GeminiReader, GeminiSegment
10
- from .supervision import Supervision
11
-
12
-
13
- class GeminiWriter:
14
- """Writer for updating YouTube transcript timestamps based on alignment results."""
15
-
16
- @staticmethod
17
- def format_timestamp(seconds: float) -> str:
18
- """Convert seconds to [HH:MM:SS] format."""
19
- hours = int(seconds // 3600)
20
- minutes = int((seconds % 3600) // 60)
21
- secs = int(seconds % 60)
22
- return f"[{hours:02d}:{minutes:02d}:{secs:02d}]"
23
-
24
- @classmethod
25
- def update_timestamps(
26
- cls,
27
- original_transcript: Pathlike,
28
- aligned_supervisions: List[Supervision],
29
- output_path: Pathlike,
30
- timestamp_mapping: Optional[Dict[int, float]] = None,
31
- ) -> Pathlike:
32
- """Update transcript file with corrected timestamps from alignment.
33
-
34
- Args:
35
- original_transcript: Path to the original transcript file
36
- aligned_supervisions: List of aligned Supervision objects with corrected timestamps
37
- output_path: Path to write the updated transcript
38
- timestamp_mapping: Optional manual mapping from line_number to new timestamp
39
-
40
- Returns:
41
- Path to the output file
42
- """
43
- original_path = Path(original_transcript)
44
- output_path = Path(output_path)
45
-
46
- # Read original file
47
- with open(original_path, "r", encoding="utf-8") as f:
48
- lines = f.readlines()
49
-
50
- # Parse original segments to get line numbers
51
- original_segments = GeminiReader.read(original_transcript, include_events=True, include_sections=True)
52
-
53
- # Create mapping from line number to new timestamp
54
- if timestamp_mapping is None:
55
- timestamp_mapping = cls._create_timestamp_mapping(original_segments, aligned_supervisions)
56
-
57
- # Update timestamps in lines
58
- updated_lines = []
59
- for line_num, line in enumerate(lines, start=1):
60
- if line_num in timestamp_mapping:
61
- new_timestamp = timestamp_mapping[line_num]
62
- updated_line = cls._replace_timestamp(line, new_timestamp)
63
- updated_lines.append(updated_line)
64
- else:
65
- updated_lines.append(line)
66
-
67
- # Write updated content
68
- output_path.parent.mkdir(parents=True, exist_ok=True)
69
- with open(output_path, "w", encoding="utf-8") as f:
70
- f.writelines(updated_lines)
71
-
72
- return output_path
73
-
74
- @classmethod
75
- def _create_timestamp_mapping(
76
- cls, original_segments: List[GeminiSegment], aligned_supervisions: List[Supervision]
77
- ) -> Dict[int, float]:
78
- """Create mapping from line numbers to new timestamps based on alignment.
79
-
80
- This performs text matching between original segments and aligned supervisions
81
- to determine which timestamps should be updated.
82
- """
83
- mapping = {}
84
-
85
- # Create a simple text-based matching
86
- dialogue_segments = [s for s in original_segments if s.segment_type == "dialogue"]
87
-
88
- # Try to match based on text content
89
- for aligned_sup in aligned_supervisions:
90
- aligned_text = aligned_sup.text.strip()
91
-
92
- # Find best matching original segment
93
- best_match = None
94
- best_score = 0
95
-
96
- for orig_seg in dialogue_segments:
97
- orig_text = orig_seg.text.strip()
98
-
99
- # Simple text similarity (could be improved with fuzzy matching)
100
- if aligned_text == orig_text:
101
- best_match = orig_seg
102
- best_score = 1.0
103
- break
104
- elif aligned_text in orig_text or orig_text in aligned_text:
105
- score = min(len(aligned_text), len(orig_text)) / max(len(aligned_text), len(orig_text))
106
- if score > best_score:
107
- best_score = score
108
- best_match = orig_seg
109
-
110
- # If we found a good match, update the mapping
111
- if best_match and best_score > 0.8:
112
- mapping[best_match.line_number] = aligned_sup.start
113
-
114
- return mapping
115
-
116
- @classmethod
117
- def _replace_timestamp(cls, line: str, new_timestamp: float) -> str:
118
- """Replace timestamp in a line with new timestamp."""
119
- new_ts_str = cls.format_timestamp(new_timestamp)
120
-
121
- # Replace timestamp patterns
122
- # Pattern 1: [HH:MM:SS] at the end or in brackets
123
- line = re.sub(r"\[\d{2}:\d{2}:\d{2}\]", new_ts_str, line)
124
-
125
- return line
126
-
127
- @classmethod
128
- def write_aligned_transcript(
129
- cls,
130
- aligned_supervisions: List[Supervision],
131
- output_path: Pathlike,
132
- include_word_timestamps: bool = False,
133
- ) -> Pathlike:
134
- """Write a new transcript file from aligned supervisions.
135
-
136
- This creates a simplified transcript format with accurate timestamps.
137
-
138
- Args:
139
- aligned_supervisions: List of aligned Supervision objects
140
- output_path: Path to write the transcript
141
- include_word_timestamps: Whether to include word-level timestamps if available
142
-
143
- Returns:
144
- Path to the output file
145
- """
146
- output_path = Path(output_path)
147
- output_path.parent.mkdir(parents=True, exist_ok=True)
148
-
149
- with open(output_path, "w", encoding="utf-8") as f:
150
- f.write("# Aligned Transcript\n\n")
151
-
152
- for i, sup in enumerate(aligned_supervisions):
153
- # Write segment with timestamp
154
- start_ts = cls.format_timestamp(sup.start)
155
- f.write(f"{start_ts} {sup.text}\n")
156
-
157
- # Optionally write word-level timestamps
158
- if include_word_timestamps and hasattr(sup, "alignment") and sup.alignment:
159
- if "word" in sup.alignment:
160
- f.write(" Words: ")
161
- word_parts = []
162
- for word_info in sup.alignment["word"]:
163
- word_ts = cls.format_timestamp(word_info["start"])
164
- word_parts.append(f'{word_info["symbol"]}{word_ts}')
165
- f.write(" ".join(word_parts))
166
- f.write("\n")
167
-
168
- f.write("\n")
169
-
170
- return output_path
171
-
172
-
173
- __all__ = ["GeminiWriter"]
@@ -1,142 +0,0 @@
1
- """CLI tool to install lai-app (frontend web application)."""
2
-
3
- import platform
4
- import subprocess
5
- import sys
6
- from pathlib import Path
7
-
8
- from lattifai.utils import safe_print
9
-
10
-
11
- def check_command_exists(cmd: str) -> bool:
12
- """Check if a command exists in PATH."""
13
- try:
14
- subprocess.run([cmd, "--version"], check=True, capture_output=True, text=True)
15
- return True
16
- except (subprocess.CalledProcessError, FileNotFoundError):
17
- return False
18
-
19
-
20
- def install_nodejs():
21
- """Install Node.js based on the operating system."""
22
- system = platform.system().lower()
23
-
24
- safe_print("📦 Node.js not found. Installing Node.js...\n")
25
-
26
- try:
27
- if system == "darwin": # macOS
28
- # Check if Homebrew is installed
29
- if check_command_exists("brew"):
30
- safe_print("🍺 Using Homebrew to install Node.js...")
31
- subprocess.run(["brew", "install", "node"], check=True)
32
- safe_print("✓ Node.js installed via Homebrew\n")
33
- else:
34
- safe_print("❌ Homebrew not found.")
35
- print(" Please install Homebrew first:")
36
- print(
37
- ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
38
- )
39
- print("\n Or install Node.js manually from: https://nodejs.org/")
40
- sys.exit(1)
41
-
42
- elif system == "linux":
43
- # Try common package managers
44
- if check_command_exists("apt"):
45
- safe_print("🐧 Using apt to install Node.js...")
46
- subprocess.run(["sudo", "apt", "update"], check=True)
47
- subprocess.run(["sudo", "apt", "install", "-y", "nodejs", "npm"], check=True)
48
- safe_print("✓ Node.js installed via apt\n")
49
- elif check_command_exists("yum"):
50
- safe_print("🐧 Using yum to install Node.js...")
51
- subprocess.run(["sudo", "yum", "install", "-y", "nodejs", "npm"], check=True)
52
- safe_print("✓ Node.js installed via yum\n")
53
- elif check_command_exists("dnf"):
54
- safe_print("🐧 Using dnf to install Node.js...")
55
- subprocess.run(["sudo", "dnf", "install", "-y", "nodejs", "npm"], check=True)
56
- safe_print("✓ Node.js installed via dnf\n")
57
- elif check_command_exists("pacman"):
58
- safe_print("🐧 Using pacman to install Node.js...")
59
- subprocess.run(["sudo", "pacman", "-S", "--noconfirm", "nodejs", "npm"], check=True)
60
- safe_print("✓ Node.js installed via pacman\n")
61
- else:
62
- safe_print("❌ No supported package manager found (apt/yum/dnf/pacman).")
63
- print(" Please install Node.js manually from: https://nodejs.org/")
64
- sys.exit(1)
65
-
66
- elif system == "windows":
67
- safe_print("❌ Automatic installation on Windows is not supported.")
68
- print(" Please download and install Node.js from: https://nodejs.org/")
69
- print(" Then run this command again.")
70
- sys.exit(1)
71
-
72
- else:
73
- safe_print(f"❌ Unsupported operating system: {system}")
74
- print(" Please install Node.js manually from: https://nodejs.org/")
75
- sys.exit(1)
76
-
77
- # Verify installation
78
- if not check_command_exists("npm"):
79
- safe_print("❌ Node.js installation verification failed.")
80
- print(" Please restart your terminal and try again.")
81
- sys.exit(1)
82
-
83
- except subprocess.CalledProcessError as e:
84
- safe_print(f"\n❌ Error during Node.js installation: {e}")
85
- print(" Please install Node.js manually from: https://nodejs.org/")
86
- sys.exit(1)
87
-
88
-
89
- def main():
90
- """Install lai-app Node.js application."""
91
- # Get the app directory relative to this package
92
- app_dir = Path(__file__).parent.parent.parent.parent / "app"
93
-
94
- if not app_dir.exists():
95
- safe_print(f"❌ Error: app directory not found at {app_dir}")
96
- print(" Make sure you're in the lattifai-python repository.")
97
- sys.exit(1)
98
-
99
- safe_print("🚀 Installing lai-app (LattifAI Web Application)...\n")
100
-
101
- # Check if npm is installed, if not, install Node.js
102
- if not check_command_exists("npm"):
103
- install_nodejs()
104
- else:
105
- npm_version = subprocess.run(["npm", "--version"], capture_output=True, text=True, check=True).stdout.strip()
106
- safe_print(f"✓ npm is already installed (v{npm_version})\n")
107
-
108
- # Change to app directory and run installation
109
- try:
110
- safe_print(f"📁 Working directory: {app_dir}\n")
111
-
112
- # Install dependencies
113
- safe_print("📦 Installing dependencies...")
114
- subprocess.run(["npm", "install"], cwd=app_dir, check=True)
115
- safe_print("✓ Dependencies installed\n")
116
-
117
- # Build the application
118
- safe_print("🔨 Building application...")
119
- subprocess.run(["npm", "run", "build"], cwd=app_dir, check=True)
120
- safe_print("✓ Application built\n")
121
-
122
- # Link globally
123
- safe_print("🔗 Linking lai-app command globally...")
124
- subprocess.run(["npm", "link"], cwd=app_dir, check=True)
125
- safe_print("✓ lai-app command linked globally\n")
126
-
127
- safe_print("=" * 60)
128
- safe_print("✅ lai-app installed successfully!")
129
- safe_print("=" * 60)
130
- safe_print("\n🎉 You can now run:")
131
- print(" lai-app # Start the web application")
132
- print(" lai-app --help # Show help")
133
- print(" lai-app --port 8080 # Use custom port")
134
- safe_print("\n📖 For more information, see app/CLI_USAGE.md\n")
135
-
136
- except subprocess.CalledProcessError as e:
137
- safe_print(f"\n❌ Error during installation: {e}")
138
- sys.exit(1)
139
-
140
-
141
- if __name__ == "__main__":
142
- main()
lattifai/cli/server.py DELETED
@@ -1,44 +0,0 @@
1
- import argparse
2
- import os
3
-
4
- import colorful
5
- import uvicorn
6
-
7
- from lattifai.utils import safe_print
8
-
9
-
10
- def main():
11
- """Launch the LattifAI Web Interface."""
12
- parser = argparse.ArgumentParser(description="LattifAI Backend Server")
13
- parser.add_argument(
14
- "-p",
15
- "--port",
16
- type=int,
17
- default=8001,
18
- help="Port to run the server on (default: 8001)",
19
- )
20
- parser.add_argument(
21
- "--host",
22
- type=str,
23
- default="0.0.0.0",
24
- help="Host to bind the server to (default: 0.0.0.0)",
25
- )
26
- parser.add_argument(
27
- "--no-reload",
28
- action="store_true",
29
- help="Disable auto-reload on code changes",
30
- )
31
-
32
- args = parser.parse_args()
33
-
34
- safe_print(colorful.bold_green("🚀 Launching LattifAI Backend Server..."))
35
- print(colorful.cyan(f"Server running at http://localhost:{args.port}"))
36
- print(colorful.yellow(f"Host: {args.host}"))
37
- print(colorful.yellow(f"Auto-reload: {'disabled' if args.no_reload else 'enabled'}"))
38
- print()
39
-
40
- uvicorn.run("lattifai.server.app:app", host=args.host, port=args.port, reload=not args.no_reload, log_level="info")
41
-
42
-
43
- if __name__ == "__main__":
44
- main()