Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
98db02d
Add support for GPT-SoVits TTS provider in ChatInterface.vue and conf…
Rhystic1 Dec 8, 2025
5a44c40
- Created an internal JSON database for character profiles so that th…
Rhystic1 Dec 9, 2025
ee93fc6
- Added a button on the top right corner of the last message to regen…
Rhystic1 Dec 11, 2025
70a8209
- Context Caching for supported models is now always enabled and the …
Rhystic1 Dec 11, 2025
013e790
- Added a secondary pass to clean-up JSON parsing mistakes, improving…
Rhystic1 Dec 12, 2025
4817727
- Added a secondary pass to clean-up JSON parsing mistakes, improving…
Rhystic1 Dec 12, 2025
f4eb129
- Added initial support for Pollinations API.
Rhystic1 Dec 12, 2025
8363366
- Added an "AI Reminders" button to remind the model of the correct b…
Rhystic1 Dec 12, 2025
8d1c51b
- Fixed (hopefully for real...) the tendency of certain models such a…
Rhystic1 Dec 13, 2025
81209f8
- Fixed (hopefully for real...) the tendency of certain models such a…
Rhystic1 Dec 13, 2025
531d0a2
- Fixed issue with the parser that would sometimes create a line with…
Rhystic1 Dec 14, 2025
b9c5a4a
- Fixed issue with the parser that would sometimes create a line with…
Rhystic1 Dec 14, 2025
bc6602c
- Added more AI Reminders.
Rhystic1 Dec 15, 2025
5589538
- Additional work and improvements for the Pollinations API support.
Rhystic1 Dec 16, 2025
79b80c6
- Added experimental support for Chatterbox and Chatterbox Turbo text…
Rhystic1 Dec 17, 2025
a71bd55
Merge branch 'integrated' into pollinations_api
Rhystic1 Dec 17, 2025
78a0c14
Merge pull request #2 from Rhystic1/pollinations_api
Rhystic1 Dec 17, 2025
2ef2173
- Updated the Help page.
Rhystic1 Dec 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,304 changes: 1,313 additions & 991 deletions src/components/views/ChatInterface.vue

Large diffs are not rendered by default.

120 changes: 120 additions & 0 deletions src/utils/aiActionNormalization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
export type AiAction = Record<string, any>

const normalizeKey = (name: string): string => {
return (name || '')
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, ' ')
.replace(/\s+/g, ' ')
}

const resolveExistingProfileKey = (name: string, existingKeys: string[]): string | undefined => {
if (!name) return undefined

// 1) Case-insensitive exact match
const exact = existingKeys.find((k) => k.toLowerCase() === name.toLowerCase())
if (exact) return exact

// 2) Normalized match, but ONLY if unique
const needle = normalizeKey(name)
if (!needle) return undefined

const matches = existingKeys.filter((k) => normalizeKey(k) === needle)
if (matches.length === 1) return matches[0]

return undefined
}

const looksLikeCharacterRecord = (obj: any): obj is Record<string, any> => {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false

// If it's a direct profile object (personality/speech_style/relationships), it's NOT a record.
const directProfileKeys = new Set(['personality', 'speech_style', 'relationships', 'id', 'name'])
const keys = Object.keys(obj)
if (keys.length > 0 && keys.every((k) => directProfileKeys.has(k))) return false

// Heuristic: a record has at least one value that is an object
return keys.some((k) => typeof (obj as any)[k] === 'object' && (obj as any)[k] !== null)
}

const mergeProgressionUpdate = (target: any, incoming: any) => {
if (!incoming || typeof incoming !== 'object') return

if (incoming.personality) {
target.personality = incoming.personality
}

if (incoming.relationships && typeof incoming.relationships === 'object') {
target.relationships = {
...(target.relationships || {}),
...incoming.relationships
}
}
}

/**
* Normalizes an AI action so that:
* - Any legacy/hallucinated `characterProfile` / `characterProfiles` blocks are NOT allowed to overwrite existing profiles.
* - If a legacy/memory block targets an existing character, we extract allowed progression updates
* (personality + relationships) and move them into `characterProgression`.
* - Unsafe fields (e.g. `speech_style`) are silently ignored (they will also be blocked later in executeAction).
*/
export const normalizeAiActionCharacterData = (
action: AiAction,
knownCharacterProfiles: Record<string, any>
): AiAction => {
const out: AiAction = { ...(action || {}) }
const existingKeys = Object.keys(knownCharacterProfiles || {})

// 1) Fold legacy keys into memory (but we'll re-route existing characters into characterProgression)
const legacyBlocks: any[] = []
if (out.characterProfile) legacyBlocks.push(out.characterProfile)
if (out.characterProfiles) legacyBlocks.push(out.characterProfiles)

if (legacyBlocks.length > 0) {
delete out.characterProfile
delete out.characterProfiles

for (const legacy of legacyBlocks) {
if (!looksLikeCharacterRecord(legacy)) continue
out.memory = {
...(out.memory && typeof out.memory === 'object' ? out.memory : {}),
...legacy
}
}
}

// 2) If memory targets an existing character, convert to characterProgression.
if (out.memory && typeof out.memory === 'object' && !Array.isArray(out.memory)) {
const newMemory: Record<string, any> = {}

for (const [rawName, profileLike] of Object.entries(out.memory)) {
const resolvedExistingKey = resolveExistingProfileKey(rawName, existingKeys)

if (resolvedExistingKey && profileLike && typeof profileLike === 'object' && !Array.isArray(profileLike)) {
out.characterProgression = {
...(out.characterProgression && typeof out.characterProgression === 'object' ? out.characterProgression : {})
}

const current =
(out.characterProgression as any)[resolvedExistingKey] &&
typeof (out.characterProgression as any)[resolvedExistingKey] === 'object'
? (out.characterProgression as any)[resolvedExistingKey]
: {}

mergeProgressionUpdate(current, profileLike)
;(out.characterProgression as any)[resolvedExistingKey] = current

// Do NOT keep this entry in memory (prevents profile override/duplication)
continue
}

newMemory[rawName] = profileLike
}

out.memory = newMemory
if (Object.keys(out.memory).length === 0) delete out.memory
}

return out
}
Loading