mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 10:24:02 +00:00
feat(bot): add configurable household assistant behavior
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
HOUSEHOLD_MEMBER_LIFECYCLE_STATUSES,
|
||||
HOUSEHOLD_PAYMENT_BALANCE_ADJUSTMENT_POLICIES,
|
||||
HOUSEHOLD_TOPIC_ROLES,
|
||||
type HouseholdAssistantConfigRecord,
|
||||
type HouseholdMemberAbsencePolicy,
|
||||
type HouseholdMemberAbsencePolicyRecord,
|
||||
type HouseholdBillingSettingsRecord,
|
||||
@@ -245,6 +246,18 @@ function toHouseholdBillingSettingsRecord(row: {
|
||||
}
|
||||
}
|
||||
|
||||
function toHouseholdAssistantConfigRecord(row: {
|
||||
householdId: string
|
||||
assistantContext: string | null
|
||||
assistantTone: string | null
|
||||
}): HouseholdAssistantConfigRecord {
|
||||
return {
|
||||
householdId: row.householdId,
|
||||
assistantContext: row.assistantContext,
|
||||
assistantTone: row.assistantTone
|
||||
}
|
||||
}
|
||||
|
||||
function toHouseholdUtilityCategoryRecord(row: {
|
||||
id: string
|
||||
householdId: string
|
||||
@@ -957,6 +970,25 @@ export function createDbHouseholdConfigurationRepository(databaseUrl: string): {
|
||||
return toHouseholdBillingSettingsRecord(row)
|
||||
},
|
||||
|
||||
async getHouseholdAssistantConfig(householdId) {
|
||||
const rows = await db
|
||||
.select({
|
||||
householdId: schema.households.id,
|
||||
assistantContext: schema.households.assistantContext,
|
||||
assistantTone: schema.households.assistantTone
|
||||
})
|
||||
.from(schema.households)
|
||||
.where(eq(schema.households.id, householdId))
|
||||
.limit(1)
|
||||
|
||||
const row = rows[0]
|
||||
if (!row) {
|
||||
throw new Error('Failed to load household assistant config')
|
||||
}
|
||||
|
||||
return toHouseholdAssistantConfigRecord(row)
|
||||
},
|
||||
|
||||
async updateHouseholdBillingSettings(input) {
|
||||
await ensureBillingSettings(input.householdId)
|
||||
|
||||
@@ -1033,6 +1065,36 @@ export function createDbHouseholdConfigurationRepository(databaseUrl: string): {
|
||||
return toHouseholdBillingSettingsRecord(row)
|
||||
},
|
||||
|
||||
async updateHouseholdAssistantConfig(input) {
|
||||
const rows = await db
|
||||
.update(schema.households)
|
||||
.set({
|
||||
...(input.assistantContext !== undefined
|
||||
? {
|
||||
assistantContext: input.assistantContext
|
||||
}
|
||||
: {}),
|
||||
...(input.assistantTone !== undefined
|
||||
? {
|
||||
assistantTone: input.assistantTone
|
||||
}
|
||||
: {})
|
||||
})
|
||||
.where(eq(schema.households.id, input.householdId))
|
||||
.returning({
|
||||
householdId: schema.households.id,
|
||||
assistantContext: schema.households.assistantContext,
|
||||
assistantTone: schema.households.assistantTone
|
||||
})
|
||||
|
||||
const row = rows[0]
|
||||
if (!row) {
|
||||
throw new Error('Failed to update household assistant config')
|
||||
}
|
||||
|
||||
return toHouseholdAssistantConfigRecord(row)
|
||||
},
|
||||
|
||||
async listHouseholdUtilityCategories(householdId) {
|
||||
await ensureUtilityCategories(householdId)
|
||||
|
||||
|
||||
@@ -172,6 +172,16 @@ function repository(): HouseholdConfigurationRepository {
|
||||
utilitiesReminderDay: input.utilitiesReminderDay ?? 3,
|
||||
timezone: input.timezone ?? 'Asia/Tbilisi'
|
||||
}),
|
||||
getHouseholdAssistantConfig: async (householdId) => ({
|
||||
householdId,
|
||||
assistantContext: 'House in Kojori',
|
||||
assistantTone: 'Playful'
|
||||
}),
|
||||
updateHouseholdAssistantConfig: async (input) => ({
|
||||
householdId: input.householdId,
|
||||
assistantContext: input.assistantContext ?? 'House in Kojori',
|
||||
assistantTone: input.assistantTone ?? 'Playful'
|
||||
}),
|
||||
listHouseholdUtilityCategories: async () => [],
|
||||
upsertHouseholdUtilityCategory: async (input) => ({
|
||||
id: input.slug ?? 'utility-category-1',
|
||||
@@ -269,6 +279,11 @@ describe('createMiniAppAdminService', () => {
|
||||
utilitiesReminderDay: 3,
|
||||
timezone: 'Asia/Tbilisi'
|
||||
},
|
||||
assistantConfig: {
|
||||
householdId: 'household-1',
|
||||
assistantContext: 'House in Kojori',
|
||||
assistantTone: 'Playful'
|
||||
},
|
||||
topics: [
|
||||
{
|
||||
householdId: 'household-1',
|
||||
@@ -322,6 +337,11 @@ describe('createMiniAppAdminService', () => {
|
||||
utilitiesDueDay: 5,
|
||||
utilitiesReminderDay: 4,
|
||||
timezone: 'Asia/Tbilisi'
|
||||
},
|
||||
assistantConfig: {
|
||||
householdId: 'household-1',
|
||||
assistantContext: 'House in Kojori',
|
||||
assistantTone: 'Playful'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
HouseholdAssistantConfigRecord,
|
||||
HouseholdBillingSettingsRecord,
|
||||
HouseholdConfigurationRepository,
|
||||
HouseholdMemberAbsencePolicy,
|
||||
@@ -29,6 +30,7 @@ export interface MiniAppAdminService {
|
||||
| {
|
||||
status: 'ok'
|
||||
settings: HouseholdBillingSettingsRecord
|
||||
assistantConfig: HouseholdAssistantConfigRecord
|
||||
categories: readonly HouseholdUtilityCategoryRecord[]
|
||||
members: readonly HouseholdMemberRecord[]
|
||||
memberAbsencePolicies: readonly HouseholdMemberAbsencePolicyRecord[]
|
||||
@@ -51,10 +53,13 @@ export interface MiniAppAdminService {
|
||||
utilitiesDueDay: number
|
||||
utilitiesReminderDay: number
|
||||
timezone: string
|
||||
assistantContext?: string
|
||||
assistantTone?: string
|
||||
}): Promise<
|
||||
| {
|
||||
status: 'ok'
|
||||
settings: HouseholdBillingSettingsRecord
|
||||
assistantConfig: HouseholdAssistantConfigRecord
|
||||
}
|
||||
| {
|
||||
status: 'rejected'
|
||||
@@ -210,6 +215,34 @@ function normalizeDisplayName(raw: string): string | null {
|
||||
return trimmed.replace(/\s+/g, ' ')
|
||||
}
|
||||
|
||||
function defaultAssistantConfig(householdId: string): HouseholdAssistantConfigRecord {
|
||||
return {
|
||||
householdId,
|
||||
assistantContext: null,
|
||||
assistantTone: null
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAssistantText(
|
||||
raw: string | undefined,
|
||||
maxLength: number
|
||||
): string | null | undefined {
|
||||
if (raw === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const trimmed = raw.trim()
|
||||
if (trimmed.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (trimmed.length > maxLength) {
|
||||
return null
|
||||
}
|
||||
|
||||
return trimmed
|
||||
}
|
||||
|
||||
export function createMiniAppAdminService(
|
||||
repository: HouseholdConfigurationRepository
|
||||
): MiniAppAdminService {
|
||||
@@ -222,17 +255,22 @@ export function createMiniAppAdminService(
|
||||
}
|
||||
}
|
||||
|
||||
const [settings, categories, members, memberAbsencePolicies, topics] = await Promise.all([
|
||||
repository.getHouseholdBillingSettings(input.householdId),
|
||||
repository.listHouseholdUtilityCategories(input.householdId),
|
||||
repository.listHouseholdMembers(input.householdId),
|
||||
repository.listHouseholdMemberAbsencePolicies(input.householdId),
|
||||
repository.listHouseholdTopicBindings(input.householdId)
|
||||
])
|
||||
const [settings, assistantConfig, categories, members, memberAbsencePolicies, topics] =
|
||||
await Promise.all([
|
||||
repository.getHouseholdBillingSettings(input.householdId),
|
||||
repository.getHouseholdAssistantConfig
|
||||
? repository.getHouseholdAssistantConfig(input.householdId)
|
||||
: Promise.resolve(defaultAssistantConfig(input.householdId)),
|
||||
repository.listHouseholdUtilityCategories(input.householdId),
|
||||
repository.listHouseholdMembers(input.householdId),
|
||||
repository.listHouseholdMemberAbsencePolicies(input.householdId),
|
||||
repository.listHouseholdTopicBindings(input.householdId)
|
||||
])
|
||||
|
||||
return {
|
||||
status: 'ok',
|
||||
settings,
|
||||
assistantConfig,
|
||||
categories,
|
||||
members,
|
||||
memberAbsencePolicies,
|
||||
@@ -263,6 +301,23 @@ export function createMiniAppAdminService(
|
||||
}
|
||||
}
|
||||
|
||||
const assistantContext = normalizeAssistantText(input.assistantContext, 1200)
|
||||
const assistantTone = normalizeAssistantText(input.assistantTone, 160)
|
||||
|
||||
if (
|
||||
(input.assistantContext !== undefined &&
|
||||
assistantContext === null &&
|
||||
input.assistantContext.trim().length > 0) ||
|
||||
(input.assistantTone !== undefined &&
|
||||
assistantTone === null &&
|
||||
input.assistantTone.trim().length > 0)
|
||||
) {
|
||||
return {
|
||||
status: 'rejected',
|
||||
reason: 'invalid_settings'
|
||||
}
|
||||
}
|
||||
|
||||
let rentAmountMinor: bigint | null | undefined
|
||||
let rentCurrency: CurrencyCode | undefined
|
||||
const settlementCurrency = input.settlementCurrency
|
||||
@@ -291,38 +346,65 @@ export function createMiniAppAdminService(
|
||||
rentCurrency = parseCurrency(input.rentCurrency ?? 'USD')
|
||||
}
|
||||
|
||||
const settings = await repository.updateHouseholdBillingSettings({
|
||||
householdId: input.householdId,
|
||||
...(settlementCurrency
|
||||
? {
|
||||
settlementCurrency
|
||||
}
|
||||
: {}),
|
||||
...(paymentBalanceAdjustmentPolicy
|
||||
? {
|
||||
paymentBalanceAdjustmentPolicy
|
||||
}
|
||||
: {}),
|
||||
...(rentAmountMinor !== undefined
|
||||
? {
|
||||
rentAmountMinor
|
||||
}
|
||||
: {}),
|
||||
...(rentCurrency
|
||||
? {
|
||||
rentCurrency
|
||||
}
|
||||
: {}),
|
||||
rentDueDay: input.rentDueDay,
|
||||
rentWarningDay: input.rentWarningDay,
|
||||
utilitiesDueDay: input.utilitiesDueDay,
|
||||
utilitiesReminderDay: input.utilitiesReminderDay,
|
||||
timezone: input.timezone.trim()
|
||||
})
|
||||
const shouldUpdateAssistantConfig =
|
||||
assistantContext !== undefined || assistantTone !== undefined
|
||||
|
||||
const [settings, nextAssistantConfig] = await Promise.all([
|
||||
repository.updateHouseholdBillingSettings({
|
||||
householdId: input.householdId,
|
||||
...(settlementCurrency
|
||||
? {
|
||||
settlementCurrency
|
||||
}
|
||||
: {}),
|
||||
...(paymentBalanceAdjustmentPolicy
|
||||
? {
|
||||
paymentBalanceAdjustmentPolicy
|
||||
}
|
||||
: {}),
|
||||
...(rentAmountMinor !== undefined
|
||||
? {
|
||||
rentAmountMinor
|
||||
}
|
||||
: {}),
|
||||
...(rentCurrency
|
||||
? {
|
||||
rentCurrency
|
||||
}
|
||||
: {}),
|
||||
rentDueDay: input.rentDueDay,
|
||||
rentWarningDay: input.rentWarningDay,
|
||||
utilitiesDueDay: input.utilitiesDueDay,
|
||||
utilitiesReminderDay: input.utilitiesReminderDay,
|
||||
timezone: input.timezone.trim()
|
||||
}),
|
||||
repository.updateHouseholdAssistantConfig && shouldUpdateAssistantConfig
|
||||
? repository.updateHouseholdAssistantConfig({
|
||||
householdId: input.householdId,
|
||||
...(assistantContext !== undefined
|
||||
? {
|
||||
assistantContext
|
||||
}
|
||||
: {}),
|
||||
...(assistantTone !== undefined
|
||||
? {
|
||||
assistantTone
|
||||
}
|
||||
: {})
|
||||
})
|
||||
: repository.getHouseholdAssistantConfig
|
||||
? repository.getHouseholdAssistantConfig(input.householdId)
|
||||
: Promise.resolve({
|
||||
householdId: input.householdId,
|
||||
assistantContext: assistantContext ?? null,
|
||||
assistantTone: assistantTone ?? null
|
||||
})
|
||||
])
|
||||
|
||||
return {
|
||||
status: 'ok',
|
||||
settings
|
||||
settings,
|
||||
assistantConfig: nextAssistantConfig
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"0014_empty_risque.sql": "6dd4aba0f84d43bc86afbd04cad3b1055ecac03bd80ad6fd510bef1550d10335",
|
||||
"0015_white_owl.sql": "a9dec4c536c660d7eb0fcea42a3bedb1301408551977d098dff8324d7d5b26bd",
|
||||
"0016_equal_susan_delgado.sql": "1698bf0516d16d2d7929dcb1bd2bb76d5a629eaba3d0bb2533c1ae926408de7a",
|
||||
"0017_gigantic_selene.sql": "232d61b979675ddb97c9d69d14406dc15dd095ee6a332d3fa71d10416204fade"
|
||||
"0017_gigantic_selene.sql": "232d61b979675ddb97c9d69d14406dc15dd095ee6a332d3fa71d10416204fade",
|
||||
"0018_nimble_kojori.sql": "818738e729119c6de8049dcfca562926a5dc6e321ecbbf9cf38e02bc70b5a0dc"
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/db/drizzle/0018_nimble_kojori.sql
Normal file
2
packages/db/drizzle/0018_nimble_kojori.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "households" ADD COLUMN "assistant_context" text;
|
||||
ALTER TABLE "households" ADD COLUMN "assistant_tone" text;
|
||||
3234
packages/db/drizzle/meta/0018_snapshot.json
Normal file
3234
packages/db/drizzle/meta/0018_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -127,6 +127,13 @@
|
||||
"when": 1773226133315,
|
||||
"tag": "0017_gigantic_selene",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "7",
|
||||
"when": 1773252000000,
|
||||
"tag": "0018_nimble_kojori",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ export const households = pgTable('households', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
defaultLocale: text('default_locale').default('ru').notNull(),
|
||||
assistantContext: text('assistant_context'),
|
||||
assistantTone: text('assistant_tone'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
||||
})
|
||||
|
||||
|
||||
@@ -86,6 +86,12 @@ export interface HouseholdBillingSettingsRecord {
|
||||
timezone: string
|
||||
}
|
||||
|
||||
export interface HouseholdAssistantConfigRecord {
|
||||
householdId: string
|
||||
assistantContext: string | null
|
||||
assistantTone: string | null
|
||||
}
|
||||
|
||||
export interface HouseholdUtilityCategoryRecord {
|
||||
id: string
|
||||
householdId: string
|
||||
@@ -166,6 +172,7 @@ export interface HouseholdConfigurationRepository {
|
||||
): Promise<HouseholdMemberRecord | null>
|
||||
listHouseholdMembers(householdId: string): Promise<readonly HouseholdMemberRecord[]>
|
||||
getHouseholdBillingSettings(householdId: string): Promise<HouseholdBillingSettingsRecord>
|
||||
getHouseholdAssistantConfig?(householdId: string): Promise<HouseholdAssistantConfigRecord>
|
||||
updateHouseholdBillingSettings(input: {
|
||||
householdId: string
|
||||
settlementCurrency?: CurrencyCode
|
||||
@@ -178,6 +185,11 @@ export interface HouseholdConfigurationRepository {
|
||||
utilitiesReminderDay?: number
|
||||
timezone?: string
|
||||
}): Promise<HouseholdBillingSettingsRecord>
|
||||
updateHouseholdAssistantConfig?(input: {
|
||||
householdId: string
|
||||
assistantContext?: string | null
|
||||
assistantTone?: string | null
|
||||
}): Promise<HouseholdAssistantConfigRecord>
|
||||
listHouseholdUtilityCategories(
|
||||
householdId: string
|
||||
): Promise<readonly HouseholdUtilityCategoryRecord[]>
|
||||
|
||||
@@ -19,6 +19,7 @@ export {
|
||||
HOUSEHOLD_TOPIC_ROLES,
|
||||
type HouseholdMemberAbsencePolicy,
|
||||
type HouseholdMemberAbsencePolicyRecord,
|
||||
type HouseholdAssistantConfigRecord,
|
||||
type HouseholdPaymentBalanceAdjustmentPolicy,
|
||||
type HouseholdConfigurationRepository,
|
||||
type HouseholdBillingSettingsRecord,
|
||||
|
||||
Reference in New Issue
Block a user