@tpitre/story-ui 3.10.0 → 3.10.2
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/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +5 -0
- package/dist/mcp-server/routes/generateStory.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStory.js +54 -63
- package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStoryStream.js +22 -17
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.js +23 -7
- package/package.json +1 -1
- package/templates/StoryUI/StoryUIPanel.tsx +19 -5
package/dist/cli/update.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../cli/update.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../cli/update.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAgTD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkItF;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CA+BpC"}
|
package/dist/cli/update.js
CHANGED
|
@@ -12,6 +12,11 @@ const MANAGED_FILES = [
|
|
|
12
12
|
target: 'src/stories/StoryUI/StoryUIPanel.tsx',
|
|
13
13
|
description: 'Main chat panel component'
|
|
14
14
|
},
|
|
15
|
+
{
|
|
16
|
+
source: 'templates/StoryUI/StoryUIPanel.css',
|
|
17
|
+
target: 'src/stories/StoryUI/StoryUIPanel.css',
|
|
18
|
+
description: 'Panel styles'
|
|
19
|
+
},
|
|
15
20
|
{
|
|
16
21
|
source: 'templates/StoryUI/StoryUIPanel.mdx',
|
|
17
22
|
target: 'src/stories/StoryUI/StoryUIPanel.mdx',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateStory.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAgc5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"generateStory.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAgc5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DAwfxE"}
|
|
@@ -605,23 +605,68 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
605
605
|
// Fallback to cleaned prompt if Claude fails
|
|
606
606
|
aiTitle = cleanPromptForTitle(prompt);
|
|
607
607
|
}
|
|
608
|
-
//
|
|
608
|
+
// Generate unique ID and filename FIRST so we can include hash in title
|
|
609
|
+
// This is done early to ensure unique titles prevent Storybook duplicate ID errors
|
|
610
|
+
const fileExtension = frameworkAdapter?.defaultExtension || '.stories.tsx';
|
|
611
|
+
const timestamp = Date.now();
|
|
612
|
+
// Always generate a hash - either from existing IDs or new
|
|
613
|
+
let hash;
|
|
614
|
+
let finalFileName;
|
|
615
|
+
let storyId;
|
|
616
|
+
if (isActualUpdate && (fileName || providedStoryId)) {
|
|
617
|
+
// For updates, preserve the existing fileName and ID
|
|
618
|
+
if (providedStoryId) {
|
|
619
|
+
storyId = providedStoryId;
|
|
620
|
+
const hashMatch = providedStoryId.match(/^story-([a-f0-9]{8})$/);
|
|
621
|
+
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
622
|
+
finalFileName = fileName || `${providedStoryId}.stories.tsx`;
|
|
623
|
+
logger.log('📝 Using provided storyId:', finalFileName);
|
|
624
|
+
}
|
|
625
|
+
else if (fileName) {
|
|
626
|
+
const hashMatch = fileName.match(/-([a-f0-9]{8})(?:\.stories\.tsx)?$/);
|
|
627
|
+
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
628
|
+
storyId = `story-${hash}`;
|
|
629
|
+
finalFileName = fileName;
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
// Fallback - should not reach here given the if condition, but satisfies TypeScript
|
|
633
|
+
hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
634
|
+
storyId = `story-${hash}`;
|
|
635
|
+
finalFileName = fileNameFromTitle(aiTitle, hash, fileExtension);
|
|
636
|
+
}
|
|
637
|
+
if (!finalFileName.endsWith('.stories.tsx')) {
|
|
638
|
+
finalFileName = finalFileName + '.stories.tsx';
|
|
639
|
+
}
|
|
640
|
+
logger.log('📌 Preserving story identity for update:', { storyId, fileName: finalFileName });
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
// For new stories, ALWAYS generate new IDs with timestamp to ensure uniqueness
|
|
644
|
+
hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
645
|
+
finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
|
|
646
|
+
storyId = `story-${hash}`;
|
|
647
|
+
logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName });
|
|
648
|
+
}
|
|
649
|
+
// Escape the title for TypeScript and append hash for uniqueness
|
|
609
650
|
const prettyPrompt = escapeTitleForTS(aiTitle);
|
|
610
|
-
//
|
|
611
|
-
|
|
651
|
+
// Append hash to title to prevent Storybook duplicate ID errors
|
|
652
|
+
const uniqueTitle = `${prettyPrompt} (${hash})`;
|
|
653
|
+
// Fix title with storyPrefix and hash - handle both single-line and multi-line formats
|
|
654
|
+
// Note: (?::\s*\w+(?:<[^>]+>)?)? handles TypeScript type annotations including generics
|
|
655
|
+
// e.g., "const meta: Meta = {" or "const meta: Meta<typeof Button> = {"
|
|
656
|
+
fixedFileContents = fixedFileContents.replace(/(const\s+meta\s*(?::\s*\w+(?:<[^>]+>)?)?\s*=\s*\{[\s\S]*?title:\s*["'])([^"']+)(["'])/, (match, p1, oldTitle, p3) => {
|
|
612
657
|
// Check if the title already has the prefix to avoid double prefixing
|
|
613
|
-
const titleToUse =
|
|
614
|
-
?
|
|
615
|
-
: config.storyPrefix +
|
|
658
|
+
const titleToUse = uniqueTitle.startsWith(config.storyPrefix)
|
|
659
|
+
? uniqueTitle
|
|
660
|
+
: config.storyPrefix + uniqueTitle;
|
|
616
661
|
return p1 + titleToUse + p3;
|
|
617
662
|
});
|
|
618
663
|
// Fallback: export default { title: "..." } format
|
|
619
664
|
if (!fixedFileContents.includes(config.storyPrefix)) {
|
|
620
665
|
fixedFileContents = fixedFileContents.replace(/(export\s+default\s*\{[\s\S]*?title:\s*["'])([^"']+)(["'])/, (match, p1, oldTitle, p3) => {
|
|
621
666
|
// Check if the title already has the prefix to avoid double prefixing
|
|
622
|
-
const titleToUse =
|
|
623
|
-
?
|
|
624
|
-
: config.storyPrefix +
|
|
667
|
+
const titleToUse = uniqueTitle.startsWith(config.storyPrefix)
|
|
668
|
+
? uniqueTitle
|
|
669
|
+
: config.storyPrefix + uniqueTitle;
|
|
625
670
|
return p1 + titleToUse + p3;
|
|
626
671
|
});
|
|
627
672
|
}
|
|
@@ -657,60 +702,6 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
657
702
|
else {
|
|
658
703
|
logger.log('✅ Final validation passed after post-processing');
|
|
659
704
|
}
|
|
660
|
-
// Check if this is an update to an existing story
|
|
661
|
-
// ONLY consider it an update if we're in the same conversation context
|
|
662
|
-
let existingStory = null;
|
|
663
|
-
if (isActualUpdate && fileName) {
|
|
664
|
-
// When updating within a conversation, look for the story by fileName
|
|
665
|
-
existingStory = storyTracker.findByTitle(aiTitle);
|
|
666
|
-
if (existingStory && existingStory.fileName !== fileName) {
|
|
667
|
-
// If found story has different fileName, it's not the same story
|
|
668
|
-
existingStory = null;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
// Remove the automatic "find by prompt" logic that was preventing duplicates
|
|
672
|
-
// Generate unique ID and filename
|
|
673
|
-
let hash, finalFileName, storyId;
|
|
674
|
-
if (isActualUpdate && (fileName || providedStoryId)) {
|
|
675
|
-
// For updates, preserve the existing fileName and ID
|
|
676
|
-
// Ensure the filename has the proper .stories.tsx extension
|
|
677
|
-
// FIX: Handle case where fileName is undefined but providedStoryId exists
|
|
678
|
-
if (fileName) {
|
|
679
|
-
finalFileName = fileName;
|
|
680
|
-
}
|
|
681
|
-
else if (providedStoryId) {
|
|
682
|
-
// Generate filename from storyId when fileName not provided
|
|
683
|
-
finalFileName = `${providedStoryId}.stories.tsx`;
|
|
684
|
-
logger.log('📝 Generated filename from storyId:', finalFileName);
|
|
685
|
-
}
|
|
686
|
-
if (finalFileName && !finalFileName.endsWith('.stories.tsx')) {
|
|
687
|
-
finalFileName = finalFileName + '.stories.tsx';
|
|
688
|
-
}
|
|
689
|
-
// Use provided storyId or extract from fileName
|
|
690
|
-
if (providedStoryId) {
|
|
691
|
-
storyId = providedStoryId;
|
|
692
|
-
// Extract hash from storyId
|
|
693
|
-
const hashMatch = providedStoryId.match(/^story-([a-f0-9]{8})$/);
|
|
694
|
-
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt).digest('hex').slice(0, 8);
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
// Extract hash from existing fileName if possible
|
|
698
|
-
const hashMatch = fileName.match(/-([a-f0-9]{8})(?:\.stories\.tsx)?$/);
|
|
699
|
-
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt).digest('hex').slice(0, 8);
|
|
700
|
-
storyId = `story-${hash}`;
|
|
701
|
-
}
|
|
702
|
-
logger.log('📌 Preserving story identity for update:', { storyId, fileName: finalFileName });
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
// For new stories, ALWAYS generate new IDs with timestamp to ensure uniqueness
|
|
706
|
-
const timestamp = Date.now();
|
|
707
|
-
hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
708
|
-
// Use the framework adapter's defaultExtension for the correct file extension
|
|
709
|
-
const fileExtension = frameworkAdapter?.defaultExtension || '.stories.tsx';
|
|
710
|
-
finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
|
|
711
|
-
storyId = `story-${hash}`;
|
|
712
|
-
logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName, extension: fileExtension });
|
|
713
|
-
}
|
|
714
705
|
// Write story to file system
|
|
715
706
|
const outPath = generateStory({
|
|
716
707
|
fileContents: fixedFileContents,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateStoryStream.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStoryStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA2c5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"generateStoryStream.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStoryStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA2c5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBA0hB9E"}
|
|
@@ -699,23 +699,7 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
699
699
|
if (!aiTitle || aiTitle.length < 2) {
|
|
700
700
|
aiTitle = cleanPromptForTitle(prompt);
|
|
701
701
|
}
|
|
702
|
-
|
|
703
|
-
// Fix title with storyPrefix
|
|
704
|
-
fixedFileContents = fixedFileContents.replace(/(const\s+meta\s*=\s*\{[\s\S]*?title:\s*["'])([^"']+)(["'])/, (match, p1, oldTitle, p3) => {
|
|
705
|
-
const titleToUse = prettyPrompt.startsWith(config.storyPrefix)
|
|
706
|
-
? prettyPrompt
|
|
707
|
-
: config.storyPrefix + prettyPrompt;
|
|
708
|
-
return p1 + titleToUse + p3;
|
|
709
|
-
});
|
|
710
|
-
if (!fixedFileContents.includes(config.storyPrefix)) {
|
|
711
|
-
fixedFileContents = fixedFileContents.replace(/(export\s+default\s*\{[\s\S]*?title:\s*["'])([^"']+)(["'])/, (match, p1, oldTitle, p3) => {
|
|
712
|
-
const titleToUse = prettyPrompt.startsWith(config.storyPrefix)
|
|
713
|
-
? prettyPrompt
|
|
714
|
-
: config.storyPrefix + prettyPrompt;
|
|
715
|
-
return p1 + titleToUse + p3;
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
// Generate IDs
|
|
702
|
+
// Generate IDs FIRST so we can include hash in title for uniqueness
|
|
719
703
|
let hash;
|
|
720
704
|
let finalFileName;
|
|
721
705
|
let storyId;
|
|
@@ -739,6 +723,27 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
739
723
|
finalFileName = fileName || fileNameFromTitle(aiTitle, hash);
|
|
740
724
|
storyId = `story-${hash}`;
|
|
741
725
|
}
|
|
726
|
+
// Now create title with hash suffix to ensure uniqueness
|
|
727
|
+
const prettyPrompt = escapeTitleForTS(aiTitle);
|
|
728
|
+
// Append hash to title to prevent Storybook duplicate ID errors
|
|
729
|
+
const uniqueTitle = `${prettyPrompt} (${hash})`;
|
|
730
|
+
// Fix title with storyPrefix and hash
|
|
731
|
+
// Note: (?::\s*\w+(?:<[^>]+>)?)? handles TypeScript type annotations including generics
|
|
732
|
+
// e.g., "const meta: Meta = {" or "const meta: Meta<typeof Button> = {"
|
|
733
|
+
fixedFileContents = fixedFileContents.replace(/(const\s+meta\s*(?::\s*\w+(?:<[^>]+>)?)?\s*=\s*\{[\s\S]*?title:\s*["'])([^"']+)(["'])/, (match, p1, oldTitle, p3) => {
|
|
734
|
+
const titleToUse = uniqueTitle.startsWith(config.storyPrefix)
|
|
735
|
+
? uniqueTitle
|
|
736
|
+
: config.storyPrefix + uniqueTitle;
|
|
737
|
+
return p1 + titleToUse + p3;
|
|
738
|
+
});
|
|
739
|
+
if (!fixedFileContents.includes(config.storyPrefix)) {
|
|
740
|
+
fixedFileContents = fixedFileContents.replace(/(export\s+default\s*\{[\s\S]*?title:\s*["'])([^"']+)(["'])/, (match, p1, oldTitle, p3) => {
|
|
741
|
+
const titleToUse = uniqueTitle.startsWith(config.storyPrefix)
|
|
742
|
+
? uniqueTitle
|
|
743
|
+
: config.storyPrefix + uniqueTitle;
|
|
744
|
+
return p1 + titleToUse + p3;
|
|
745
|
+
});
|
|
746
|
+
}
|
|
742
747
|
// Ensure file extension is correct
|
|
743
748
|
if (finalFileName && !finalFileName.endsWith('.stories.tsx')) {
|
|
744
749
|
finalFileName = finalFileName + '.stories.tsx';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoryUIPanel.d.ts","sourceRoot":"","sources":["../../../templates/StoryUI/StoryUIPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"StoryUIPanel.d.ts","sourceRoot":"","sources":["../../../templates/StoryUI/StoryUIPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,oBAAoB,CAAC;AAmuB5B,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED,iBAAS,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,iBAAiB,2CAs5BnD;AAED,eAAe,YAAY,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -123,7 +123,11 @@ function getApiBaseUrl() {
|
|
|
123
123
|
if (window.__STORY_UI_EDGE_URL__) {
|
|
124
124
|
return window.__STORY_UI_EDGE_URL__;
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
// Detect cloud deployments: Railway, custom domains, or any non-localhost
|
|
127
|
+
const hostname = window.location.hostname;
|
|
128
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.');
|
|
129
|
+
if (!isLocalhost) {
|
|
130
|
+
// Cloud deployment - use same origin (works for Railway, custom domains, etc.)
|
|
127
131
|
return window.location.origin;
|
|
128
132
|
}
|
|
129
133
|
}
|
|
@@ -148,15 +152,27 @@ const PROVIDERS_API = `${API_BASE}/mcp/providers`;
|
|
|
148
152
|
const STORIES_API = `${API_BASE}/story-ui/stories`;
|
|
149
153
|
const CONSIDERATIONS_API = `${API_BASE}/mcp/considerations`;
|
|
150
154
|
function isEdgeMode() {
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
if (typeof window !== 'undefined') {
|
|
156
|
+
const hostname = window.location.hostname;
|
|
157
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.');
|
|
158
|
+
return !isLocalhost;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
153
161
|
}
|
|
154
162
|
function getConnectionDisplayText() {
|
|
155
163
|
const baseUrl = getApiBaseUrl();
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
if (typeof window !== 'undefined') {
|
|
165
|
+
const hostname = window.location.hostname;
|
|
166
|
+
if (hostname.includes('railway.app'))
|
|
167
|
+
return 'Railway Cloud';
|
|
168
|
+
if (hostname.includes('workers.dev'))
|
|
169
|
+
return 'Cloudflare Edge';
|
|
170
|
+
if (hostname.includes('southleft.com'))
|
|
171
|
+
return 'Southleft Cloud';
|
|
172
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.');
|
|
173
|
+
if (!isLocalhost)
|
|
174
|
+
return `Cloud (${hostname})`;
|
|
175
|
+
}
|
|
160
176
|
const port = baseUrl.match(/:(\d+)/)?.[1] || '4001';
|
|
161
177
|
return `localhost:${port}`;
|
|
162
178
|
}
|
package/package.json
CHANGED
|
@@ -308,7 +308,11 @@ function getApiBaseUrl(): string {
|
|
|
308
308
|
if ((window as any).__STORY_UI_EDGE_URL__) {
|
|
309
309
|
return (window as any).__STORY_UI_EDGE_URL__;
|
|
310
310
|
}
|
|
311
|
-
|
|
311
|
+
// Detect cloud deployments: Railway, custom domains, or any non-localhost
|
|
312
|
+
const hostname = window.location.hostname;
|
|
313
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.');
|
|
314
|
+
if (!isLocalhost) {
|
|
315
|
+
// Cloud deployment - use same origin (works for Railway, custom domains, etc.)
|
|
312
316
|
return window.location.origin;
|
|
313
317
|
}
|
|
314
318
|
}
|
|
@@ -333,14 +337,24 @@ const STORIES_API = `${API_BASE}/story-ui/stories`;
|
|
|
333
337
|
const CONSIDERATIONS_API = `${API_BASE}/mcp/considerations`;
|
|
334
338
|
|
|
335
339
|
function isEdgeMode(): boolean {
|
|
336
|
-
|
|
337
|
-
|
|
340
|
+
if (typeof window !== 'undefined') {
|
|
341
|
+
const hostname = window.location.hostname;
|
|
342
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.');
|
|
343
|
+
return !isLocalhost;
|
|
344
|
+
}
|
|
345
|
+
return false;
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
function getConnectionDisplayText(): string {
|
|
341
349
|
const baseUrl = getApiBaseUrl();
|
|
342
|
-
if (
|
|
343
|
-
|
|
350
|
+
if (typeof window !== 'undefined') {
|
|
351
|
+
const hostname = window.location.hostname;
|
|
352
|
+
if (hostname.includes('railway.app')) return 'Railway Cloud';
|
|
353
|
+
if (hostname.includes('workers.dev')) return 'Cloudflare Edge';
|
|
354
|
+
if (hostname.includes('southleft.com')) return 'Southleft Cloud';
|
|
355
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.');
|
|
356
|
+
if (!isLocalhost) return `Cloud (${hostname})`;
|
|
357
|
+
}
|
|
344
358
|
const port = baseUrl.match(/:(\d+)/)?.[1] || '4001';
|
|
345
359
|
return `localhost:${port}`;
|
|
346
360
|
}
|