@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.
- package/CLAUDE.md +81 -13
- package/index.js +19 -2
- package/package.json +1 -1
- package/routes/dispatcher.js +17 -7
- package/routes/info.js +17 -16
- package/routes/providers.js +7 -9
- package/routes/sources.js +47 -9
- package/routes/status.js +12 -11
- package/routes/tm.js +89 -26
- package/ui/dist/assets/Cart-B_Zkvuz_.js +1 -0
- package/ui/dist/assets/Job-B2D10u5q.js +1 -0
- package/ui/dist/assets/Providers-UAlXnJQI.js +1 -0
- package/ui/dist/assets/Sources-DcwZBkaA.js +1 -0
- package/ui/dist/assets/SourcesDetail-11v96V44.js +1 -0
- package/ui/dist/assets/SourcesResource-CPGv3HQ8.js +1 -0
- package/ui/dist/assets/Status-C_qyyf2l.js +1 -0
- package/ui/dist/assets/StatusDetail-CXE95RTb.js +1 -0
- package/ui/dist/assets/TM-DIDYSCIe.js +1 -0
- package/ui/dist/assets/TMDetail-BmHvlsJe.js +1 -0
- package/ui/dist/assets/Welcome-Bd1y86xQ.js +1 -0
- package/ui/dist/assets/index-Dl1WS_Lc.js +1 -0
- package/ui/dist/assets/index-HzbF4bPY.js +2 -0
- package/ui/dist/assets/vendor-BVSmEsOp.js +75 -0
- package/ui/dist/index.html +3 -1
- package/ui/dist/assets/Cart-jHOAGjwC.js +0 -1
- package/ui/dist/assets/Job-vnJpBXcp.js +0 -1
- package/ui/dist/assets/Providers-DUp_y6mi.js +0 -1
- package/ui/dist/assets/Sources-CC5dBiir.js +0 -1
- package/ui/dist/assets/Status-SRPm28Cg.js +0 -1
- package/ui/dist/assets/StatusDetail-DqXzi16U.js +0 -1
- package/ui/dist/assets/TM-CUqywRKC.js +0 -1
- package/ui/dist/assets/TMDetail-Bl4xHL_X.js +0 -1
- package/ui/dist/assets/Welcome-D33rOKBc.js +0 -1
- package/ui/dist/assets/api-CQ_emPlI.js +0 -1
- package/ui/dist/assets/badge-D0XxOmqE.js +0 -1
- package/ui/dist/assets/grid-c9ySqPWI.js +0 -1
- package/ui/dist/assets/index-BHll-Wur.js +0 -1
- package/ui/dist/assets/index-CE6JgXwC.js +0 -76
- package/ui/dist/assets/input-C_b-hL0i.js +0 -1
- package/ui/dist/assets/link-BJrTt4Ej.js +0 -1
- package/ui/dist/assets/namespace-DqVzlNZ0.js +0 -1
- package/ui/dist/assets/select-B7OxVhQ5.js +0 -1
- package/ui/dist/assets/tooltip-DsbPmc5I.js +0 -1
- package/ui/dist/assets/use-field-context-DPgnX2eS.js +0 -1
- package/ui/dist/assets/use-presence-context-w7qGDwCc.js +0 -1
- package/ui/dist/assets/useQuery-DJATtsyt.js +0 -1
- 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 #
|
|
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
|
|
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
|
|
627
|
-
- **GET `/api/
|
|
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
|
|
631
|
-
- **GET `/api/tm/
|
|
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` (
|
|
636
|
-
- `nsrc`, `ntgt` (
|
|
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 {
|
|
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
|
-
|
|
59
|
+
setupChannelRoutes(apiRouter, mm);
|
|
43
60
|
setupTmRoutes(apiRouter, mm);
|
|
44
61
|
setupProviderRoute(apiRouter, mm);
|
|
45
62
|
setupDispatcherRoutes(apiRouter, mm);
|
package/package.json
CHANGED
package/routes/dispatcher.js
CHANGED
|
@@ -28,14 +28,24 @@ export function setupDispatcherRoutes(router, mm) {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
logVerbose`
|
|
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
|
|
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
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
}
|
package/routes/providers.js
CHANGED
|
@@ -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
|
-
|
|
4
|
+
apiRouter.get('/providers/:providerId', async (req, res) => {
|
|
5
|
+
const { providerId } = req.params;
|
|
6
|
+
logInfo`/providers/${providerId}`;
|
|
6
7
|
try {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
4
|
-
router.get('/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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};
|