diff --git a/original_Loader.vue b/original_Loader.vue new file mode 100644 index 0000000..db1ca9e Binary files /dev/null and b/original_Loader.vue differ diff --git a/package-lock.json b/package-lock.json index d2ca1d9..1d12624 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "crypto-js": "^4.2.0", "html2canvas": "^1.4.1", "less": "^4.2.0", + "marked": "^17.0.1", "naive-ui": "^2.34.4", "ndarray-pixels": "^3.1.0", "pinia": "^2.1.6", @@ -4817,6 +4818,17 @@ "semver": "bin/semver" } }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", diff --git a/package.json b/package.json index dd07478..8f24a21 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "crypto-js": "^4.2.0", "html2canvas": "^1.4.1", "less": "^4.2.0", + "marked": "^17.0.1", "naive-ui": "^2.34.4", "ndarray-pixels": "^3.1.0", "pinia": "^2.1.6", diff --git a/src/App.vue b/src/App.vue index 5a65cbf..ae8f41e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -27,7 +27,7 @@ const checkMobile = () => { return market.globalParams.isMobile } const isL2d = () => { - return market.route.name === 'visualiser' + return market.route.name === 'visualiser' || market.route.name === 'story-gen' } const isChibiMobile = () => { return checkMobile() && market.route.name === 'chibi' diff --git a/src/components/common/Credits/Table.vue b/src/components/common/Credits/Table.vue index 0ac160d..57cf0f2 100644 --- a/src/components/common/Credits/Table.vue +++ b/src/components/common/Credits/Table.vue @@ -54,4 +54,9 @@ const props = defineProps({ background: #87ceeb; color: black; } + +.skyblue2 { + background: #52f8f2; + color: black; +} diff --git a/src/components/common/Header/routes2Display.ts b/src/components/common/Header/routes2Display.ts index f1ffac1..a83f143 100644 --- a/src/components/common/Header/routes2Display.ts +++ b/src/components/common/Header/routes2Display.ts @@ -24,6 +24,11 @@ export const ROUTES: route2DisplayInterface[] = [ path: 'gallery', text: 'Gallery', mobile: true + }, + { + path: 'story-gen', + text: 'Story/Roleplaying Generator', + mobile: true } // { // path: 'tierlistmaker', diff --git a/src/components/common/Spine/Loader.vue b/src/components/common/Spine/Loader.vue index 3a83e38..ca02508 100644 --- a/src/components/common/Spine/Loader.vue +++ b/src/components/common/Spine/Loader.vue @@ -2,6 +2,7 @@
@@ -16,9 +17,18 @@ import spine41 from '@/utils/spine/spine-player4.1' import { globalParams, messagesEnum } from '@/utils/enum/globalParams' import type { AttachmentInterface, AttachmentItemColorInterface } from '@/utils/interfaces/live2d' +import { animationMappings } from '@/utils/animationMappings' + +// Helper for debug logging +const logDebug = (...args: any[]) => { + if (import.meta.env.DEV) { + console.log(...args) + } +} let canvas: HTMLCanvasElement | null = null let spineCanvas: any = null +let currentLoadId = 0 // Track active load requests const market = useMarket() // http://esotericsoftware.com/spine-player#Viewports @@ -37,7 +47,123 @@ onMounted(() => { const SPINE_DEFAULT_MIX = 0.25 let spinePlayer: any = null +const resetAttachmentColors = (player: any) => { + if (!player?.animationState?.data?.skeletonData?.defaultSkin?.attachments) return + + player.animationState.data.skeletonData.defaultSkin.attachments.forEach((a: any[]) => { + if (a) { + const keys = Object.keys(a) + if (keys !== null && keys !== undefined && keys.length > 0) { + keys.forEach((k: string) => { + a[k as any].color = { + r: 1, + g: 1, + b: 1, + a: 1 + } + }) + } + } + }) +} + +const resolveAnimation = (requested: string, available: string[]): string | null => { + logDebug(`[Loader] Resolving animation: '${requested}' against available:`, available) + + if (!requested || requested === 'none') return null + if (available.includes(requested)) { + logDebug(`[Loader] Found exact match: ${requested}`) + return requested + } + + const lowerRequested = requested.toLowerCase() + + // Special handling for multi-stage anger (e.g. Chime) + const specialMappings = [ + { + target: 'angry', + condition: (avail: string[]) => avail.filter((a) => a.toLowerCase().includes('angry')).length > 1, + triggers: ['irritated', 'bothered', 'grumpy', 'frustrated', 'annoyed', 'displeased'] + }, + { + target: 'angry_02', + condition: (avail: string[]) => avail.includes('angry_02'), + triggers: ['very angry', 'furious', 'rage', 'shouting', 'yelling', 'livid', 'outraged', 'irate', 'mad'] + }, + { + target: 'angry_03', + condition: (avail: string[]) => avail.includes('angry_03'), + triggers: ['stern', 'frown', 'slightly angry', 'serious', 'disapproving', 'cold', 'glaring'] + } + ] + + for (const { target, condition, triggers } of specialMappings) { + if (condition(available) && triggers.some((t) => lowerRequested.includes(t))) { + logDebug(`[Loader] Mapped '${requested}' to '${target}'`) + return target + } + } + + // Direct fuzzy match + const directMatch = available.find((a) => a.toLowerCase().includes(lowerRequested)) + if (directMatch) { + logDebug(`[Loader] Found direct fuzzy match: ${directMatch}`) + return directMatch + } + + // Semantic mapping + for (const [targetAnim, triggers] of Object.entries(animationMappings)) { + // If requested animation contains the target name OR any of the triggers + if (lowerRequested.includes(targetAnim) || triggers.some((t) => lowerRequested.includes(t))) { + + // Try to find the target animation in available + // exact match of targetAnim (fuzzy)... + let match = available.find((a) => a.toLowerCase().includes(targetAnim)) + if (match) { + logDebug(`[Loader] Found semantic match for ${targetAnim} (base): ${match}`) + return match + } + + // ...or match any of the triggers in available + for (const trigger of triggers) { + match = available.find((a) => a.toLowerCase().includes(trigger)) + if (match) { + logDebug(`[Loader] Found semantic match for ${targetAnim} (trigger: ${trigger}): ${match}`) + return match + } + } + } + } + + console.warn(`[Loader] No match found for animation: ${requested}`) + return null +} + +watch(() => market.live2d.current_animation, (newAnim) => { + if (spinePlayer && newAnim) { + try { + const resolvedAnim = resolveAnimation(newAnim, market.live2d.animations) + + if (resolvedAnim) { + spinePlayer.animationState.setAnimation(0, resolvedAnim, true) + } else { + console.warn(`Animation ${newAnim} not found and no fallback discovered.`) + } + } catch (e) { + console.error('Error setting animation:', e) + } + } +}) + const spineLoader = () => { + if (!market.live2d.current_id) { + logDebug('[Loader] No current_id set, skipping load.') + return + } + + currentLoadId++ + const thisLoadId = currentLoadId + const skelUrl = getPathing('skel') const request = new XMLHttpRequest() @@ -45,6 +171,11 @@ const spineLoader = () => { request.open('GET', skelUrl, true) request.send() request.onloadend = () => { + if (thisLoadId !== currentLoadId) { + logDebug('[Loader] Ignoring stale load request') + return + } + if (request.status !== 200) { console.error('Failed to load skel file:', request.statusText) return @@ -85,6 +216,7 @@ const spineLoader = () => { atlasUrl: getPathing('atlas'), animation: getDefaultAnimation(), skin: market.live2d.getSkin(), + showControls: !market.live2d.hideUI && market.route.name !== 'story-gen', backgroundColor: '#00000000', alpha: true, premultipliedAlpha: true, @@ -95,24 +227,42 @@ const spineLoader = () => { defaultMix: SPINE_DEFAULT_MIX, success: (player: any) => { - spineCanvas.animationState.data.skeletonData.defaultSkin.attachments.forEach((a: any[]) => { - if (a) { - const keys = Object.keys(a) - if (keys !== null && keys !== undefined && keys.length > 0) { - keys.forEach((k: string) => { - a[k as any].color = { - r: 1, - g: 1, - b: 1, - a: 1 - } - }) - } - } - }) - spinePlayer = player + resetAttachmentColors(player) market.live2d.attachments = player.animationState.data.skeletonData.defaultSkin.attachments + market.live2d.animations = player.animationState.data.skeletonData.animations.map((a: any) => a.name) + + const currentAnim = market.live2d.current_animation + let resolvedAnim = resolveAnimation(currentAnim, market.live2d.animations) + + if (!resolvedAnim) { + // Try default animation from config + resolvedAnim = resolveAnimation(player.config.animation, market.live2d.animations) + } + + if (!resolvedAnim && market.live2d.animations.length > 0) { + // Fallback to first available animation + resolvedAnim = market.live2d.animations[0] + console.warn(`No valid animation found. Falling back to first available: ${resolvedAnim}`) + } + + if (resolvedAnim) { + logDebug(`[Loader] Setting initial animation to: ${resolvedAnim} (Requested: ${currentAnim})`) + market.live2d.current_animation = resolvedAnim + + // Force set animation with a slight delay to ensure player is ready + setTimeout(() => { + try { + player.animationState.setAnimation(0, resolvedAnim, true) + player.play() + } catch (e) { + console.error('[Loader] Failed to set animation in timeout', e) + } + }, 100) + } else { + console.error('[Loader] No animations available for this character.') + } + market.live2d.triggerFinishedLoading() successfullyLoaded() }, @@ -156,7 +306,19 @@ const customSpineLoader = () => { defaultMix: SPINE_DEFAULT_MIX, success: (player: any) => { spinePlayer = player + resetAttachmentColors(player) market.live2d.attachments = player.animationState.data.skeletonData.defaultSkin.attachments + market.live2d.animations = player.animationState.data.skeletonData.animations.map((a: any) => a.name) + + const currentAnim = market.live2d.current_animation + const hasAnim = market.live2d.animations.includes(currentAnim) + + if (hasAnim) { + player.animationState.setAnimation(0, currentAnim, true) + } else { + market.live2d.current_animation = player.config.animation + } + market.live2d.triggerFinishedLoading() successfullyLoaded() try { @@ -307,7 +469,14 @@ watch(() => market.live2d.exportAnimationTimestamp, (newVal, oldVal) => { }) watch(() => market.live2d.customLoad, () => { - spineCanvas.dispose() + if (spineCanvas) { + try { + spineCanvas.dispose() + } catch (e) { + console.warn('[Loader] Error disposing spineCanvas for customLoad:', e) + } + spineCanvas = null + } market.load.beginLoad() customSpineLoader() applyDefaultStyle2Canvas() @@ -315,7 +484,8 @@ watch(() => market.live2d.customLoad, () => { watch(() => market.live2d.hideUI, () => { const controls = document.querySelector('.spine-player-controls') as HTMLElement - if (market.live2d.hideUI === false) { + if (!controls) return + if (market.live2d.hideUI === false && market.route.name !== 'story-gen') { controls.style.visibility = 'visible' } else { controls.style.visibility = 'hidden' @@ -442,7 +612,14 @@ async function exportAnimationFrames(timestamp: number) { const loadSpineAfterWatcher = () => { if (market.live2d.canLoadSpine) { - spineCanvas.dispose() + if (spineCanvas) { + try { + spineCanvas.dispose() + } catch (e) { + console.warn('[Loader] Error disposing spineCanvas:', e) + } + spineCanvas = null + } market.load.beginLoad() spineLoader() applyDefaultStyle2Canvas() @@ -531,8 +708,8 @@ document.addEventListener('mousemove', (e) => { const newX = e.clientX const newY = e.clientY - const stylel = parseInt(canvas.style.left.replaceAll('px', '')) - const stylet = parseInt(canvas.style.top.replaceAll('px', '')) + const stylel = parseInt(canvas.style.left.replace(/px/g, '')) + const stylet = parseInt(canvas.style.top.replace(/px/g, '')) if (newX !== oldX) { canvas.style.left = stylel + (newX - oldX) + 'px' @@ -604,20 +781,43 @@ const checkIfAssetCanYap = () => { }) } setYappable(yappable) + + if (yappable && market.live2d.isYapping && market.live2d.yapEnabled) { + try { + spineCanvas.animationState.setAnimation(1, YAP_TRACK, true) + } catch (e) { + console.warn('Could not add yap track on load', e) + } + } } const setYappable = (bool: boolean) => { market.live2d.canYap = bool - market.live2d.isYapping = false + if (!bool) { + market.live2d.isYapping = false + } } watch(() => market.live2d.isYapping, (value) => { + if (!spineCanvas || !spineCanvas.animationState) return + + logDebug(`[Loader] isYapping changed to: ${value}`) - if (value) { - spineCanvas.animationState.addAnimation(1, YAP_TRACK) - spineCanvas.animationState.setAnimation(1, YAP_TRACK, true) + // Only allow yapping if asset supports it AND user enabled it + if (value && market.live2d.canYap && market.live2d.yapEnabled) { + try { + logDebug('[Loader] Setting yap animation') + spineCanvas.animationState.setAnimation(1, YAP_TRACK, true) + } catch (e) { + console.warn('Could not add yap track', e) + } } else { - spineCanvas.animationState.tracks = [spineCanvas.animationState.tracks[0]] + try { + logDebug('[Loader] Clearing yap animation') + spineCanvas.animationState.setEmptyAnimation(1, 0) + } catch (e) { + console.warn('Could not clear yap track', e) + } } }) diff --git a/src/components/views/ChatInterface.vue b/src/components/views/ChatInterface.vue new file mode 100644 index 0000000..ef961d9 --- /dev/null +++ b/src/components/views/ChatInterface.vue @@ -0,0 +1,2930 @@ + + + + + diff --git a/src/components/views/Credits.vue b/src/components/views/Credits.vue index 9ad5077..b00060f 100644 --- a/src/components/views/Credits.vue +++ b/src/components/views/Credits.vue @@ -35,6 +35,11 @@ onMounted(() => { const market = useMarket() const external: help[] = [ + { + name: 'Rhystic1', + contribution: 'Story/Roleplaying Generator', + tier: 'skyblue2' + }, { name: 'Bingle', contribution: 'Fixing favorite assets through Spine Pro.', diff --git a/src/components/views/L2D.vue b/src/components/views/L2D.vue index d354a87..df76949 100644 --- a/src/components/views/L2D.vue +++ b/src/components/views/L2D.vue @@ -13,9 +13,14 @@ import { theme } from '@/utils/enum/globalParams' import { onUnmounted } from 'vue' import WrapperPc from '@/components/common/Spine/WrapperPC.vue' import WrapperMobile from '@/components/common/Spine/WrapperMobile.vue' +import { onMounted } from 'vue' const market = useMarket() +if (!market.live2d.current_id) { + market.live2d.current_id = 'c010' +} + onUnmounted(() => { document.body.style.backgroundColor = theme.BACKGROUND_COLOR }) diff --git a/src/components/views/StoryGenerator.vue b/src/components/views/StoryGenerator.vue new file mode 100644 index 0000000..c963488 --- /dev/null +++ b/src/components/views/StoryGenerator.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index fb9f9da..11a67b4 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -5,6 +5,7 @@ import Credits from '@/components/views/Credits.vue' import Tools from '@/components/views/Tools.vue' import Chibi from '@/components/views/Chibi.vue' import Gallery from '@/components/views/Gallery.vue' +import StoryGenerator from '@/components/views/StoryGenerator.vue' import TierListMaker from '@/components/views/Tierlistmaker.vue' import HighAndLowQualityAssets from '@/components/views/notices/HighAndLowQualityAssets.vue' import AttachmentEditor from '@/components/views/notices/AttachmentEditor.vue' @@ -65,6 +66,11 @@ const router = createRouter({ name: 'gallery', component: Gallery }, + { + path: '/story-gen', + name: 'story-gen', + component: StoryGenerator + }, // { // path: '/tierlistmaker', // name: 'tierlistmaker', diff --git a/src/stores/live2dStore.ts b/src/stores/live2dStore.ts index 808d0da..a736828 100644 --- a/src/stores/live2dStore.ts +++ b/src/stores/live2dStore.ts @@ -17,8 +17,12 @@ export const useLive2dStore = defineStore('live2d', () => { const HQassets = ref(true) const canAssetTalk = ref(false) const canYap = ref(true) + const yapEnabled = ref(true) const isYapping = ref(false) const attachments = ref([]) + const animations = ref([]) + const current_animation = ref('idle') + const isVisible = ref(true) const updateAttachments = ref(0) const applyAttachments = ref(0) const selectionAttachments = ref<'select' | 'unselect'>('select') @@ -313,8 +317,12 @@ export const useLive2dStore = defineStore('live2d', () => { HQassets, canAssetTalk, canYap, + yapEnabled, isYapping, attachments, + animations, + current_animation, + isVisible, updateAttachments, applyAttachments, triggerUpdateAttachments, diff --git a/src/utils/animationMappings.ts b/src/utils/animationMappings.ts new file mode 100644 index 0000000..41b2f25 --- /dev/null +++ b/src/utils/animationMappings.ts @@ -0,0 +1,30 @@ +export const animationMappings: Record = { + 'happy': [ + 'delight', 'joy', 'smile', 'laugh', 'excited', 'glee', 'grin', 'chuckle', 'giggle', 'smirk', + 'beam', 'radiant', 'happy', 'content', 'pleased', 'amused', 'cheerful', 'triumph' + ], + 'cry': [ + 'cry', 'crying', 'weep', 'tears', + ], + 'sad': [ + // High intensity (sad) - actual sadness/very sad/almost crying + 'depressed', 'sorrow', 'tear', 'gloom', 'worry', 'sobbing', 'devastated', 'heartbroken', + 'hurt', 'pain', 'agony', 'grief', 'mourn', 'sad', 'unhappy', 'melancholy', + 'despair', 'hopeless', 'anguish', 'misery', + // Low intensity (sad_02) - about to cry, watery eyes + 'teary', 'upset', 'emotional', 'misty', 'choked up', 'pout', 'whine' + ], + 'angry': [ + // High intensity (angry_02) + 'furious', 'rage', 'shouting', 'yelling', 'livid', 'outraged', 'irate', 'mad', 'fury', + 'anger', 'seethe', 'hiss', 'growl', 'snap', 'bitter', 'resent', 'hate', 'loathe', 'vengeful', + // Low intensity (angry_03) + 'stern', 'frown', 'serious', 'disapproving', 'scowl', 'glare', 'cross', + // Medium intensity (angry) + 'annoyed', 'irritated', 'bothered', 'grumpy', 'frustrated', 'groan', 'disgust' + ], + 'surprise': ['shock', 'surprised', 'startle', 'gasp', 'wide-eyed', 'blink', 'stunned', 'amazed', 'astonished', 'aback', 'confused', 'puzzled'], + 'shy': ['blush', 'embarrassed', 'shy', 'bashful', 'timid', 'stutter', 'stammer', 'fluster', 'nervous', 'anxious'], + 'no': ['frowning', 'disapproval', 'skeptical', 'indifferent', 'unimpressed', 'shake head', 'deny', 'refuse', 'reject', 'doubt', 'sigh'], + 'idle': ['stand', 'wait', 'default', 'calm', 'neutral'] +} diff --git a/src/utils/interfaces/contributor.ts b/src/utils/interfaces/contributor.ts index 8b45ca1..15b51b2 100644 --- a/src/utils/interfaces/contributor.ts +++ b/src/utils/interfaces/contributor.ts @@ -1,5 +1,5 @@ export interface help { name: string contribution: string - tier: 'gold' | 'silver' | 'amethyst' | 'pink' | 'SELEKCJONER' | 'skyblue' | null + tier: string | null } diff --git a/src/utils/json/honorifics.json b/src/utils/json/honorifics.json new file mode 100644 index 0000000..d54c7be --- /dev/null +++ b/src/utils/json/honorifics.json @@ -0,0 +1,127 @@ +{ + "Emma": "Commander", + "Privaty": "Commander", + "Privaty: Unkind Maid": "Master", + "Signal": "Commander", + "Poli": "Commander", + "Miranda": "Commander", + "Brid": "Commander", + "Soline": "Commander", + "Diesel": "Commander", + "Vesti": "Commander", + "Eunhwa": "Weakling", + "Eunhwa: Tactical Upgrade": "Junior Blockhead", + "Guillotine": "Partner", + "Maiden": "Commander", + "D": "Commander", + "D: Killer Wife": "Honey", + "Helm": "Subordinate", + "Mast": "Captain", + "Mast: Romantic Maid": "Master Captain", + "Marciana": "Commander", + "Quiry": "Commander", + "Rapi": "Commander", + "Neon": "Master", + "Delta": "Commander", + "Anchor": "Captain", + "Anchor: Innocent Maid": "Master", + "Maxwell": "Cutie Pie", + "Yuni": "Commander", + "Liter": "Greenhorn", + "Julia": "Conductor", + "Centi": "Boss", + "Drake": "Moron", + "Crow": "Commander", + "Pepper": "Commander", + "Admi": "Commander", + "Jackal": "Commander", + "Laplace": "Birdie", + "Guilty": "Counselor", + "Sin": "Instructor", + "Quency": "Instructor", + "Epinel": "Coach", + "Naga": "Teacher", + "Tia": "Teacher", + "Mihara": "Commander", + "Anne": "Teacher", + "Ether": "No. 7", + "Sugar": "Partner", + "Exia": "Noob", + "Alice": "Rabbity", + "Blanc": "Pit Boss", + "Noir": "Pit Boss", + "Folkwang": "Coach", + "Sakura": "Commander-kun", + "Viper": "Honey", + "Cocoa": "Master", + "Soda": "Master", + "Biscuit": "Trainer", + "Rei": "Teacher", + "Aria": "Maestro", + "Noise": "Producer", + "Rumani": "Gym Rat", + "Papillon": "Honey", + "Scarlet": "My Lord", + "Dorothy": "Your Grace", + "Rapunzel": "Believer", + "Modernia": "Commander", + "Red Hood": "Handsome", + "Snow White": "Commander", + "Ludmilla": "Servant", + "Noah": "Impostor", + "Novel": "Watson", + "Harran": "Follower", + "Isabel": "Darling", + "Nihilister": "Human", + "Rupee": "Sweetie", + "Dolla": "Client", + "Belorta": "Old-Timer", + "Mica": "Old-Timer", + "Nero": "Scoops", + "Milk": "Pal", + "Tove": "Survivalist", + "Neve": "Burly Bear", + "Rouge": "Highroller", + "Frima": "You", + "Crown": "Advisor", + "Flora": "Blossom", + "Bready": "Bonne Bouche", + "Moran": "Cadet", + "Sora": "Captain", + "Cielo": "Captain", + "Sky": "Captain", + "Bay": "Coach", + "Clay": "Coach", + "Only One": "Daddy", + "Cecil": "Darling", + "Mori": "Evaluator", + "Nayuta": "Great Hero", + "Chatterbox": "Human", + "Liberalio": "Human", + "Behemoth": "Human", + "Mecha Shifty": "Human", + "Ade": "Master", + "Rosanna": "Mister", + "Phantom": "Monsieur", + "Crust": "Mr. Gourmand", + "Yulha": "Newbie", + "Jien": "No. 7", + "Cinderella": "Prince", + "Little Mermaid": "Prince", + "Chime": "Rapscallion", + "Yan": "Rookie", + "Trina": "Sun", + "Grave": "Superhuman", + "Einkk": "Sweetie", + "Ein": "Teacher", + "N102": "Teacher", + "Zwei": "Teacher", + "Ziz": "Young Man", + "Bahamut": "Young Man", + "Leona": "Zoo Keeper", + "Asuka: WILLE": "Lilin", + "Volume": "Manager", + "K": "Commander", + "Arcana": "Commander", + "Elegg": "Mr. Commander" +} diff --git a/src/utils/json/updateLog.json b/src/utils/json/updateLog.json index d6fa4d9..96796ac 100644 --- a/src/utils/json/updateLog.json +++ b/src/utils/json/updateLog.json @@ -573,10 +573,14 @@ }, { "date": "November 22nd 2025", - "update": "Galley: Updated chapter and albums. added chapter 41 & 42, " + "update": "Galley: Updated chapter and albums. added chapter 41 & 42, goddess fall and blank ticket galleries" }, { "date": "December 6th 2025", "update": "Live2d: Added christmas diesel, blanc, brid and bready" + }, + { + "date": "December 6th 2025", + "update": "Added the Story/Roleplaying Generator feature. Developped entirely by Rhystic1 on GitHub." } ] \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 2fb21e9..1867a81 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,5 +11,14 @@ export default defineConfig({ alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } + }, + server: { + proxy: { + '/alltalk': { + target: 'http://127.0.0.1:7851', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/alltalk/, '') + } + } } })