feat(bot): add configurable household assistant behavior

This commit is contained in:
2026-03-12 03:22:43 +04:00
parent 146f5294f4
commit 4e7400e908
22 changed files with 4127 additions and 96 deletions

View File

@@ -376,7 +376,9 @@ function App() {
rentWarningDay: 17,
utilitiesDueDay: 4,
utilitiesReminderDay: 3,
timezone: 'Asia/Tbilisi'
timezone: 'Asia/Tbilisi',
assistantContext: '',
assistantTone: ''
})
const [newCategoryName, setNewCategoryName] = createSignal('')
const [cycleForm, setCycleForm] = createSignal({
@@ -917,7 +919,9 @@ function App() {
rentWarningDay: payload.settings.rentWarningDay,
utilitiesDueDay: payload.settings.utilitiesDueDay,
utilitiesReminderDay: payload.settings.utilitiesReminderDay,
timezone: payload.settings.timezone
timezone: payload.settings.timezone,
assistantContext: payload.assistantConfig.assistantContext ?? '',
assistantTone: payload.assistantConfig.assistantTone ?? ''
})
setPaymentForm((current) => ({
...current,
@@ -1033,7 +1037,9 @@ function App() {
rentWarningDay: demoAdminSettings.settings.rentWarningDay,
utilitiesDueDay: demoAdminSettings.settings.utilitiesDueDay,
utilitiesReminderDay: demoAdminSettings.settings.utilitiesReminderDay,
timezone: demoAdminSettings.settings.timezone
timezone: demoAdminSettings.settings.timezone,
assistantContext: demoAdminSettings.assistantConfig.assistantContext ?? '',
assistantTone: demoAdminSettings.assistantConfig.assistantTone ?? ''
})
setCycleForm((current) => ({
...current,
@@ -1338,12 +1344,16 @@ function App() {
setSavingBillingSettings(true)
try {
const settings = await updateMiniAppBillingSettings(initData, billingForm())
const { settings, assistantConfig } = await updateMiniAppBillingSettings(
initData,
billingForm()
)
setAdminSettings((current) =>
current
? {
...current,
settings
settings,
assistantConfig
}
: current
)
@@ -2230,6 +2240,18 @@ function App() {
timezone: value
}))
}
onBillingAssistantContextChange={(value) =>
setBillingForm((current) => ({
...current,
assistantContext: value
}))
}
onBillingAssistantToneChange={(value) =>
setBillingForm((current) => ({
...current,
assistantTone: value
}))
}
onOpenAddUtilityBill={() => setAddingUtilityBillOpen(true)}
onCloseAddUtilityBill={() => setAddingUtilityBillOpen(false)}
onAddUtilityBill={handleAddUtilityBill}

View File

@@ -203,6 +203,11 @@ export const demoAdminSettings: MiniAppAdminSettingsPayload = {
utilitiesReminderDay: 3,
timezone: 'Asia/Tbilisi'
},
assistantConfig: {
householdId: 'demo-household',
assistantContext: 'The household is a house in Kojori with a backyard and pine forest nearby.',
assistantTone: 'Playful but concise'
},
topics: [
{ role: 'purchase', telegramThreadId: '101', topicName: 'Purchases' },
{ role: 'feedback', telegramThreadId: '102', topicName: 'Anonymous feedback' },

View File

@@ -147,6 +147,16 @@ export const dictionary = {
topicBound: 'Bound',
topicUnbound: 'Unbound',
billingSettingsTitle: 'Billing settings',
assistantSettingsTitle: 'Bot personality',
assistantSettingsBody:
'Give the bot household context and a tone so replies feel grounded without getting intrusive.',
assistantToneLabel: 'Bot mood',
assistantTonePlaceholder: 'Playful, dry, concise, slightly sarcastic',
assistantToneDefault: 'Default',
assistantContextLabel: 'Household context',
assistantContextPlaceholder:
'The household is a house in Kojori with a backyard and pine forest nearby.',
assistantContextEmpty: 'No custom context',
settlementCurrency: 'Settlement currency',
paymentBalanceAdjustmentPolicy: 'Purchase balance adjustment',
paymentBalanceAdjustmentUtilities: 'Adjust through utilities',
@@ -392,6 +402,15 @@ export const dictionary = {
topicBound: 'Привязан',
topicUnbound: 'Не привязан',
billingSettingsTitle: 'Настройки биллинга',
assistantSettingsTitle: 'Характер бота',
assistantSettingsBody:
'Задай бытовой контекст и тон, чтобы ответы бота звучали уместно и не лезли в разговор без повода.',
assistantToneLabel: 'Настроение бота',
assistantTonePlaceholder: 'Игривый, сухой, короткий, слегка саркастичный',
assistantToneDefault: 'По умолчанию',
assistantContextLabel: 'Контекст дома',
assistantContextPlaceholder: 'Это дом в Коджори, рядом двор и сосновый лес.',
assistantContextEmpty: 'Контекст не задан',
settlementCurrency: 'Валюта расчёта',
paymentBalanceAdjustmentPolicy: 'Зачёт баланса по покупкам',
paymentBalanceAdjustmentUtilities: 'Зачитывать через коммуналку',

View File

@@ -70,6 +70,12 @@ export interface MiniAppBillingSettings {
timezone: string
}
export interface MiniAppAssistantConfig {
householdId: string
assistantContext: string | null
assistantTone: string | null
}
export interface MiniAppUtilityCategory {
id: string
householdId: string
@@ -133,6 +139,7 @@ export interface MiniAppDashboard {
export interface MiniAppAdminSettingsPayload {
settings: MiniAppBillingSettings
assistantConfig: MiniAppAssistantConfig
topics: readonly MiniAppTopicBinding[]
categories: readonly MiniAppUtilityCategory[]
members: readonly MiniAppMember[]
@@ -380,6 +387,7 @@ export async function fetchMiniAppAdminSettings(
ok: boolean
authorized?: boolean
settings?: MiniAppBillingSettings
assistantConfig?: MiniAppAssistantConfig
topics?: MiniAppTopicBinding[]
categories?: MiniAppUtilityCategory[]
members?: MiniAppMember[]
@@ -391,6 +399,7 @@ export async function fetchMiniAppAdminSettings(
!response.ok ||
!payload.authorized ||
!payload.settings ||
!payload.assistantConfig ||
!payload.topics ||
!payload.categories ||
!payload.members ||
@@ -401,6 +410,7 @@ export async function fetchMiniAppAdminSettings(
return {
settings: payload.settings,
assistantConfig: payload.assistantConfig,
topics: payload.topics,
categories: payload.categories,
members: payload.members,
@@ -420,8 +430,13 @@ export async function updateMiniAppBillingSettings(
utilitiesDueDay: number
utilitiesReminderDay: number
timezone: string
assistantContext?: string
assistantTone?: string
}
): Promise<MiniAppBillingSettings> {
): Promise<{
settings: MiniAppBillingSettings
assistantConfig: MiniAppAssistantConfig
}> {
const response = await fetch(`${apiBaseUrl()}/api/miniapp/admin/settings/update`, {
method: 'POST',
headers: {
@@ -437,14 +452,18 @@ export async function updateMiniAppBillingSettings(
ok: boolean
authorized?: boolean
settings?: MiniAppBillingSettings
assistantConfig?: MiniAppAssistantConfig
error?: string
}
if (!response.ok || !payload.authorized || !payload.settings) {
if (!response.ok || !payload.authorized || !payload.settings || !payload.assistantConfig) {
throw new Error(payload.error ?? 'Failed to update billing settings')
}
return payload.settings
return {
settings: payload.settings,
assistantConfig: payload.assistantConfig
}
}
export async function upsertMiniAppUtilityCategory(

View File

@@ -37,6 +37,8 @@ type BillingForm = {
utilitiesDueDay: number
utilitiesReminderDay: number
timezone: string
assistantContext: string
assistantTone: string
}
type CycleForm = {
@@ -121,6 +123,8 @@ type Props = {
onBillingUtilitiesDueDayChange: (value: number | null) => void
onBillingUtilitiesReminderDayChange: (value: number | null) => void
onBillingTimezoneChange: (value: string) => void
onBillingAssistantContextChange: (value: string) => void
onBillingAssistantToneChange: (value: string) => void
onOpenAddUtilityBill: () => void
onCloseAddUtilityBill: () => void
onAddUtilityBill: () => Promise<void>
@@ -260,23 +264,22 @@ export function HouseScreen(props: Props) {
<article class="balance-item">
<header>
<strong>{props.copy.billingSettingsTitle ?? ''}</strong>
<span>{props.billingForm.settlementCurrency}</span>
<strong>{props.copy.assistantSettingsTitle ?? ''}</strong>
<span>
{props.billingForm.assistantTone || (props.copy.assistantToneDefault ?? '')}
</span>
</header>
<p>
{props.billingForm.paymentBalanceAdjustmentPolicy === 'utilities'
? props.copy.paymentBalanceAdjustmentUtilities
: props.billingForm.paymentBalanceAdjustmentPolicy === 'rent'
? props.copy.paymentBalanceAdjustmentRent
: props.copy.paymentBalanceAdjustmentSeparate}
</p>
<p>{props.copy.assistantSettingsBody ?? ''}</p>
<div class="ledger-compact-card__meta">
<span class="mini-chip">
{props.copy.rentAmount ?? ''}: {props.billingForm.rentAmountMajor || '—'}{' '}
{props.billingForm.rentCurrency}
{props.copy.assistantToneLabel ?? ''}:{' '}
{props.billingForm.assistantTone || props.copy.assistantToneDefault || '—'}
</span>
<span class="mini-chip mini-chip--muted">
{props.copy.timezone ?? ''}: {props.billingForm.timezone}
{props.copy.assistantContextLabel ?? ''}:{' '}
{props.billingForm.assistantContext.trim().length > 0
? props.billingForm.assistantContext.trim().slice(0, 80)
: (props.copy.assistantContextEmpty ?? '')}
</span>
</div>
<div class="panel-toolbar">
@@ -514,6 +517,27 @@ export function HouseScreen(props: Props) {
onInput={(event) => props.onBillingTimezoneChange(event.currentTarget.value)}
/>
</Field>
<Field label={props.copy.assistantToneLabel ?? ''} wide>
<input
value={props.billingForm.assistantTone}
maxlength="160"
placeholder={props.copy.assistantTonePlaceholder ?? ''}
onInput={(event) =>
props.onBillingAssistantToneChange(event.currentTarget.value)
}
/>
</Field>
<Field label={props.copy.assistantContextLabel ?? ''} wide>
<textarea
rows="6"
maxlength="1200"
placeholder={props.copy.assistantContextPlaceholder ?? ''}
value={props.billingForm.assistantContext}
onInput={(event) =>
props.onBillingAssistantContextChange(event.currentTarget.value)
}
/>
</Field>
</div>
</Modal>
</section>