Add system memory monitoring, Bedrock support, and Discord integrations

- Add CPU/GPU memory monitoring endpoint and frontend widget
- Add Amazon Bedrock model support (API key / inference profiles) plus model selector entries
- Add Discord weather/laundry notification job (JMA forecast + LLM-generated message)
- Add Discord bot that replies to mentions via the configured LLM, with weather-aware context
- Add scheduled "useless tips" job with separate webhook and quiet-hours skip
- Switch FastAPI startup to lifespan handler, log viewer, markdown rendering, and toast UI additions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 00:59:38 +09:00
co-authored by Claude Sonnet 4.6
parent 903d34761c
commit 4a08ba29fa
34 changed files with 5051 additions and 160 deletions
+58 -5
View File
@@ -1,3 +1,14 @@
export interface ApiLog {
id: number
session_id: string | null
model: string
request_json: string
response_text: string | null
error: string | null
latency_ms: number | null
created_at: string
}
export interface SessionInfo {
id: string
title: string
@@ -6,22 +17,64 @@ export interface SessionInfo {
}
export interface SSEChunk {
type: 'session_id' | 'chunk' | 'done'
type: 'session_id' | 'chunk' | 'done' | 'error'
value?: string
}
const BASE = ''
async function parseJson<T>(res: Response, label: string): Promise<T> {
const ct = res.headers.get('content-type') ?? ''
if (!ct.includes('application/json')) {
throw new Error(`${label}: バックエンドに接続できません (${res.status} ${res.statusText})`)
}
if (!res.ok) {
const body = await res.json().catch(() => ({}))
throw new Error(`${label}: ${body.detail ?? res.statusText}`)
}
return res.json()
}
export async function fetchSessions(): Promise<SessionInfo[]> {
const res = await fetch(`${BASE}/chat/sessions`)
if (!res.ok) throw new Error('Failed to fetch sessions')
return res.json()
return parseJson<SessionInfo[]>(res, 'セッション取得')
}
export interface MessageRecord {
id: number
role: 'user' | 'assistant' | 'system'
content: string
created_at: string
}
export async function fetchMessages(sessionId: string): Promise<MessageRecord[]> {
const res = await fetch(`${BASE}/chat/sessions/${sessionId}/messages`)
return parseJson<MessageRecord[]>(res, 'メッセージ取得')
}
export async function deleteSession(sessionId: string): Promise<void> {
await fetch(`${BASE}/chat/sessions/${sessionId}`, { method: 'DELETE' })
}
export async function fetchLogs(params: {
session_id?: string
status?: 'ok' | 'error'
limit?: number
offset?: number
} = {}): Promise<ApiLog[]> {
const q = new URLSearchParams()
if (params.session_id) q.set('session_id', params.session_id)
if (params.status) q.set('status', params.status)
if (params.limit != null) q.set('limit', String(params.limit))
if (params.offset != null) q.set('offset', String(params.offset))
const res = await fetch(`${BASE}/logs?${q}`)
return parseJson<ApiLog[]>(res, 'ログ取得')
}
export async function deleteLog(logId: number): Promise<void> {
await fetch(`${BASE}/logs/${logId}`, { method: 'DELETE' })
}
export function streamChat(
message: string,
sessionId: string | null,
@@ -54,8 +107,8 @@ export function streamChat(
try {
const parsed: SSEChunk = JSON.parse(line.slice(6))
onChunk(parsed)
} catch {
// ignore malformed
} catch (e) {
console.error('[SSE] JSON parse error:', e, '| raw:', line)
}
}
}