@l10nmonster/server 3.0.0-alpha.11 → 3.0.0-alpha.13

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 (47) hide show
  1. package/CLAUDE.md +81 -13
  2. package/index.js +19 -2
  3. package/package.json +1 -1
  4. package/routes/dispatcher.js +17 -7
  5. package/routes/info.js +17 -16
  6. package/routes/providers.js +7 -9
  7. package/routes/sources.js +47 -9
  8. package/routes/status.js +12 -11
  9. package/routes/tm.js +89 -26
  10. package/ui/dist/assets/Cart-B_Zkvuz_.js +1 -0
  11. package/ui/dist/assets/Job-B2D10u5q.js +1 -0
  12. package/ui/dist/assets/Providers-UAlXnJQI.js +1 -0
  13. package/ui/dist/assets/Sources-DcwZBkaA.js +1 -0
  14. package/ui/dist/assets/SourcesDetail-11v96V44.js +1 -0
  15. package/ui/dist/assets/SourcesResource-CPGv3HQ8.js +1 -0
  16. package/ui/dist/assets/Status-C_qyyf2l.js +1 -0
  17. package/ui/dist/assets/StatusDetail-CXE95RTb.js +1 -0
  18. package/ui/dist/assets/TM-DIDYSCIe.js +1 -0
  19. package/ui/dist/assets/TMDetail-BmHvlsJe.js +1 -0
  20. package/ui/dist/assets/Welcome-Bd1y86xQ.js +1 -0
  21. package/ui/dist/assets/index-Dl1WS_Lc.js +1 -0
  22. package/ui/dist/assets/index-HzbF4bPY.js +2 -0
  23. package/ui/dist/assets/vendor-BVSmEsOp.js +75 -0
  24. package/ui/dist/index.html +3 -1
  25. package/ui/dist/assets/Cart-jHOAGjwC.js +0 -1
  26. package/ui/dist/assets/Job-vnJpBXcp.js +0 -1
  27. package/ui/dist/assets/Providers-DUp_y6mi.js +0 -1
  28. package/ui/dist/assets/Sources-CC5dBiir.js +0 -1
  29. package/ui/dist/assets/Status-SRPm28Cg.js +0 -1
  30. package/ui/dist/assets/StatusDetail-DqXzi16U.js +0 -1
  31. package/ui/dist/assets/TM-CUqywRKC.js +0 -1
  32. package/ui/dist/assets/TMDetail-Bl4xHL_X.js +0 -1
  33. package/ui/dist/assets/Welcome-D33rOKBc.js +0 -1
  34. package/ui/dist/assets/api-CQ_emPlI.js +0 -1
  35. package/ui/dist/assets/badge-D0XxOmqE.js +0 -1
  36. package/ui/dist/assets/grid-c9ySqPWI.js +0 -1
  37. package/ui/dist/assets/index-BHll-Wur.js +0 -1
  38. package/ui/dist/assets/index-CE6JgXwC.js +0 -76
  39. package/ui/dist/assets/input-C_b-hL0i.js +0 -1
  40. package/ui/dist/assets/link-BJrTt4Ej.js +0 -1
  41. package/ui/dist/assets/namespace-DqVzlNZ0.js +0 -1
  42. package/ui/dist/assets/select-B7OxVhQ5.js +0 -1
  43. package/ui/dist/assets/tooltip-DsbPmc5I.js +0 -1
  44. package/ui/dist/assets/use-field-context-DPgnX2eS.js +0 -1
  45. package/ui/dist/assets/use-presence-context-w7qGDwCc.js +0 -1
  46. package/ui/dist/assets/useQuery-DJATtsyt.js +0 -1
  47. package/ui/dist/assets/v-stack-BLV93Nj6.js +0 -1
package/CLAUDE.md CHANGED
@@ -206,6 +206,7 @@ borderColor="border.default" // Default border color
206
206
  - **Before using any component**, check the official Chakra UI v3 documentation
207
207
  - **Never assume** v2 patterns will work in v3
208
208
  - Use the MCP Context7 tool to get up-to-date documentation
209
+ - **Use the Chakra UI MCP server** - This project has access to specialized Chakra UI MCP tools for getting component examples, props, themes, and migration guidance
209
210
 
210
211
  ### 2. Common Pitfalls to Avoid
211
212
  - Don't use `isChecked`, use `checked`
