2026-08-25 13:57:23 +02:00
import { useEffect , useId , useState } from 'react'
2026-08-21 15:48:43 +02:00
import { useAuth } from '../context/AuthContext.jsx'
import { api } from '../api.js'
2026-08-25 13:57:23 +02:00
const KIND _LABELS = {
journal _entry : 'Finale Fassung (Stimme)' ,
imported _text : 'Import' ,
dialogue _style : 'Dialogstil (schwächer)'
}
const GOVERNANCE _LABELS = {
learning : 'Lernen — geeignete Quellen dürfen das Profil weiterentwickeln' ,
advising : 'Vorschlagen — Änderungen erst nach deiner Bestätigung' ,
frozen : 'Eingefroren — neue Beobachtungen ändern nichts, du kannst weiter manuell editieren'
}
function kindLabel ( kind ) {
return KIND _LABELS [ kind ] || kind
}
function downloadJson ( filename , data ) {
const blob = new Blob ( [ JSON . stringify ( data , null , 2 ) ] , { type : 'application/json' } )
const url = URL . createObjectURL ( blob )
const link = document . createElement ( 'a' )
link . href = url
link . download = filename
document . body . appendChild ( link )
link . click ( )
link . remove ( )
URL . revokeObjectURL ( url )
}
function DocumentTransfer ( { token , exportPath , restorePath , filename , confirmText , onRestored , onError } ) {
const inputId = useId ( )
const exportDoc = async ( ) => {
onError ( '' )
try {
downloadJson ( filename , await api ( exportPath , { token } ) )
} catch ( err ) {
onError ( err . message )
}
}
const restoreFile = async ( event ) => {
const file = event . target . files ? . [ 0 ]
event . target . value = ''
if ( ! file ) return
onError ( '' )
if ( ! window . confirm ( confirmText ) ) return
try {
const document = JSON . parse ( await file . text ( ) )
onRestored ( await api ( restorePath , { token , method : 'POST' , body : document } ) )
} catch ( err ) {
onError ( err . message || 'JSON konnte nicht gelesen werden.' )
}
}
return (
< div className = "row-actions" >
< button type = "button" className = "ghost" onClick = { exportDoc } > JSON exportieren < / button >
< label className = "button-file" htmlFor = { inputId } > JSON wiederherstellen < / label >
< input id = { inputId } type = "file" accept = "application/json,.json" hidden onChange = { restoreFile } / >
< / div >
)
}
const CONTEXT _HINTS = [
{ value : 'vacation_diary' , label : 'Urlaubstagebuch → autobiografisches Journal' } ,
{ value : 'autobiographical_journal' , label : 'Autobiografisches Journal' } ,
{ value : 'reflective' , label : 'Reflektierender Text' }
]
const LIFECYCLE _LABELS = {
uninitialized : 'Noch kein bestätigtes Profil. Quellen sammeln, dann Initial Profile Build.' ,
initial _pending : 'Initiales Profilpaket ist offen. Nach der Prüfung wird das Profil bestätigt.' ,
confirmed : 'Profilhülle bestätigt. Neue Texte gehen in die Review-Queue, nicht in ein Blind-Reinfer.'
}
function FacetRow ( { item , onSave } ) {
const [ value , setValue ] = useState ( item . value || '' )
const [ locked , setLocked ] = useState ( Boolean ( item . locked ) )
useEffect ( ( ) => {
setValue ( item . value || '' )
setLocked ( Boolean ( item . locked ) )
} , [ item . id , item . value , item . locked ] )
const dirty = value !== ( item . value || '' ) || locked !== Boolean ( item . locked )
return (
< li className = "facet-row" >
< div className = "facet-head" >
< strong > { item . label || item . facet _key } < / strong >
< span className = "muted" > { item . layer || 'context' } · { item . origin } { item . locked ? ' · geschützt' : '' } < / span >
< / div >
{ item . evidence && < p className = "muted" > { item . evidence } < / p > }
< textarea rows = { 2 } value = { value } onChange = { ( e ) => setValue ( e . target . value ) } / >
< label className = "inline" >
< input type = "checkbox" checked = { locked } onChange = { ( e ) => setLocked ( e . target . checked ) } / >
Diese Facet vor automatischem Lernen schützen
< / label >
< button type = "button" disabled = { ! dirty } onClick = { ( ) => onSave ( item . facet _key , { value , locked } ) } >
Facet speichern
< / button >
< / li >
)
}
function TraitRow ( { item , onSave } ) {
const [ statement , setStatement ] = useState ( item . statement || '' )
const [ locked , setLocked ] = useState ( Boolean ( item . locked ) )
useEffect ( ( ) => {
setStatement ( item . statement || '' )
setLocked ( Boolean ( item . locked ) )
} , [ item . id , item . statement , item . locked ] )
const dirty = statement !== ( item . statement || '' ) || locked !== Boolean ( item . locked )
const exemplars = item . exemplars || [ ]
return (
< li className = "facet-row" >
< div className = "facet-head" >
< strong > { item . label || item . slug } < / strong >
< span className = "muted" > { item . facet _key } · { item . slug } · { item . origin } { item . locked ? ' · geschützt' : '' } < / span >
< / div >
< textarea rows = { 2 } value = { statement } onChange = { ( e ) => setStatement ( e . target . value ) } / >
{ exemplars . length > 0 && (
< ul className = "muted" >
{ exemplars . map ( ( ref , index ) => (
< li key = { ref . id || index } > Beispiel { ref . occurred _at ? ` · ${ String ( ref . occurred _at ) . slice ( 0 , 10 ) } ` : '' } : { ref . excerpt } < / li >
) ) }
< / ul >
) }
< label className = "inline" >
< input type = "checkbox" checked = { locked } onChange = { ( e ) => setLocked ( e . target . checked ) } / >
Diesen Trait vor automatischem Lernen schützen
< / label >
< button type = "button" disabled = { ! dirty } onClick = { ( ) => onSave ( item . slug , { statement , locked , facet _key : item . facet _key } ) } >
Trait speichern
< / button >
< / li >
)
}
2026-08-21 15:48:43 +02:00
export default function SettingsPage ( ) {
const { session , isAdmin } = useAuth ( )
const [ sub , setSub ] = useState ( null )
2026-08-25 13:57:23 +02:00
const [ profile , setProfile ] = useState ( null )
const [ interaction , setInteraction ] = useState ( null )
const [ importText , setImportText ] = useState ( '' )
const [ importDate , setImportDate ] = useState ( '' )
const [ importHint , setImportHint ] = useState ( 'vacation_diary' )
const [ prefDrafts , setPrefDrafts ] = useState ( { } )
const [ error , setError ] = useState ( '' )
const [ review , setReview ] = useState ( null )
const [ reviewPrompt , setReviewPrompt ] = useState ( '' )
const [ reviewResult , setReviewResult ] = useState ( '' )
const [ reviewBusy , setReviewBusy ] = useState ( false )
2026-08-21 15:48:43 +02:00
2026-08-25 13:57:23 +02:00
const load = ( ) => {
2026-08-21 15:48:43 +02:00
api ( '/api/subscription' , { token : session . token } ) . then ( setSub ) . catch ( ( ) => setSub ( null ) )
2026-08-25 13:57:23 +02:00
api ( '/api/journal/writing-profile' , { token : session . token } ) . then ( setProfile ) . catch ( ( ) => setProfile ( null ) )
api ( '/api/journal/writing-profile/review' , { token : session . token } ) . then ( setReview ) . catch ( ( ) => setReview ( null ) )
api ( '/api/journal/interaction-profile' , { token : session . token } ) . then ( setInteraction ) . catch ( ( ) => setInteraction ( null ) )
}
useEffect ( ( ) => { load ( ) } , [ session . token ] )
const patchWriting = async ( body ) => {
setError ( '' )
try {
setProfile ( await api ( '/api/journal/writing-profile' , { token : session . token , method : 'PATCH' , body } ) )
} catch ( err ) {
setError ( err . message )
}
}
const saveFacet = async ( facetKey , body ) => {
setError ( '' )
try {
setProfile ( await api ( ` /api/journal/writing-profile/facets/ ${ facetKey } ` , {
token : session . token ,
method : 'PATCH' ,
body
} ) )
} catch ( err ) {
setError ( err . message )
}
}
const saveTrait = async ( slug , body ) => {
setError ( '' )
try {
setProfile ( await api ( ` /api/journal/writing-profile/traits/ ${ slug } ` , {
token : session . token ,
method : 'PATCH' ,
body
} ) )
} catch ( err ) {
setError ( err . message )
}
}
const resolveWriting = async ( id , action ) => {
setError ( '' )
try {
setProfile ( await api ( ` /api/journal/writing-profile/suggestions/ ${ id } / ${ action } ` , {
token : session . token ,
method : 'POST'
} ) )
} catch ( err ) {
setError ( err . message )
}
}
const importOwn = async ( event ) => {
event . preventDefault ( )
setError ( '' )
try {
const next = await api ( '/api/journal/writing-profile/import' , {
token : session . token ,
method : 'POST' ,
body : {
body : importText ,
occurred _at : importDate || undefined ,
context _hint : importHint || undefined
}
} )
setProfile ( next )
setImportText ( '' )
setReview ( await api ( '/api/journal/writing-profile/review' , { token : session . token } ) )
} catch ( err ) {
setError ( err . message )
}
}
const copyReviewPrompt = async ( ) => {
setError ( '' )
setReviewBusy ( true )
try {
const path = profile ? . confirmed
? '/api/journal/writing-profile/review/paste'
: '/api/journal/writing-profile/initial-build/paste'
const opened = await api ( path , {
token : session . token ,
method : 'POST'
} )
setReview ( opened )
setReviewPrompt ( opened . prompt || '' )
setProfile ( await api ( '/api/journal/writing-profile' , { token : session . token } ) )
try {
await navigator . clipboard . writeText ( opened . prompt || '' )
} catch {
/* textarea remains for manual copy */
}
} catch ( err ) {
setError ( err . message )
} finally {
setReviewBusy ( false )
}
}
const runApiReview = async ( ) => {
setError ( '' )
setReviewBusy ( true )
try {
const path = profile ? . confirmed
? '/api/journal/writing-profile/review/api'
: '/api/journal/writing-profile/initial-build/api'
const next = await api ( path , {
token : session . token ,
method : 'POST'
} )
if ( next . profile ) setProfile ( next . profile )
setReview ( next )
setReviewPrompt ( '' )
} catch ( err ) {
setError ( err . message )
} finally {
setReviewBusy ( false )
}
}
const importReviewResult = async ( event ) => {
event . preventDefault ( )
setError ( '' )
setReviewBusy ( true )
try {
const next = await api ( '/api/journal/writing-profile/review/import' , {
token : session . token ,
method : 'POST' ,
body : { result : reviewResult , review _id : review ? . open _review ? . id }
} )
if ( next . profile ) setProfile ( next . profile )
setReview ( next )
setReviewResult ( '' )
} catch ( err ) {
setError ( err . message )
} finally {
setReviewBusy ( false )
}
}
const patchInteraction = async ( body ) => {
setError ( '' )
try {
setInteraction ( await api ( '/api/journal/interaction-profile' , {
token : session . token ,
method : 'PATCH' ,
body
} ) )
} catch ( err ) {
setError ( err . message )
}
}
const savePref = async ( item ) => {
setError ( '' )
const value = prefDrafts [ ` ${ item . scope } : ${ item . pref _key } ` ] ? ? item . value
try {
const next = await api ( '/api/journal/interaction-profile/preferences' , {
token : session . token ,
method : 'PATCH' ,
body : { scope : item . scope , pref _key : item . pref _key , value , origin : 'manual' }
} )
setInteraction ( next )
} catch ( err ) {
setError ( err . message )
}
}
const saveDefaultAsPref = async ( item ) => {
setError ( '' )
const value = prefDrafts [ ` default: ${ item . scope } : ${ item . pref _key } ` ] ? ? item . value
try {
const next = await api ( '/api/journal/interaction-profile/preferences' , {
token : session . token ,
method : 'PATCH' ,
body : { scope : item . scope , pref _key : item . pref _key , value , origin : 'manual' }
} )
setInteraction ( next )
} catch ( err ) {
setError ( err . message )
}
}
const resolveInteraction = async ( id , action ) => {
setError ( '' )
try {
setInteraction ( await api ( ` /api/journal/interaction-profile/suggestions/ ${ id } / ${ action } ` , {
token : session . token ,
method : 'POST'
} ) )
} catch ( err ) {
setError ( err . message )
}
}
2026-08-21 15:48:43 +02:00
return (
< section className = "card" >
< h1 > Einstellungen < / h1 >
< dl className = "meta" >
< dt > Name < / dt >
< dd > { session ? . profile ? . name } < / dd >
< dt > E - Mail < / dt >
< dd > { session ? . profile ? . email } < / dd >
< dt > Rolle < / dt >
< dd > { isAdmin ? 'Admin' : 'Nutzer' } < / dd >
< dt > Rahmen - Tier < / dt >
< dd > { sub ? . tier ? . name || '…' } < / dd >
< / dl >
{ sub ? . features && (
< ul className = "muted" >
{ sub . features . map ( ( item ) => (
< li key = { item . feature _id } > { item . feature _id } : { item . allowed ? 'verfügbar' : 'gesperrt' } { item . limit == null ? ' (ohne Limit)' : ` ( ${ item . used } / ${ item . limit } ) ` } < / li >
) ) }
< / ul >
) }
2026-08-25 13:57:23 +02:00
{ error && < p className = "error" > { error } < / p > }
< h2 > Writing Profile < / h2 >
< p className = "muted" >
Stabile Hülle : Core , optionale Facets , darin dynamische semantische Traits , Evidence und Exemplare .
Die Keys chronology , humor , detail , rhythm sind nur eine erste Ordnungshilfe , kein festes Raster .
Semantische Traits entstehen durch Review , nicht durch Wortzählungen . Kontinuierliches Lernen erst nach dem bestätigten Initial Profile Build .
< / p >
< p className = "muted" > { LIFECYCLE _LABELS [ profile ? . lifecycle ] || LIFECYCLE _LABELS . uninitialized } < / p >
{ profile ? . confirmed && (
< >
< label className = "stack" >
Governance
< select
value = { profile ? . governance || 'learning' }
onChange = { ( e ) => patchWriting ( { governance : e . target . value } ) }
>
< option value = "learning" > Lernen < / option >
< option value = "advising" > Vorschlagen < / option >
< option value = "frozen" > Eingefroren < / option >
< / select >
< / label >
< p className = "muted" > { GOVERNANCE _LABELS [ profile ? . governance ] || '' } < / p >
< / >
) }
< DocumentTransfer
token = { session . token }
exportPath = "/api/journal/writing-profile/export"
restorePath = "/api/journal/writing-profile/restore"
filename = "kansho-writing-profile.json"
confirmText = "Writing Profile aus der Datei wiederherstellen? Vorhandene Facets, Traits und Governance werden ersetzt. Journaltexte bleiben."
onRestored = { setProfile }
onError = { setError }
/ >
< form className = "stack" onSubmit = { importOwn } >
< textarea
rows = { 6 }
value = { importText }
onChange = { ( e ) => setImportText ( e . target . value ) }
placeholder = "Eigenen früheren Text einfügen. Für den Initial Build einen größeren Korpus nutzen, nicht nur zwei Einträge."
/ >
< label className = "stack" >
Entstanden
< input type = "date" value = { importDate } onChange = { ( e ) => setImportDate ( e . target . value ) } / >
< / label >
< label className = "stack" >
Quellenart
< select value = { importHint } onChange = { ( e ) => setImportHint ( e . target . value ) } >
{ CONTEXT _HINTS . map ( ( item ) => (
< option key = { item . value } value = { item . value } > { item . label } < / option >
) ) }
< / select >
< / label >
< button type = "submit" > Text in den Korpus legen < / button >
< / form >
< div className = "stack" >
< h3 > { profile ? . confirmed ? 'Profile Review' : 'Initial Profile Build' } < / h3 >
< p className = "muted" >
{ profile ? . confirmed
? 'Neue Texte werden lokal auf Drift geprüft. Eine AI-Review startet nur, wenn du sie auslöst — gebündelt, als API-Call oder als kopierbares Paket. Existing-before-New: bestehenden Trait bestätigen, präzisieren, Scope ändern, zusammenführen oder aufteilen, erst dann neu anlegen.'
: 'Sammle zuerst den historischen Korpus. Der Initial Build liest viele zeitgestempelte Quellen, nicht nur die letzten zwei Einträge. Urlaubstagebücher belegen primär das autobiografische Journal, nicht automatisch den globalen Core.' }
< / p >
< p className = "muted" >
Version { review ? . version ? ? profile ? . version ? ? 0 }
{ review ? . review _ready ? ' · Sammelprüfung bereit' : '' }
{ ( review ? . pending _evidence ? . length || profile ? . pending _evidence ) ? ` · ${ review ? . pending _evidence ? . length ? ? profile ? . pending _evidence } Evidenz(en) ` : '' }
< / p >
{ review ? . pending _evidence ? . length > 0 && profile ? . confirmed && (
< ul className = "muted" >
{ review . pending _evidence . map ( ( item ) => (
< li key = { item . id } > { item . trigger } · { item . source _kind || 'ohne Quelle' } < / li >
) ) }
< / ul >
) }
{ review ? . versions ? . length > 0 && (
< ul className = "muted" >
{ review . versions . slice ( 0 , 5 ) . map ( ( item ) => (
< li key = { item . seq } > v { item . seq } · { item . cause || 'Änderung' } < / li >
) ) }
< / ul >
) }
< div className = "row-actions" >
< button type = "button" className = "ghost" disabled = { reviewBusy } onClick = { copyReviewPrompt } >
{ profile ? . confirmed ? 'Review-Paket kopieren' : 'Initial-Paket kopieren' }
< / button >
< button type = "button" disabled = { reviewBusy } onClick = { runApiReview } >
{ profile ? . confirmed ? 'API-Review' : 'Initial Build (API)' }
< / button >
< / div >
{ reviewPrompt && (
< textarea
className = "writing-review-prompt"
rows = { 8 }
readOnly
value = { reviewPrompt }
/ >
) }
< form className = "stack" onSubmit = { importReviewResult } >
< textarea
rows = { 6 }
value = { reviewResult }
onChange = { ( e ) => setReviewResult ( e . target . value ) }
placeholder = "Review-Ergebnis (JSON: kansho.profile_review_result) einfügen …"
/ >
< button type = "submit" disabled = { reviewBusy || ! reviewResult . trim ( ) } > Ergebnis prüfen < / button >
< / form >
< / div >
{ profile ? . suggestions ? . length > 0 && (
< div className = "stack" >
< h3 > Vorschläge < / h3 >
< ul className = "stack" >
{ profile . suggestions . map ( ( item ) => (
< li key = { item . id } >
< p > { item . trait _slug || item . facet _key } : { item . proposed _value } < / p >
{ item . evidence && < p className = "muted" > { item . evidence } < / p > }
< button type = "button" onClick = { ( ) => resolveWriting ( item . id , 'accept' ) } > Übernehmen < / button >
{ ' ' }
< button type = "button" className = "ghost" onClick = { ( ) => resolveWriting ( item . id , 'reject' ) } > Verwerfen < / button >
< / li >
) ) }
< / ul >
< / div >
) }
{ profile ? . core && (
< div className = "stack" >
< h3 > Core < / h3 >
< ul className = "stack facet-list" >
< FacetRow item = { profile . core } onSave = { saveFacet } / >
< / ul >
< / div >
) }
{ ( profile ? . facets || [ ] ) . filter ( ( item ) => item . facet _key !== 'core' ) . length > 0 && (
< div className = "stack" >
< h3 > Facets < / h3 >
< ul className = "stack facet-list" >
{ ( profile . facets || [ ] ) . filter ( ( item ) => item . facet _key !== 'core' ) . map ( ( item ) => (
< FacetRow key = { item . id || item . facet _key } item = { item } onSave = { saveFacet } / >
) ) }
< / ul >
< / div >
) }
{ ( profile ? . traits || [ ] ) . length > 0 && (
< div className = "stack" >
< h3 > Semantische Traits < / h3 >
< ul className = "stack facet-list" >
{ profile . traits . map ( ( item ) => (
< TraitRow key = { item . id || item . slug } item = { item } onSave = { saveTrait } / >
) ) }
< / ul >
< / div >
) }
{ ! profile ? . compiled _brief && (
< p className = "muted" >
Noch kein Stilbrief . Sobald Einträge oder Importe mit Zeitbezug vorliegen , entstehen Exzerpte . Traits folgen erst aus dem Initial Build .
< / p >
) }
{ profile ? . sources ? . length > 0 && (
< ul className = "muted" >
{ profile . sources . map ( ( item ) => (
< li key = { item . id } >
{ kindLabel ( item . kind ) }
{ item . occurred _at ? ` · ${ String ( item . occurred _at ) . slice ( 0 , 10 ) } ` : '' }
{ item . context _hint ? ` · ${ item . context _hint } ` : '' }
{ item . recency _role ? ` · ${ item . recency _role } ` : '' }
< / li >
) ) }
< / ul >
) }
{ profile ? . compiled _brief && (
< div className = "stack" >
< h2 > Aktueller Brief < / h2 >
< p className = "muted" > Abgeleitet aus bestätigten Traits und zeitgestempelten Quellen , nicht die einzige Wahrheitsquelle . < / p >
< p className = "writing-brief" > { profile . compiled _brief } < / p >
< / div >
) }
< h2 > Interaction Profile < / h2 >
< p className = "muted" >
{ interaction ? . note || 'Stabile Dialogpräferenzen, getrennt vom Writing Profile. Der aktuelle Gesprächszustand bleibt automatisch und ist kein Profil.' }
{ ' ' } Dauerhafte Präferenzen nur aus explizitem Feedback , manuellen Einstellungen oder angenommenen Vorschlägen . Beobachtetes bleibt Candidate .
< / p >
< label className = "stack" >
Governance
< select
value = { interaction ? . governance || 'advising' }
onChange = { ( e ) => patchInteraction ( { governance : e . target . value } ) }
>
< option value = "learning" > Lernen < / option >
< option value = "advising" > Vorschlagen < / option >
< option value = "frozen" > Eingefroren < / option >
< / select >
< / label >
< DocumentTransfer
token = { session . token }
exportPath = "/api/journal/interaction-profile/export"
restorePath = "/api/journal/interaction-profile/restore"
filename = "kansho-interaction-profile.json"
confirmText = "Interaction Profile aus der Datei wiederherstellen? Vorhandene persönliche Präferenzen und Governance werden ersetzt."
onRestored = { setInteraction }
onError = { setError }
/ >
{ interaction ? . defaults ? . length > 0 && (
< div className = "stack" >
< h3 > Produktdefaults < / h3 >
< ul className = "stack" >
{ interaction . defaults . map ( ( item ) => (
< li key = { ` default- ${ item . scope } - ${ item . pref _key } ` } className = "facet-row" >
< div className = "facet-head" >
< strong > { item . title } < / strong >
< span className = "muted" > { item . scope } { item . overridden ? ' · persönlich überschrieben' : '' } < / span >
< / div >
< textarea
key = { ` ${ item . scope } - ${ item . pref _key } - ${ item . value } ` }
rows = { 2 }
defaultValue = { item . value }
onChange = { ( e ) => setPrefDrafts ( ( prev ) => ( {
... prev ,
[ ` default: ${ item . scope } : ${ item . pref _key } ` ] : e . target . value
} ) ) }
/ >
{ ! item . overridden && (
< button type = "button" onClick = { ( ) => saveDefaultAsPref ( item ) } > Als persönliche Präferenz übernehmen < / button >
) }
< / li >
) ) }
< / ul >
< / div >
) }
{ interaction ? . preferences ? . length > 0 && (
< div className = "stack" >
< h3 > Persönliche Präferenzen < / h3 >
< ul className = "stack" >
{ interaction . preferences . map ( ( item ) => (
< li key = { item . id } className = "facet-row" >
< div className = "facet-head" >
< strong > { item . title || item . pref _key } < / strong >
< span className = "muted" > { item . scope } · { item . origin } < / span >
< / div >
< textarea
key = { ` ${ item . id } - ${ item . value } ` }
rows = { 2 }
defaultValue = { item . value }
onChange = { ( e ) => setPrefDrafts ( ( prev ) => ( {
... prev ,
[ ` ${ item . scope } : ${ item . pref _key } ` ] : e . target . value
} ) ) }
/ >
< button type = "button" onClick = { ( ) => savePref ( item ) } > Speichern < / button >
< / li >
) ) }
< / ul >
< / div >
) }
{ interaction ? . suggestions ? . length > 0 && (
< div className = "stack" >
< h3 > Vorschläge < / h3 >
< ul className = "stack" >
{ interaction . suggestions . map ( ( item ) => (
< li key = { item . id } >
< p > { item . scope } / { item . pref _key } : { item . proposed _value } < / p >
{ item . evidence && < p className = "muted" > { item . evidence } < / p > }
< button type = "button" onClick = { ( ) => resolveInteraction ( item . id , 'accept' ) } > Übernehmen < / button >
{ ' ' }
< button type = "button" className = "ghost" onClick = { ( ) => resolveInteraction ( item . id , 'reject' ) } > Verwerfen < / button >
< / li >
) ) }
< / ul >
< / div >
) }
< h2 > Beide Profile < / h2 >
< p className = "muted" >
Ein JSON - Dokument mit Writing Profile und Interaction Profile . Kein Journal - Backup , keine Dialoge .
< / p >
< DocumentTransfer
token = { session . token }
exportPath = "/api/journal/profiles/export"
restorePath = "/api/journal/profiles/restore"
filename = "kansho-profiles.json"
confirmText = "Beide Profile aus der Datei wiederherstellen? Facets, Präferenzen und Governance werden ersetzt."
onRestored = { ( next ) => {
if ( next ? . writing ) setProfile ( next . writing )
if ( next ? . interaction ) setInteraction ( next . interaction )
} }
onError = { setError }
/ >
2026-08-21 15:48:43 +02:00
< / section >
)
}