fix(aiui): fabricated demo content is demo-build-only, fake credential scrubbed (S7)
Per operator decision 2026-08-07 (mock content is isolated to demo.archipelago-foundation.org, never in shipped code): the auto-seeded 'node-demo' conversation (invented balances, file listings, bitcoin.conf) no longer ships on nodes — a VITE_DEMO_CONTENT build flag (or dev) gates it, /seed, and the Guide 'Load Demo' button. The genuine onboarding guide still seeds everywhere. The fixture's bitcoin.conf rpcpassword is now unmistakably example-shaped: fake must never look like a real credential. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -113,7 +113,7 @@ txindex=1
|
|||||||
|
|
||||||
# RPC Settings
|
# RPC Settings
|
||||||
rpcuser=archipelago
|
rpcuser=archipelago
|
||||||
rpcpassword=archipelago123
|
rpcpassword=EXAMPLE-ONLY-not-a-real-password
|
||||||
rpcallowip=127.0.0.1
|
rpcallowip=127.0.0.1
|
||||||
rpcbind=127.0.0.1
|
rpcbind=127.0.0.1
|
||||||
rpcport=8332
|
rpcport=8332
|
||||||
|
|||||||
@@ -148,6 +148,7 @@
|
|||||||
import { computed, ref, watch, nextTick } from 'vue'
|
import { computed, ref, watch, nextTick } from 'vue'
|
||||||
import { useVirtualizer } from '@tanstack/vue-virtual'
|
import { useVirtualizer } from '@tanstack/vue-virtual'
|
||||||
import { useChatStore } from '@/stores/chat'
|
import { useChatStore } from '@/stores/chat'
|
||||||
|
import { DEMO_CONTENT_ENABLED } from '@/utils/demoContent'
|
||||||
import { useAI, streamWithModel } from '@/composables/useAI'
|
import { useAI, streamWithModel } from '@/composables/useAI'
|
||||||
import { useContentPanel } from '@/composables/useContentPanel'
|
import { useContentPanel } from '@/composables/useContentPanel'
|
||||||
import { useComparisonMode } from '@/composables/useComparisonMode'
|
import { useComparisonMode } from '@/composables/useComparisonMode'
|
||||||
@@ -354,6 +355,18 @@ async function handleSend(text: string, images: ImageAttachment[] = []) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (trimmed === '/seed') {
|
if (trimmed === '/seed') {
|
||||||
|
// Demo-site/dev only (S7): the seed showcase is fabricated example
|
||||||
|
// content and must not ship in the node build.
|
||||||
|
if (!DEMO_CONTENT_ENABLED) {
|
||||||
|
const convId = chatStore.activeConversationId
|
||||||
|
if (convId) {
|
||||||
|
chatStore.addMessage(convId, {
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'The `/seed` showcase is only available in demo builds.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
await chatStore.loadSeedChats()
|
await chatStore.loadSeedChats()
|
||||||
// loadSeedChats already switches to the seed conversation
|
// loadSeedChats already switches to the seed conversation
|
||||||
// Collapse chat so user sees PromptIndex for quick picking
|
// Collapse chat so user sees PromptIndex for quick picking
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { usePromptTemplateStore, extractVariables, type PromptTemplate } from '@/stores/promptTemplates'
|
import { usePromptTemplateStore, extractVariables, type PromptTemplate } from '@/stores/promptTemplates'
|
||||||
|
import { DEMO_CONTENT_ENABLED } from '@/utils/demoContent'
|
||||||
|
|
||||||
interface PaletteCommand {
|
interface PaletteCommand {
|
||||||
id: string
|
id: string
|
||||||
@@ -99,7 +100,10 @@ const BUILT_IN_COMMANDS: PaletteCommand[] = [
|
|||||||
{ id: 'cmd-nostr', slash: '/nostr', title: 'Nostr', preview: 'Browse the Nostr network feed' },
|
{ id: 'cmd-nostr', slash: '/nostr', title: 'Nostr', preview: 'Browse the Nostr network feed' },
|
||||||
{ id: 'cmd-design', slash: '/design', title: 'Design System', preview: 'Open the design system viewer' },
|
{ id: 'cmd-design', slash: '/design', title: 'Design System', preview: 'Open the design system viewer' },
|
||||||
{ id: 'cmd-search', slash: '/search ', title: 'Search', preview: 'Search your content library' },
|
{ id: 'cmd-search', slash: '/search ', title: 'Search', preview: 'Search your content library' },
|
||||||
{ id: 'cmd-seed', slash: '/seed', title: 'Seed', preview: 'Load seed conversations for all content types' },
|
// /seed loads fabricated showcase content — demo-site/dev builds only (S7)
|
||||||
|
...(DEMO_CONTENT_ENABLED
|
||||||
|
? [{ id: 'cmd-seed', slash: '/seed', title: 'Seed', preview: 'Load seed conversations for all content types' }]
|
||||||
|
: []),
|
||||||
{ id: 'cmd-freefilms', slash: '/freefilms', title: 'Free Films', preview: 'Browse free documentary films from InDeeHub' },
|
{ id: 'cmd-freefilms', slash: '/freefilms', title: 'Free Films', preview: 'Browse free documentary films from InDeeHub' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -197,8 +197,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Try It -->
|
<!-- Try It — demo builds only (S7): the demo conversation is
|
||||||
<section class="guide-section">
|
fabricated example node state, demo-site/dev only -->
|
||||||
|
<section v-if="DEMO_CONTENT_ENABLED" class="guide-section">
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
<span class="section-icon">🚀</span>
|
<span class="section-icon">🚀</span>
|
||||||
Try It Out
|
Try It Out
|
||||||
@@ -220,6 +221,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useChatStore } from '@/stores/chat'
|
import { useChatStore } from '@/stores/chat'
|
||||||
|
import { DEMO_CONTENT_ENABLED } from '@/utils/demoContent'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import type { Message, Conversation, WebSearchResult } from '@aiui/core/types/message'
|
import type { Message, Conversation, WebSearchResult } from '@aiui/core/types/message'
|
||||||
import { apiFetch } from '@/utils/api-fetch'
|
import { apiFetch } from '@/utils/api-fetch'
|
||||||
|
import { DEMO_CONTENT_ENABLED } from '@/utils/demoContent'
|
||||||
|
|
||||||
function generateId(): string {
|
function generateId(): string {
|
||||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||||
@@ -406,8 +407,9 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
return siblings
|
return siblings
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load all seed prompts as a single conversation */
|
/** Load all seed prompts as a single conversation — demo builds only (S7) */
|
||||||
async function loadSeedChats(): Promise<number> {
|
async function loadSeedChats(): Promise<number> {
|
||||||
|
if (!DEMO_CONTENT_ENABLED) return 0
|
||||||
try {
|
try {
|
||||||
const { seedPromptsToConversation } = await import('@/__tests__/fixtures/seedPrompts')
|
const { seedPromptsToConversation } = await import('@/__tests__/fixtures/seedPrompts')
|
||||||
const conv = seedPromptsToConversation() as Conversation
|
const conv = seedPromptsToConversation() as Conversation
|
||||||
@@ -429,8 +431,10 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load node demo conversation showing local node capabilities */
|
/** Load node demo conversation — demo builds only (S7); the content is
|
||||||
|
* fabricated node state and must not ship on real nodes */
|
||||||
async function loadNodeDemoChat(): Promise<number> {
|
async function loadNodeDemoChat(): Promise<number> {
|
||||||
|
if (!DEMO_CONTENT_ENABLED) return 0
|
||||||
try {
|
try {
|
||||||
const { nodeDemoToConversation } = await import('@/__tests__/fixtures/nodeDemoPrompts')
|
const { nodeDemoToConversation } = await import('@/__tests__/fixtures/nodeDemoPrompts')
|
||||||
const conv = nodeDemoToConversation() as Conversation
|
const conv = nodeDemoToConversation() as Conversation
|
||||||
@@ -472,11 +476,16 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Seed demo conversations (guide + node demo) on first use */
|
/** Seed demo conversations (guide always; node demo only in demo builds) */
|
||||||
async function seedDemoConversations(): Promise<void> {
|
async function seedDemoConversations(): Promise<void> {
|
||||||
|
// The guide is genuine onboarding documentation — always seed it.
|
||||||
|
// `node-demo` is FABRICATED node state (invented balances, file listings,
|
||||||
|
// a fake bitcoin.conf) — demo-site/dev builds only (S7, operator
|
||||||
|
// decision 2026-08-07: mocks belong to the demo site, not shipped code).
|
||||||
|
const wantDemo = DEMO_CONTENT_ENABLED
|
||||||
const hasGuide = conversations.value.has('aiui-guide')
|
const hasGuide = conversations.value.has('aiui-guide')
|
||||||
const hasDemo = conversations.value.has('node-demo')
|
const hasDemo = conversations.value.has('node-demo')
|
||||||
if (hasGuide && hasDemo) {
|
if (hasGuide && (hasDemo || !wantDemo)) {
|
||||||
// Already seeded — still select guide if nothing active
|
// Already seeded — still select guide if nothing active
|
||||||
if (!activeConversationId.value) {
|
if (!activeConversationId.value) {
|
||||||
activeConversationId.value = 'aiui-guide'
|
activeConversationId.value = 'aiui-guide'
|
||||||
@@ -485,15 +494,15 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { guideToConversation } = await import('@/__tests__/fixtures/guideConversation')
|
const { guideToConversation } = await import('@/__tests__/fixtures/guideConversation')
|
||||||
const { nodeDemoToConversation } = await import('@/__tests__/fixtures/nodeDemoPrompts')
|
|
||||||
const guide = guideToConversation() as Conversation
|
const guide = guideToConversation() as Conversation
|
||||||
const demo = nodeDemoToConversation() as Conversation
|
|
||||||
const merged = new Map(conversations.value)
|
const merged = new Map(conversations.value)
|
||||||
if (!merged.has(guide.id)) {
|
if (!merged.has(guide.id)) {
|
||||||
merged.set(guide.id, guide)
|
merged.set(guide.id, guide)
|
||||||
immediateIDBSave(guide)
|
immediateIDBSave(guide)
|
||||||
}
|
}
|
||||||
if (!merged.has(demo.id)) {
|
if (wantDemo && !merged.has('node-demo')) {
|
||||||
|
const { nodeDemoToConversation } = await import('@/__tests__/fixtures/nodeDemoPrompts')
|
||||||
|
const demo = nodeDemoToConversation() as Conversation
|
||||||
merged.set(demo.id, demo)
|
merged.set(demo.id, demo)
|
||||||
immediateIDBSave(demo)
|
immediateIDBSave(demo)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Demo-content gate (operator decision 2026-08-07): seed/mock showcase
|
||||||
|
* conversations exist for the demo.archipelago-foundation.org website ONLY
|
||||||
|
* and must not ship in the node build. Demo builds set
|
||||||
|
* `VITE_DEMO_CONTENT=true`; local dev keeps them for development.
|
||||||
|
*
|
||||||
|
* Anything behind this flag is fabricated example content — never present
|
||||||
|
* it as real node data in a shipped build.
|
||||||
|
*/
|
||||||
|
export const DEMO_CONTENT_ENABLED =
|
||||||
|
import.meta.env.DEV || import.meta.env.VITE_DEMO_CONTENT === 'true'
|
||||||
Reference in New Issue
Block a user