@@ -279,6 +280,14 @@ import { Checkbox } from '@chakra-ui/react'
279
280
  - [Chakra UI v3 Migration Guide](https://v3.chakra-ui.com/docs/migration)
280
281
  - [Chakra UI v3 Components](https://v3.chakra-ui.com/docs/components)
281
282
  - Use MCP Context7 tool with library ID `/llmstxt/chakra-ui-llms-full.txt` for latest docs
283
+ - **Chakra UI MCP Server Tools:**
284
+ - `mcp__chakra-ui__get_component_props` - Get component properties and configuration options
285
+ - `mcp__chakra-ui__get_component_example` - Get practical implementation examples with code snippets
286
+ - `mcp__chakra-ui__list_components` - List all available Chakra UI components
287
+ - `mcp__chakra-ui__get_theme` - Retrieve theme specification (colors, fonts, textStyles)
288
+ - `mcp__chakra-ui__customize_theme` - Setup custom theme tokens
289
+ - `mcp__chakra-ui__v2_to_v3_code_review` - Get migration guidance for specific v2→v3 scenarios
290
+ - `mcp__chakra-ui__installation` - Get setup instructions for different frameworks
282
291
 
283
292
  ## Data Fetching Architecture
284
293
 
@@ -334,7 +343,6 @@ Use consistent query keys that include all relevant parameters:
334
343
  // ✅ Good query keys - include all parameters that affect the data
335
344
  queryKey: ['status']
336
345
  queryKey: ['tmStats']
337
- queryKey: ['activeContentStats']
338
346
  queryKey: ['info']
339
347
  queryKey: ['tmSearch', sourceLang, targetLang, filters]
340
348
 
@@ -605,11 +613,11 @@ Routes are organized by functionality in separate files under `/server/routes/`:
605
613
  ```
606
614
  /server/routes/
607
615
  ├── info.js # System information endpoints
608
- ├── status.js # Project status endpoints
609
- ├── sources.js # Content source endpoints
616
+ ├── status.js # Project status endpoints
617
+ ├── sources.js # Source content (channels, projects, resources)
610
618
  ├── tm.js # Translation memory endpoints
611
619
  ├── providers.js # Translation provider endpoints
612
- └── dispatcher.js # Job creation endpoints
620
+ └── dispatcher.js # Job creation and management
613
621
  ```
614
622
 
615
623
  ### Available Endpoints
@@ -620,23 +628,40 @@ Routes are organized by functionality in separate files under `/server/routes/`:
620
628
  - `providers`: Array of available provider IDs
621
629
  - `config`: System configuration
622
630
 
623
- #### Project Status
631
+ #### Project Status
624
632
  - **GET `/api/status`** - Returns project status and statistics
625
633
 
626
- #### Content Sources
627
- - **GET `/api/activeContentStats`** - Returns statistics about active content sources grouped by channel
634
+ #### Source Content
635
+ - **GET `/api/channel/:channelId`** - Returns channel metadata and active content statistics
636
+ - **Response:** `{ ts: number, store: string, projects: [...] }`
637
+ - **GET `/api/channel/:channelId/:prj`** - Returns project table of contents
638
+ - **Query Parameters:** `offset`, `limit` (pagination)
639
+ - **Response:** Array of resource handles for the project
640
+ - **GET `/api/resource/:channelId?rid=<resourceId>`** - Returns resource details with segments
641
+ - **Query Parameters:** `rid` (required) - Resource ID
642
+ - **Response:** Resource handle with segments array
628
643
 
629
644
  #### Translation Memory
630
- - **GET `/api/tm/stats`** - Returns translation memory statistics for all language pairs
631
- - **GET `/api/tm/search`** - Search translation memory entries
645
+ - **GET `/api/tm/stats`** - Returns available language pairs (sorted array)
646
+ - **GET `/api/tm/stats/:sourceLang/:targetLang`** - Returns TM statistics for specific language pair
647
+ - **Response:** Statistics object with counts, quality distribution, etc.
648
+ - **GET `/api/tm/lowCardinalityColumns/:sourceLang/:targetLang`** - Returns available filter options
649
+ - **Response:** `{ channel: [...], translationProvider: [...], ... }`
650
+ - **GET `/api/tm/search`** - Search translation memory entries with advanced filtering
632
651
  - **Query Parameters:**
633
652
  - `sourceLang`, `targetLang` (required)
634
- - `page`, `limit` (pagination)
635
- - `guid`, `jobGuid`, `rid`, `sid` (filtering)
636
- - `nsrc`, `ntgt` (source/target text filtering)
653
+ - `page`, `limit` (pagination, default: page=1, limit=100)
654
+ - `guid`, `nid`, `jobGuid`, `rid`, `sid`, `channel` (exact or partial match)
655
+ - `nsrc`, `ntgt`, `notes`, `tconf` (text search - supports quoted exact match)
637
656
  - `q` (quality score filtering)
638
657
  - `translationProvider` (provider filtering)
658
+ - `onlyTNotes` (boolean: "1" to show only TUs with translator notes)
659
+ - `minTS`, `maxTS` (timestamp range filtering - milliseconds since epoch)
660
+ - **Text Search:** Use quotes for exact match (e.g., `nsrc="hello world"`), otherwise partial match
661
+ - **Date Range Filtering:** Filter by timestamp range using minTS and maxTS (milliseconds). UI displays clickable date range (M/D format without leading zeros, uses current year). Click to open popover with From/To date pickers and Apply/Clear buttons.
639
662
  - **Response:** `{ data: [...], page: number, limit: number }`
663
+ - **GET `/api/tm/job/:jobGuid`** - Returns job details by GUID
664
+ - **Response:** Job object with metadata, TUs, timestamps, etc.
640
665
 
641
666
  #### Translation Providers
642
667
  - **GET `/api/providers`** - Returns detailed provider information (slower)
@@ -731,10 +756,53 @@ The job management system follows a two-step process:
731
756
  - Actually initiates the translation process
732
757
  - Returns status information for tracking job progress
733
758
 
759
+ ## Production Build Configuration
760
+
761
+ The Vite build is configured with a hybrid chunking strategy for optimal performance:
762
+
763
+ ```javascript
764
+ // vite.config.js
765
+ build: {
766
+ rollupOptions: {
767
+ output: {
768
+ manualChunks: (id) => {
769
+ // All node_modules in one vendor bundle
770
+ if (id.includes('node_modules')) {
771
+ return 'vendor';
772
+ }
773
+ // Bundle utility files with index instead of creating tiny chunks
774
+ if (id.includes('/utils/') || id.includes('/src/components/')) {
775
+ return 'index';
776
+ }
777
+ // Pages remain separate for lazy loading
778
+ }
779
+ }
780
+ }
781
+ }
782
+ ```
783
+
784
+ **Build Output:**
785
+ - **1 vendor bundle** (~725 KB / 210 KB gzipped) - All dependencies (React, Chakra UI, React Router, etc.)
786
+ - **1 index bundle** (~28 KB) - App core, utilities, and shared components
787
+ - **~11 page chunks** (1-17 KB each) - Individual pages for code splitting
788
+
789
+ **Benefits:**
790
+ - ✅ Vendor bundle rarely changes (excellent for caching)
791
+ - ✅ Pages lazy load independently for faster initial render
792
+ - ✅ Shared components bundled efficiently
793
+ - ✅ Fewer files than default Vite chunking (~16 vs ~40)
794
+
795
+ **Commands:**
796
+ ```bash
797
+ npm run build # Build production bundle
798
+ npm run preview # Preview production build locally
799
+ ```
800
+
734
801
  ## Remember
735
802
 
736
803
  **When in doubt:**
737
804
  1. **Check React Query docs** for data fetching patterns
738
805
  2. **Check Chakra UI v3 docs** for component usage
739
806
  3. **Use pure React Router** for navigation
740
- 4. **Never guess based on v2 Chakra UI knowledge**
807
+ 4. **Never guess based on v2 Chakra UI knowledge**
808
+ 5. **Use Chakra UI MCP tools** for accurate component examples and props
package/index.js CHANGED
@@ -1,3 +1,20 @@
1
+ // Global unhandled promise rejection handler for server
2
+ process.on('unhandledRejection', (reason, promise) => {
3
+ console.error('🚨 Unhandled Promise Rejection in L10n Monster Server:');
4
+ console.error('Promise:', promise);
5
+ console.error('Reason:', reason);
6
+ if (reason instanceof Error && reason.stack) {
7
+ console.error('Stack trace:', reason.stack);
8
+ }
9
+ // In server context, log but don't exit immediately to allow graceful handling
10
+ console.error('This should be investigated and fixed!');
11
+ });
12
+
13
+ // Global uncaught exception handler
14
+ process.on('uncaughtException', (error) => {
15
+ console.error('🚨 Uncaught Exception in L10n Monster Server:', error);
16
+ });
17
+
1
18
  import express from 'express';
2
19
  import cors from 'cors';
3
20
  import path from 'path';
@@ -6,7 +23,7 @@ import os from 'os';
6
23
  import { readFileSync } from 'fs';
7
24
  import { setupInfoRoute } from './routes/info.js';
8
25
  import { setupStatusRoute } from './routes/status.js';
9
- import { setupActiveContentStatsRoute } from './routes/sources.js';
26
+ import { setupChannelRoutes } from './routes/sources.js';
10
27
  import { setupTmRoutes } from './routes/tm.js';
11
28
  import { setupProviderRoute } from './routes/providers.js';
12
29
  import { setupDispatcherRoutes } from './routes/dispatcher.js';
@@ -39,7 +56,7 @@ export default class serve {
39
56
  // Setup routes from separate files
40
57
  setupInfoRoute(apiRouter, mm, serverPackage);
41
58
  setupStatusRoute(apiRouter, mm);
42
- setupActiveContentStatsRoute(apiRouter, mm);
59
+ setupChannelRoutes(apiRouter, mm);
43
60
  setupTmRoutes(apiRouter, mm);
44
61
  setupProviderRoute(apiRouter, mm);
45
62
  setupDispatcherRoutes(apiRouter, mm);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@l10nmonster/server",
3
- "version": "3.0.0-alpha.11",
3
+ "version": "3.0.0-alpha.13",
4
4
  "description": "L10n Monster Manager",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -28,14 +28,24 @@ export function setupDispatcherRoutes(router, mm) {
28
28
  });
29
29
  }
30
30
 
31
- logVerbose`Creating jobs for ${tus.length} TUs with providers: ${providerList.join(', ')}`;
32
-
31
+ logVerbose`Fetching content of ${tus.length} TUs`;
32
+ const tm = mm.tmm.getTM(sourceLang, targetLang);
33
+ const guidsByChannel = {};
34
+ for (const tu of tus) {
35
+ const channel = tu.channel ?? '';
36
+ guidsByChannel[channel] ??= new Set();
37
+ guidsByChannel[channel].add(tu.guid);
38
+ }
39
+ const expandedTus = [];
40
+ for (const [ channel, tus ] of Object.entries(guidsByChannel)) {
41
+ const tusForChannel = await tm.queryByGuids(Array.from(tus), channel.length > 0 ? channel : undefined);
42
+ expandedTus.push(tusForChannel);
43
+ }
33
44
  // Call the MonsterManager dispatcher
34
- const result = await mm.dispatcher.createJobs({ sourceLang, targetLang, tus }, { providerList, skipQualityCheck: true, skipGroupCheck: true });
35
-
36
- logVerbose`Created ${result?.length || 0} jobs successfully`;
37
- res.json(result);
45
+ const jobs = await mm.dispatcher.createJobs({ sourceLang, targetLang, tus: expandedTus.flat(1) }, { providerList, skipQualityCheck: true, skipGroupCheck: true });
38
46
 
47
+ logVerbose`Created ${jobs?.length || 0} jobs successfully`;
48
+ res.json(jobs);
39
49
  } catch (error) {
40
50
  logInfo`Error creating jobs: ${error.message}`;
41
51
  res.status(500).json({
@@ -63,7 +73,7 @@ export function setupDispatcherRoutes(router, mm) {
63
73
  // Call the MonsterManager dispatcher
64
74
  const result = await mm.dispatcher.startJobs(jobs, { instructions });
65
75
 
66
- logVerbose`Started jobs, returned ${result?.length || 0} job statuses`;
76
+ logVerbose`Started ${result?.length || 0} jobs`;
67
77
  res.json(result);
68
78
 
69
79
  } catch (error) {
package/routes/info.js CHANGED
@@ -4,21 +4,22 @@ import path from 'path';
4
4
  export function setupInfoRoute(router, mm, serverPackage) {
5
5
  router.get('/info', async (req, res) => {
6
6
  logInfo`/info`;
7
- res.json({
8
- version: serverPackage.version,
9
- description: serverPackage.description,
10
- baseDir: path.resolve(getBaseDir()),
11
- providers: mm.dispatcher.providers.map(p => p.id),
12
- channels: Object.keys(mm.rm.channels),
13
- tmStores: mm.getTmStoreIds().map(id => {
14
- const tmStore = mm.getTmStore(id);
15
- return {
16
- id: tmStore.id,
17
- type: tmStore.constructor.name,
18
- access: tmStore.access,
19
- partitioning: tmStore.partitioning,
20
- };
21
- }),
22
- });
7
+ try {
8
+ res.json({
9
+ version: serverPackage.version,
10
+ description: serverPackage.description,
11
+ baseDir: path.resolve(getBaseDir()),
12
+ providers: mm.dispatcher.providers.map(p => ({id: p.id, type: p.constructor.name})),
13
+ channels: mm.rm.channelIds.map(id => mm.rm.getChannel(id).getInfo()),
14
+ tmStores: mm.tmm.tmStoreIds.map(id => mm.tmm.getTmStoreInfo(id)),
15
+ snapStores: mm.rm.snapStoreIds.map(id => mm.rm.getSnapStoreInfo(id)),
16
+ });
17
+ } catch (error) {
18
+ logInfo`Error in /info: ${error.message}`;
19
+ res.status(500).json({
20
+ error: 'Failed to get system info',
21
+ message: error.message
22
+ });
23
+ }
23
24
  });
24
25
  }
@@ -1,16 +1,14 @@
1
1
  import { logInfo, logVerbose } from '@l10nmonster/core';
2
2
 
3
3
  export function setupProviderRoute(apiRouter, mm) {
4
- apiRouter.get('/providers', async (req, res) => {
5
- logInfo`/providers`;
4
+ apiRouter.get('/providers/:providerId', async (req, res) => {
5
+ const { providerId } = req.params;
6
+ logInfo`/providers/${providerId}`;
6
7
  try {
7
- const providers = {};
8
- for (const provider of mm.dispatcher.providers) {
9
- const { id, ...info } = await provider.info();
10
- providers[id] = { properties: provider, info };
11
- }
12
- logVerbose`Returned provider info for ${Object.keys(providers).length} providers`;
13
- res.json(providers);
8
+ const provider = mm.dispatcher.getProvider(providerId);
9
+ const { id, ...info } = await provider.info();
10
+ logVerbose`Returned provider info for ${id}`;
11
+ res.json({ properties: provider, info });
14
12
  } catch (error) {
15
13
  console.error('Error fetching provider data:', error);
16
14
  res.status(500).json({ error: 'Failed to fetch provider data' });
package/routes/sources.js CHANGED
@@ -1,14 +1,52 @@
1
1
  import { logInfo, logVerbose } from '@l10nmonster/core';
2
2
 
3
- export function setupActiveContentStatsRoute(router, mm) {
4
- router.get('/activeContentStats', async (req, res) => {
5
- logInfo`/activeContentStats`;
6
- const sources = {};
7
- for (const channelId of Object.keys(mm.rm.channels)) {
8
- const channelStats = await mm.rm.getActiveContentStats(channelId);
9
- sources[channelId] = channelStats;
3
+ export function setupChannelRoutes(router, mm) {
4
+ router.get('/channel/:channelId', async (req, res) => {
5
+ const { channelId } = req.params;
6
+ logInfo`/channel/${channelId}`;
7
+ try {
8
+ const { ts, store } = await mm.rm.getChannelMeta(channelId);
9
+ const projects = await mm.rm.getActiveContentStats(channelId);
10
+ logVerbose`Returned active content stats for ${projects.length} projects`;
11
+ res.json({ ts, store, projects });
12
+ } catch (error) {
13
+ logInfo`Error in /channel/${channelId}: ${error.message}`;
14
+ res.status(500).json({
15
+ error: 'Failed to get channel data',
16
+ message: error.message
17
+ });
18
+ }
19
+ });
20
+ router.get('/channel/:channelId/:prj', async (req, res) => {
21
+ const { channelId, prj } = req.params;
22
+ const { offset, limit } = req.query;
23
+ logInfo`/channel/${channelId}/${prj} (offset=${offset}, limit=${limit})`;
24
+ try {
25
+ const projectTOC = await mm.rm.getProjectTOC(channelId, prj, offset, limit);
26
+ logVerbose`Returned project TOC for ${prj} with ${projectTOC.length} resources`;
27
+ res.json(projectTOC);
28
+ } catch (error) {
29
+ logInfo`Error in /channel/${channelId}/${prj}: ${error.message}`;
30
+ res.status(500).json({
31
+ error: 'Failed to get project TOC',
32
+ message: error.message
33
+ });
34
+ }
35
+ });
36
+ router.get('/resource/:channelId', async (req, res) => {
37
+ const { channelId } = req.params;
38
+ const { rid } = req.query;
39
+ logInfo`/resource/${channelId}/${rid}`;
40
+ try {
41
+ const resource = await mm.rm.getResourceHandle(channelId, rid, { keepRaw: true });
42
+ logVerbose`Returned resource ${rid} with ${resource.segments.length} segments`;
43
+ res.json(resource);
44
+ } catch (error) {
45
+ logInfo`Error in /resource/${channelId}/${rid}: ${error.message}`;
46
+ res.status(500).json({
47
+ error: 'Failed to get resource',
48
+ message: error.message
49
+ });
10
50
  }
11
- logVerbose`Returned active content stats for ${Object.keys(sources).length} channels`;
12
- res.json(sources);
13
51
  });
14
52
  }
package/routes/status.js CHANGED
@@ -1,30 +1,31 @@
1
1
  import { logInfo, logVerbose } from '@l10nmonster/core';
2
2
 
3
3
  export function setupStatusRoute(router, mm) {
4
- router.get('/status', async (req, res) => {
5
- logInfo`/status`;
4
+ router.get('/status/:channelId', async (req, res) => {
5
+ const { channelId } = req.params;
6
+ logInfo`/status/${channelId}`;
6
7
  try {
7
- const status = await mm.getTranslationStatus();
8
- // source_lang -> target_lang -> channel -> project -> data
8
+ const status = await mm.getTranslationStatus(channelId);
9
+ // channel -> source_lang -> target_lang -> project -> data
9
10
  logVerbose`Returned translation status`;
10
- res.json(status);
11
+ res.json(status[channelId]);
11
12
  } catch (error) {
12
13
  console.error('Error fetching status: ', error);
13
14
  res.status(500).json({ message: 'Problems fetching status data' });
14
15
  }
15
16
  });
16
17
 
17
- router.get('/status/:sourceLang/:targetLang', async (req, res) => {
18
- const { sourceLang, targetLang } = req.params;
19
- logInfo`/status/${sourceLang}/${targetLang}`;
18
+ router.get('/status/:channelId/:sourceLang/:targetLang', async (req, res) => {
19
+ const { channelId, sourceLang, targetLang } = req.params;
20
+ logInfo`/status/${channelId}/${sourceLang}/${targetLang}`;
20
21
  try {
21
22
  const tm = mm.tmm.getTM(sourceLang, targetLang);
22
- const tus = tm.getUntranslatedContent();
23
- logVerbose`Returned ${tus.length} untranslated TUs for ${sourceLang}->${targetLang}`;
23
+ const tus = await tm.getUntranslatedContent(channelId, 500);
24
+ logVerbose`Returned ${tus.length} untranslated TUs for ${sourceLang}->${targetLang} in channel ${channelId}`;
24
25
  res.json(tus);
25
26
  } catch (error) {
26
27
  logInfo`Error: ${error.message}`;
27
28
  res.status(500).json({ error: error.message });
28
29
  }
29
30
  });
30
- }
31
+ }
package/routes/tm.js CHANGED
@@ -1,39 +1,101 @@
1
1
  import { logInfo, logVerbose } from '@l10nmonster/core';
2
2
 
3
+ // Helper function to process search terms - handles exact vs partial matching
4
+ function processSearchTerm(term) {
5
+ if (!term) return undefined;
6
+
7
+ // Check if term is surrounded by double quotes
8
+ if (term.startsWith('"') && term.endsWith('"') && term.length >= 2) {
9
+ // Extract the content inside quotes for exact match
10
+ return term.slice(1, -1);
11
+ }
12
+
13
+ // Default partial match behavior
14
+ return `%${term}%`;
15
+ }
16
+
3
17
  export function setupTmRoutes(router, mm) {
4
18
  router.get('/tm/stats', async (req, res) => {
5
19
  logInfo`/tm/stats`;
6
- const tmInfo = {};
7
- const availableLangPairs = (await mm.tmm.getAvailableLangPairs()).sort();
8
- for (const [sourceLang, targetLang] of availableLangPairs) {
9
- const tm = mm.tmm.getTM(sourceLang, targetLang);
10
- tmInfo[sourceLang] ??= {};
11
- tmInfo[sourceLang][targetLang] = tm.getStats();
20
+ try {
21
+ const availableLangPairs = (await mm.tmm.getAvailableLangPairs()).sort();
22
+ logVerbose`Returned ${availableLangPairs.length} language pairs`;
23
+ res.json(availableLangPairs);
24
+ } catch (error) {
25
+ logInfo`Error in /tm/stats: ${error.message}`;
26
+ res.status(500).json({
27
+ error: 'Failed to get TM stats',
28
+ message: error.message
29
+ });
12
30
  }
13
- logVerbose`Returned TM stats for ${Object.keys(tmInfo).length} lang pairs`;
14
- res.json(tmInfo);
15
31
  });
16
32
 
33
+ router.get('/tm/stats/:sourceLang/:targetLang', async (req, res) => {
34
+ logInfo`/tm/stats/${req.params.sourceLang}/${req.params.targetLang}`;
35
+ try {
36
+ const tm = mm.tmm.getTM(req.params.sourceLang, req.params.targetLang);
37
+ const stats = await tm.getStats();
38
+ logVerbose`Returned TM stats for ${req.params.sourceLang}->${req.params.targetLang}`;
39
+ res.json(stats);
40
+ } catch (error) {
41
+ logInfo`Error in /tm/stats/${req.params.sourceLang}/${req.params.targetLang}: ${error.message}`;
42
+ res.status(500).json({
43
+ error: 'Failed to get TM stats for language pair',
44
+ message: error.message
45
+ });
46
+ }
47
+ });
48
+
49
+ router.get('/tm/lowCardinalityColumns/:sourceLang/:targetLang', async (req, res) => {
50
+ const { sourceLang, targetLang } = req.params;
51
+ logInfo`/tm/lowCardinalityColumns/${sourceLang}/${targetLang}`;
52
+ try {
53
+ const tm = mm.tmm.getTM(sourceLang, targetLang);
54
+ const data = await tm.getLowCardinalityColumns();
55
+ logVerbose`Returned TM low cardinality columns for ${sourceLang}->${targetLang}`;
56
+ res.json({ channel: mm.rm.channelIds, ...data });
57
+ } catch (error) {
58
+ logInfo`Error in /tm/lowCardinalityColumns/${sourceLang}/${targetLang}: ${error.message}`;
59
+ res.status(500).json({
60
+ error: 'Failed to get low cardinality columns',
61
+ message: error.message
62
+ });
63
+ }
64
+ });
17
65
  router.get('/tm/search', async (req, res) => {
18
66
  logInfo`/tm/search`;
19
- const { sourceLang, targetLang, page, limit, guid, jobGuid, rid, sid, nsrc, ntgt, notes, q, translationProvider } = req.query;
20
- const tm = mm.tmm.getTM(sourceLang, targetLang);
21
- const limitInt = limit ? parseInt(limit, 10) : 100;
22
- const pageInt = page ? parseInt(page, 10) : 1;
23
- const offset = (pageInt - 1) * limitInt;
24
- const data = tm.search(offset, limitInt, {
25
- guid: guid && `%${guid}%`,
26
- jobGuid: jobGuid && `%${jobGuid}%`,
27
- rid: rid && `%${rid}%`,
28
- sid: sid && `%${sid}%`,
29
- nsrc: nsrc && `%${nsrc}%`,
30
- ntgt: ntgt && `%${ntgt}%`,
31
- notes: notes && `%${notes}%`,
32
- q,
33
- translationProvider: translationProvider && `%${translationProvider}%`,
34
- });
35
- logVerbose`Returned TM search results for ${data.length} entries`;
36
- res.json({ data, page: pageInt, limit: limitInt });
67
+ try {
68
+ const { sourceLang, targetLang, page, limit, guid, nid, jobGuid, rid, sid, channel, nsrc, ntgt, notes, tconf, q, translationProvider, onlyTNotes, minTS, maxTS } = req.query;
69
+ const tm = mm.tmm.getTM(sourceLang, targetLang);
70
+ const limitInt = limit ? parseInt(limit, 10) : 100;
71
+ const pageInt = page ? parseInt(page, 10) : 1;
72
+ const offset = (pageInt - 1) * limitInt;
73
+ const data = await tm.search(offset, limitInt, {
74
+ guid: processSearchTerm(guid),
75
+ nid: processSearchTerm(nid),
76
+ jobGuid: processSearchTerm(jobGuid),
77
+ rid: processSearchTerm(rid),
78
+ sid: processSearchTerm(sid),
79
+ channel: processSearchTerm(channel),
80
+ nsrc: processSearchTerm(nsrc),
81
+ ntgt: processSearchTerm(ntgt),
82
+ notes: processSearchTerm(notes),
83
+ tconf: processSearchTerm(tconf),
84
+ q,
85
+ minTS,
86
+ maxTS,
87
+ translationProvider: processSearchTerm(translationProvider),
88
+ onlyTNotes: onlyTNotes === '1',
89
+ });
90
+ logVerbose`Returned TM search results for ${data.length} entries`;
91
+ res.json({ data, page: pageInt, limit: limitInt });
92
+ } catch (error) {
93
+ logInfo`Error in /tm/search: ${error.message}`;
94
+ res.status(500).json({
95
+ error: 'Failed to search translation memory',
96
+ message: error.message
97
+ });
98
+ }
37
99
  });
38
100
 
39
101
  router.get('/tm/job/:jobGuid', async (req, res) => {
@@ -68,4 +130,5 @@ export function setupTmRoutes(router, mm) {
68
130
  });
69
131
  }
70
132
  });
133
+
71
134
  }
@@ -0,0 +1 @@
1
+ import{r as o,j as e,B as c,T as a,V as h,F as C,v as b}from"./vendor-BVSmEsOp.js";import{g as S}from"./index-Dl1WS_Lc.js";const T=()=>{const[i,p]=o.useState({}),[x,l]=o.useState(!0),d=()=>{try{const t=sessionStorage.getItem("tmCart");return t?JSON.parse(t):{}}catch(t){return console.error("Error loading cart:",t),{}}},u=t=>{sessionStorage.setItem("tmCart",JSON.stringify(t)),window.dispatchEvent(new Event("cartUpdated"))},n=()=>{l(!0);const t=d();p(t),l(!1)},f=(t,r)=>{const s=d();s[t]&&(Array.isArray(s[t])?(s[t].splice(r,1),s[t].length===0&&delete s[t]):(s[t].tus.splice(r,1),s[t].tus.length===0&&delete s[t]),u(s),n())},j=()=>{u({}),n()},m=()=>Object.values(i).reduce((t,r)=>Array.isArray(r)?t+r.length:t+(r.tus?r.tus.length:0),0);if(o.useEffect(()=>{n()},[]),x)return e.jsx(c,{py:6,px:6,children:e.jsx(a,{children:"Loading cart..."})});const g=m();return e.jsx(c,{py:6,px:6,children:e.jsxs(h,{gap:6,align:"stretch",children:[e.jsxs(C,{align:"center",justify:"space-between",children:[e.jsx(a,{fontSize:"2xl",fontWeight:"bold",children:"Translation Memory Cart"}),g>0&&e.jsx(b,{colorPalette:"red",variant:"outline",onClick:j,children:"Empty Cart"})]}),g===0?e.jsxs(c,{p:8,textAlign:"center",borderWidth:"1px",borderRadius:"lg",bg:"bg.muted",children:[e.jsx(a,{fontSize:"lg",color:"fg.default",mb:2,children:"Your cart is empty"}),e.jsx(a,{color:"fg.muted",children:"Add translation units from the TM pages to get started"})]}):e.jsx(h,{gap:6,align:"stretch",children:Object.entries(i).map(([t,r])=>e.jsx(S,{langPairKey:t,tus:r,onRemoveTU:f},t))})]})})};export{T as default};
@@ -0,0 +1 @@
1
+ import{a8 as C,r as F,i as R,j as e,B as n,S as L,T as r,A as T,V as x,F as c,$ as w,g as f,v as D,a6 as N,a7 as U}from"./vendor-BVSmEsOp.js";import{f as B}from"./index-Dl1WS_Lc.js";function W(i){return i.map(a=>typeof a=="string"?a:`{{${a.t}}}`).join("")}function u(i){return new Date(i).toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}function J(i){return i===0?"Free":i==null?"Unknown":`$${i.toFixed(2)}`}function G(i){if(!i)return null;const a=/(https:\/\/[^\s]+)/g;return i.split(a).map((s,j)=>s.match(a)?e.jsx(r,{as:"a",href:s,target:"_blank",rel:"noopener noreferrer",color:"blue.600",textDecoration:"underline",_hover:{color:"blue.800"},children:s},j):s)}const $=()=>{const{jobGuid:i}=C(),[a,p]=F.useState({}),{data:s,isLoading:j,error:S}=R({queryKey:["job",i],queryFn:()=>B(`/api/tm/job/${i}`),retry:(t,o)=>o?.status===404?!1:t<3});if(j)return e.jsx(n,{display:"flex",justifyContent:"center",mt:10,children:e.jsx(L,{size:"xl"})});if(S)return e.jsx(n,{mt:5,px:6,children:e.jsxs(n,{p:4,bg:"red.subtle",borderRadius:"md",borderWidth:"1px",borderColor:"red.muted",children:[e.jsx(r,{fontWeight:"bold",color:"red.600",mb:2,children:"Error"}),e.jsx(r,{color:"red.600",children:S?.message||"Failed to fetch job data"})]})});if(!s)return e.jsx(n,{mt:5,px:6,children:e.jsx(T,{status:"warning",children:e.jsxs(n,{children:[e.jsx(r,{fontWeight:"bold",children:"Job Not Found"}),e.jsxs(r,{children:["Job with GUID ",i," was not found."]})]})})});const P=t=>{switch(t?.toLowerCase()){case"done":case"completed":return"green";case"failed":case"error":return"red";case"in-progress":case"running":return"orange";default:return"gray"}},A=(t,o)=>{const h=new Set(["jobGuid","guid","rid","sid","nsrc","ntgt","notes","q","prj"]),b=Object.entries(t).filter(([l])=>!h.has(l)),y=t.guid||o,m=a[y]||!1;return e.jsx(n,{p:4,borderWidth:"1px",borderRadius:"lg",bg:"white",shadow:"sm",mb:4,children:e.jsxs(x,{gap:3,align:"stretch",children:[e.jsx(c,{justify:"space-between",align:"start",wrap:"wrap",children:e.jsxs(x,{gap:1,align:"stretch",children:[e.jsxs(c,{align:"center",gap:2,children:[e.jsx(r,{fontSize:"xs",fontWeight:"bold",color:"fg.muted",children:"GUID:"}),e.jsx(r,{fontSize:"xs",fontFamily:"mono",color:"blue.600",wordBreak:"break-all",children:t.guid})]}),t.rid&&e.jsxs(c,{align:"center",gap:2,children:[e.jsx(r,{fontSize:"xs",fontWeight:"bold",color:"fg.muted",children:"RID:"}),e.jsx(r,{fontSize:"xs",fontFamily:"mono",color:"blue.600",wordBreak:"break-all",children:t.rid})]}),t.sid&&e.jsxs(c,{align:"center",gap:2,children:[e.jsx(r,{fontSize:"xs",fontWeight:"bold",color:"fg.muted",children:"SID:"}),e.jsx(r,{fontSize:"xs",fontFamily:"mono",color:"blue.600",children:t.sid})]})]})}),e.jsxs(n,{children:[e.jsx(r,{fontSize:"xs",fontWeight:"bold",color:"fg.muted",children:"Source:"}),e.jsx(r,{fontSize:"sm",p:2,bg:"blue.subtle",borderRadius:"md",dir:s.sourceLang?.startsWith("he")||s.sourceLang?.startsWith("ar")?"rtl":"ltr",children:Array.isArray(t.nsrc)?W(t.nsrc):t.nsrc})]}),e.jsxs(n,{children:[e.jsx(r,{fontSize:"xs",fontWeight:"bold",color:"fg.muted",children:"Target:"}),e.jsx(r,{fontSize:"sm",p:2,bg:"green.subtle",borderRadius:"md",dir:s.targetLang?.startsWith("he")||s.targetLang?.startsWith("ar")?"rtl":"ltr",children:Array.isArray(t.ntgt)?W(t.ntgt):t.ntgt})]}),t.notes&&e.jsxs(n,{children:[e.jsx(r,{fontSize:"xs",fontWeight:"bold",color:"fg.muted",children:"Notes:"}),e.jsxs(n,{p:2,bg:"yellow.subtle",borderRadius:"md",children:[t.notes.desc&&e.jsx(r,{fontSize:"xs",mb:2,whiteSpace:"pre-wrap",children:t.notes.desc}),t.notes.ph&&e.jsxs(n,{children:[e.jsx(r,{fontSize:"xs",fontWeight:"bold",mb:1,children:"Placeholders:"}),Object.entries(t.notes.ph).map(([l,d])=>e.jsxs(n,{mb:1,children:[e.jsx(r,{fontSize:"xs",fontFamily:"mono",color:"blue.600",children:l}),e.jsxs(r,{fontSize:"xs",color:"gray.600",children:[d.desc," (e.g., ",d.sample,")"]})]},l))]})]})]}),b.length>0&&e.jsxs(n,{children:[e.jsxs(c,{align:"center",justify:"space-between",mb:2,children:[e.jsx(D,{size:"xs",variant:"ghost",onClick:()=>p(l=>({...l,[y]:!m})),p:1,children:e.jsx(r,{fontSize:"xs",color:"blue.600",children:m?"Hide additional properties":`Show ${b.length} additional properties`})}),e.jsxs(w,{gap:2,children:[t.prj&&e.jsx(f,{size:"sm",colorPalette:"purple",children:t.prj}),t.q&&e.jsxs(f,{size:"sm",colorPalette:"blue",children:["Q: ",t.q]})]})]}),e.jsx(N,{open:m,children:e.jsx(U,{children:e.jsx(n,{p:3,bg:"gray.subtle",borderRadius:"md",children:b.map(([l,d])=>{let g=d;return l==="ts"&&typeof d=="number"?g=u(d):typeof d=="object"?g=JSON.stringify(d,null,2):g=String(d),e.jsxs(c,{align:"start",mb:2,gap:2,children:[e.jsxs(r,{fontSize:"xs",fontWeight:"bold",color:"gray.600",minW:"fit-content",children:[l,":"]}),e.jsx(r,{fontSize:"xs",fontFamily:"mono",color:"blue.600",flex:"1",wordBreak:"break-all",children:g})]},l)})})})})]})]})},t.guid||o)},k=new Set(["jobGuid","sourceLang","targetLang","translationProvider","updatedAt","taskName","inflight","status","statusDescription","tus","estimatedCost"]),z=Object.entries(s).filter(([t])=>!k.has(t));return e.jsxs(n,{p:6,minH:"100vh",bg:"gray.50",children:[e.jsxs(r,{fontSize:"3xl",fontWeight:"bold",mb:6,color:"fg.default",children:["Job ",s.jobGuid]}),e.jsxs(x,{gap:6,align:"stretch",maxW:"6xl",mx:"auto",children:[e.jsx(n,{p:6,bg:"white",borderRadius:"lg",shadow:"sm",children:e.jsxs(x,{gap:4,align:"stretch",children:[e.jsxs(c,{justify:"space-between",align:"center",wrap:"wrap",children:[e.jsxs(w,{gap:6,wrap:"wrap",align:"center",children:[e.jsxs(n,{children:[e.jsx(r,{fontSize:"sm",fontWeight:"bold",color:"fg.muted",children:"Language Pair:"}),e.jsxs(r,{fontSize:"sm",children:[s.sourceLang," → ",s.targetLang]})]}),e.jsxs(n,{children:[e.jsx(r,{fontSize:"sm",fontWeight:"bold",color:"fg.muted",children:"Provider:"}),e.jsx(r,{fontSize:"sm",children:s.translationProvider})]}),s.updatedAt&&e.jsxs(n,{children:[e.jsx(r,{fontSize:"sm",fontWeight:"bold",color:"fg.muted",children:"Updated:"}),e.jsx(r,{fontSize:"sm",children:u(new Date(s.updatedAt).getTime())})]}),s.taskName&&e.jsxs(n,{children:[e.jsx(r,{fontSize:"sm",fontWeight:"bold",color:"fg.muted",children:"Task:"}),e.jsx(r,{fontSize:"sm",children:s.taskName})]}),s.estimatedCost!==void 0&&e.jsxs(n,{children:[e.jsx(r,{fontSize:"sm",fontWeight:"bold",color:"fg.muted",children:"Estimated Cost:"}),e.jsx(r,{fontSize:"sm",children:J(s.estimatedCost)})]}),s.inflight?.length>0&&e.jsxs(f,{colorPalette:"orange",size:"sm",children:[s.inflight.length," TUs In Flight"]})]}),e.jsxs(n,{textAlign:"right",children:[e.jsx(f,{size:"lg",colorPalette:P(s.status),children:s.status?.toUpperCase()}),s.statusDescription&&e.jsx(r,{fontSize:"xs",color:"fg.muted",mt:1,children:G(s.statusDescription)})]})]}),z.length>0&&e.jsxs(n,{mt:4,pt:4,borderTop:"1px",borderColor:"border.default",children:[e.jsx(r,{fontSize:"sm",fontWeight:"bold",color:"fg.muted",mb:3,children:"Additional Properties"}),e.jsx(n,{bg:"gray.subtle",p:3,borderRadius:"md",children:z.map(([t,o])=>{let h=o;return t==="ts"&&typeof o=="number"?h=u(o):typeof o=="object"?h=JSON.stringify(o,null,2):h=String(o),e.jsxs(c,{align:"start",mb:2,gap:2,children:[e.jsxs(r,{fontSize:"xs",fontWeight:"bold",color:"gray.600",minW:"fit-content",children:[t,":"]}),e.jsx(r,{fontSize:"xs",fontFamily:"mono",color:"blue.600",flex:"1",wordBreak:"break-all",children:h})]},t)})})]})]})}),s.inflight?.length>0&&e.jsxs(n,{p:4,bg:"orange.subtle",borderRadius:"lg",borderWidth:"1px",borderColor:"orange.muted",children:[e.jsx(r,{fontSize:"sm",fontWeight:"bold",color:"orange.700",mb:2,children:"Translation In Progress"}),e.jsxs(r,{fontSize:"sm",color:"orange.600",children:[s.inflight.length," translation unit",s.inflight.length===1?"":"s"," ",s.inflight.length===1?"is":"are"," still being translated."]})]}),e.jsxs(n,{children:[e.jsxs(r,{fontSize:"xl",fontWeight:"bold",mb:4,children:["Translation Units (",s.tus?.length||0,")"]}),s.tus?.length>0?s.tus.map((t,o)=>A(t,o)):e.jsx(n,{p:6,bg:"white",borderRadius:"lg",textAlign:"center",children:e.jsx(r,{color:"fg.muted",children:"No translation units found."})})]})]})]})};export{$ as default};
@@ -0,0 +1 @@
1
+ import{a0 as m,r as l,i as P,Y as y,j as r,B as t,S as g,A as S,T as p,F as b}from"./vendor-BVSmEsOp.js";import{d as E,e as F,f as w}from"./index-Dl1WS_Lc.js";const q=()=>{const[x,d]=m(),[o,a]=l.useState(()=>x.get("provider")||null),{data:f={},isLoading:h,error:n}=P({queryKey:["info"],queryFn:()=>w("/api/info")}),c=f.providers||[],i=l.useMemo(()=>c.slice().sort((e,s)=>{const v=typeof e=="object"&&e?.id?e.id:e,j=typeof s=="object"&&s?.id?s.id:s;return v.localeCompare(j)}),[c]);y.useEffect(()=>{if(i.length>0&&!o){const e=i[0],s=typeof e=="object"&&e?.id?e.id:e;a(s),d({provider:s},{replace:!0})}},[i,o,d]);const u=e=>{a(e),d({provider:e})};return h?r.jsx(t,{display:"flex",justifyContent:"center",mt:10,children:r.jsx(g,{size:"xl"})}):n?r.jsx(t,{mt:5,px:6,children:r.jsx(S,{status:"error",children:r.jsxs(t,{children:[r.jsx(p,{fontWeight:"bold",children:"Error"}),r.jsx(p,{children:n?.message||"Failed to fetch provider data"})]})})}):r.jsx(t,{py:6,px:6,children:r.jsx(t,{borderWidth:"1px",borderRadius:"lg",bg:"white",shadow:"sm",overflow:"hidden",minH:"70vh",children:r.jsxs(b,{children:[r.jsx(E,{providers:i,selectedProvider:o,onProviderSelect:u}),r.jsx(F,{providerId:o})]})})})};export{q as default};
@@ -0,0 +1 @@
1
+ import{i as m,j as e,B as t,S as g,A as y,T as n,V as C,r as x,I as E,a6 as w,a7 as z,G as W}from"./vendor-BVSmEsOp.js";import{S as D,f as b}from"./index-Dl1WS_Lc.js";const L=({channelId:a})=>{const[d,f]=x.useState(!1),[i,h]=x.useState(!0),c=x.useRef(null);x.useEffect(()=>{const s=new IntersectionObserver(l=>{l.forEach(j=>{j.isIntersecting&&(f(!0),s.disconnect())})},{rootMargin:"200px",threshold:0});return c.current&&s.observe(c.current),()=>s.disconnect()},[]);const{data:r,isLoading:p,error:u}=m({queryKey:["channel",a],queryFn:()=>b(`/api/channel/${a}`),enabled:d}),o=r?.projects||[];return r?.ts,r?.store,e.jsxs(t,{ref:c,p:6,borderWidth:"2px",borderRadius:"lg",bg:"white",borderColor:"green.200",minW:"600px",maxW:"1200px",w:"100%",children:[e.jsxs(t,{display:"flex",alignItems:"center",gap:3,flexWrap:"wrap",mb:6,pb:4,borderBottom:"2px",borderColor:"green.100",children:[e.jsx(E,{"aria-label":i?"Collapse channel":"Expand channel",onClick:()=>h(!i),variant:"ghost",size:"sm",children:i?"▼":"▶"}),e.jsxs(t,{children:[e.jsx(n,{fontSize:"sm",color:"fg.muted",mb:1,children:"Channel"}),e.jsx(n,{fontSize:"lg",fontWeight:"bold",color:"green.600",children:a})]}),r&&e.jsx(t,{flex:"1",textAlign:"right",children:(()=>{const s=r?.ts,l=r?.store;if(!s)return e.jsx(n,{fontSize:"sm",color:"fg.muted",children:"Never snapped"});const S=new Date(s).toLocaleDateString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});return e.jsxs(e.Fragment,{children:[e.jsxs(n,{fontSize:"sm",color:"fg.muted",children:["Snapped on ",S]}),l&&e.jsxs(n,{fontSize:"sm",color:"fg.muted",children:["Imported from snap store"," ",e.jsx(n,{as:"span",fontWeight:"bold",color:"blue.600",children:l})]})]})})()}),p&&e.jsx(g,{size:"md"})]}),e.jsx(w,{open:i,children:e.jsx(z,{children:u?e.jsx(y,{status:"error",children:e.jsxs(t,{children:[e.jsxs(n,{fontWeight:"bold",children:["Error loading channel ",a]}),e.jsx(n,{children:u.message||"Unknown error"})]})}):p?e.jsx(t,{display:"flex",justifyContent:"center",py:8,children:e.jsx(n,{color:"fg.muted",children:"Loading channel data..."})}):o&&o.length>0?e.jsx(W,{templateColumns:{base:"1fr",lg:"repeat(auto-fit, minmax(600px, 1fr))"},gap:4,justifyItems:"center",children:o.sort((s,l)=>new Date(l.lastModified)-new Date(s.lastModified)).map((s,l)=>e.jsx(D,{item:s,channelId:a},l))}):o&&o.length===0?e.jsx(t,{display:"flex",justifyContent:"center",py:8,children:e.jsx(n,{color:"fg.muted",children:"This channel has no projects"})}):d?e.jsx(t,{display:"flex",justifyContent:"center",py:8,children:e.jsx(n,{color:"fg.muted",children:"No content available for this channel"})}):e.jsx(t,{display:"flex",justifyContent:"center",py:8,children:e.jsx(n,{color:"fg.muted",children:"Scroll down to load content..."})})})}),!i&&o&&o.length>0&&e.jsx(t,{display:"flex",justifyContent:"center",py:4,children:e.jsxs(n,{fontSize:"sm",color:"gray.600",fontStyle:"italic",children:[o.length," project",o.length!==1?"s":""," (collapsed)"]})})]})},I=()=>{const{data:a,isLoading:d,error:f}=m({queryKey:["info"],queryFn:()=>b("/api/info")}),i=a?.channels?.map(r=>r.id)||[],h=d,c=f;return h?e.jsx(t,{display:"flex",justifyContent:"center",mt:10,children:e.jsx(g,{size:"xl"})}):c?e.jsx(t,{mt:5,px:6,children:e.jsx(y,{status:"error",children:e.jsxs(t,{children:[e.jsx(n,{fontWeight:"bold",children:"Error"}),e.jsx(n,{children:c})]})})}):e.jsx(t,{py:6,px:6,children:e.jsxs(C,{gap:6,align:"center",children:[i.map(r=>e.jsx(L,{channelId:r},r)),i.length===0&&!h&&e.jsx(n,{mt:4,color:"fg.muted",children:"No channels found."})]})})};export{I as default};
@@ -0,0 +1 @@
1
+ import{a8 as C,u as w,r as b,ad as B,j as e,B as r,S as g,A as z,T as n,L as v,h as M,g as D,F as E}from"./vendor-BVSmEsOp.js";import{a as I,f as L}from"./index-Dl1WS_Lc.js";const k=()=>{const{channelId:l,prj:f}=C(),p=w(),h=b.useRef(null),{data:j,isLoading:S,error:m,fetchNextPage:u,hasNextPage:d,isFetchingNextPage:x}=B({queryKey:["projectTOC",l,f],queryFn:async({pageParam:t=0})=>{const c=await L(`/api/channel/${l}/${f}?offset=${t}&limit=100`);return{data:c,offset:t,limit:100,hasMore:c.length===100}},getNextPageParam:t=>t.hasMore?t.offset+t.limit:void 0,staleTime:3e4}),a=j?.pages.flatMap(t=>t.data)||[];b.useEffect(()=>{if(!d||x)return;const t=new IntersectionObserver(s=>{s[0].isIntersecting&&u()},{rootMargin:"100px",threshold:0});return h.current&&t.observe(h.current),()=>t.disconnect()},[d,x,u]);const A=t=>new Date(t).toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),y=t=>{const s=new Date,c=new Date(t),o=Math.floor((s-c)/1e3),i=new Intl.RelativeTimeFormat("en",{numeric:"auto",style:"short"});return o<60?i.format(-o,"second"):o<3600?i.format(-Math.floor(o/60),"minute"):o<86400?i.format(-Math.floor(o/3600),"hour"):o<2592e3?i.format(-Math.floor(o/86400),"day"):o<31536e3?i.format(-Math.floor(o/2592e3),"month"):i.format(-Math.floor(o/31536e3),"year")};return S?e.jsx(r,{display:"flex",justifyContent:"center",mt:10,children:e.jsx(g,{size:"xl"})}):m?e.jsx(r,{mt:5,px:6,children:e.jsx(z,{status:"error",children:e.jsxs(r,{children:[e.jsx(n,{fontWeight:"bold",children:"Error"}),e.jsx(n,{children:m?.message||"Failed to fetch project data"})]})})}):e.jsxs(r,{py:6,px:6,children:[e.jsx(I,{channelId:l,project:f,onBackClick:()=>p("/sources"),backLabel:"Back to sources"}),a.length===0?e.jsx(r,{p:8,borderWidth:"1px",borderRadius:"md",bg:"white",textAlign:"center",children:e.jsx(n,{color:"fg.muted",children:"No resources found for this project."})}):e.jsxs(r,{bg:"white",borderRadius:"lg",shadow:"sm",overflow:"auto",maxH:"78vh",children:[e.jsxs(r,{as:"table",w:"100%",fontSize:"sm",children:[e.jsx(r,{as:"thead",bg:"blue.subtle",borderBottom:"2px",borderColor:"blue.muted",position:"sticky",top:0,zIndex:1,children:e.jsxs(r,{as:"tr",children:[e.jsx(r,{as:"th",p:4,textAlign:"left",borderBottom:"1px",borderColor:"border.default",children:e.jsx(n,{fontSize:"sm",fontWeight:"bold",color:"blue.600",children:"RESOURCE ID"})}),e.jsx(r,{as:"th",p:4,textAlign:"left",borderBottom:"1px",borderColor:"border.default",children:e.jsx(n,{fontSize:"sm",fontWeight:"bold",color:"blue.600",children:"SOURCE LANGUAGE"})}),e.jsx(r,{as:"th",p:4,textAlign:"center",borderBottom:"1px",borderColor:"border.default",children:e.jsx(n,{fontSize:"sm",fontWeight:"bold",color:"blue.600",children:"SEGMENTS"})}),e.jsx(r,{as:"th",p:4,textAlign:"center",borderBottom:"1px",borderColor:"border.default",children:e.jsx(n,{fontSize:"sm",fontWeight:"bold",color:"blue.600",children:"LAST MODIFIED"})})]})}),e.jsx(r,{as:"tbody",children:a.sort((t,s)=>new Date(s.modifiedAt)-new Date(t.modifiedAt)).map((t,s)=>e.jsxs(r,{as:"tr",_hover:{bg:"gray.subtle"},children:[e.jsx(r,{as:"td",p:4,borderBottom:"1px",borderColor:"border.subtle",children:e.jsx(v,{as:M,to:`/sources/${l}?rid=${encodeURIComponent(t.rid)}`,fontSize:"sm",fontFamily:"mono",color:"blue.600",wordBreak:"break-all",_hover:{textDecoration:"underline"},children:t.rid})}),e.jsx(r,{as:"td",p:4,borderBottom:"1px",borderColor:"border.subtle",children:e.jsx(D,{colorPalette:"purple",size:"sm",children:t.sourceLang})}),e.jsx(r,{as:"td",p:4,borderBottom:"1px",borderColor:"border.subtle",textAlign:"center",children:e.jsx(n,{fontSize:"sm",fontWeight:"semibold",color:"orange.600",children:t.segmentCount.toLocaleString()})}),e.jsx(r,{as:"td",p:4,borderBottom:"1px",borderColor:"border.subtle",textAlign:"center",children:e.jsx(n,{fontSize:"sm",color:"fg.muted",title:A(t.modifiedAt),children:y(t.modifiedAt)})})]},`${t.rid}-${s}`))})]}),d&&e.jsx(r,{ref:h,p:4,textAlign:"center",children:x&&e.jsxs(E,{justify:"center",align:"center",gap:2,children:[e.jsx(g,{size:"sm"}),e.jsx(n,{fontSize:"sm",color:"fg.muted",children:"Loading more resources..."})]})}),!d&&a.length>0&&e.jsx(r,{p:4,textAlign:"center",children:e.jsxs(n,{fontSize:"sm",color:"fg.muted",children:["All ",a.length," resources loaded"]})})]})]})};export{k as default};