diff --git a/app.go b/app.go index 3a1a1d1..df147ce 100644 --- a/app.go +++ b/app.go @@ -62,14 +62,15 @@ func NewApp() *App { logger: logger, summarizer: services.NewOpenRouterClient(), settings: types.Settings{ - Workspace: storage.GetWorkspace(), - SourceLang: "en", - APIProvider: "openrouter", - APIKey: "", - SummaryLength: "medium", - SummaryLanguage: "en", - Temperature: 0.3, - MaxTokens: 4096, + Workspace: storage.GetWorkspace(), + SourceLang: "en", + APIProvider: "openrouter", + APIKey: "", + SummaryLength: "medium", + SummaryLanguage: "en", + Temperature: 0.3, + MaxTokens: 4096, + ChannelLanguagePrefs: make(map[string]string), }, settingsStore: ss, } @@ -103,6 +104,10 @@ func (a *App) startup(ctx context.Context) { a.logger.Warn("Failed to load settings", "error", err) } else if loaded != nil { a.settings = *loaded + // Initialize ChannelLanguagePrefs if nil (for backward compatibility) + if a.settings.ChannelLanguagePrefs == nil { + a.settings.ChannelLanguagePrefs = make(map[string]string) + } // keep storage workspace in sync if a.settings.Workspace != "" { a.storage.SetWorkspace(a.settings.Workspace) @@ -154,6 +159,44 @@ func (a *App) UpdateSettings(settings types.Settings) types.Settings { return a.settings } +// buildChannelKey creates a unique key for channel language preferences +func buildChannelKey(platform, channelID, channelName string) string { + identifier := channelID + if identifier == "" { + identifier = channelName + } + return fmt.Sprintf("%s:%s", platform, identifier) +} + +// GetChannelLanguagePreference returns the saved language preference for a channel +func (a *App) GetChannelLanguagePreference(platform, channelID, channelName string) string { + if a.settings.ChannelLanguagePrefs == nil { + return a.settings.SourceLang + } + + key := buildChannelKey(platform, channelID, channelName) + if lang, ok := a.settings.ChannelLanguagePrefs[key]; ok { + return lang + } + + return a.settings.SourceLang +} + +// SetChannelLanguagePreference saves the language preference for a channel +func (a *App) SetChannelLanguagePreference(platform, channelID, channelName, lang string) error { + if a.settings.ChannelLanguagePrefs == nil { + a.settings.ChannelLanguagePrefs = make(map[string]string) + } + + key := buildChannelKey(platform, channelID, channelName) + a.settings.ChannelLanguagePrefs[key] = lang + + if a.settingsStore != nil { + return a.settingsStore.Save(a.settings) + } + return nil +} + // DetectPlatform detects which video platform the URL belongs to func (a *App) DetectPlatform(url string) string { return a.downloader.DetectPlatform(url) @@ -228,6 +271,11 @@ func (a *App) StartTranscription(url string, sourceLang string) (*types.Task, er platform := a.downloader.DetectPlatform(url) + // Save channel language preference + if err := a.SetChannelLanguagePreference(platform, info.ChannelID, info.Channel, sourceLang); err != nil { + a.logger.Warn("Failed to save channel language preference", "error", err) + } + task, err := a.taskManager.CreateTask( url, sourceLang, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 685d8ef..c3c5583 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -97,6 +97,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -419,7 +420,6 @@ "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -508,7 +508,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -531,7 +530,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -554,7 +552,6 @@ "os": [ "darwin" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -571,7 +568,6 @@ "os": [ "darwin" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -588,7 +584,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -605,7 +600,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -622,7 +616,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -639,7 +632,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -656,7 +648,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -673,7 +664,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -690,7 +680,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -707,7 +696,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -730,7 +718,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -753,7 +740,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -776,7 +762,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -799,7 +784,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -822,7 +806,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -845,7 +828,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -865,7 +847,6 @@ ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/runtime": "^1.4.4" }, @@ -888,7 +869,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -908,7 +888,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -928,7 +907,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -982,7 +960,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -1010,8 +987,7 @@ "version": "15.5.2", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.2.tgz", "integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { "version": "15.5.2", @@ -1025,7 +1001,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10" } @@ -1042,7 +1017,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10" } @@ -1059,7 +1033,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -1076,7 +1049,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -1093,7 +1065,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -1110,7 +1081,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -1127,7 +1097,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10" } @@ -1144,7 +1113,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10" } @@ -1965,7 +1933,6 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -1983,6 +1950,7 @@ "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1994,6 +1962,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -2187,6 +2156,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001735", "electron-to-chromium": "^1.5.204", @@ -2206,8 +2176,7 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/camelcase-css": { "version": "2.0.1", @@ -2293,8 +2262,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/clsx": { "version": "2.1.1", @@ -2311,7 +2279,6 @@ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -2346,7 +2313,6 @@ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -2437,7 +2403,6 @@ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "license": "Apache-2.0", "optional": true, - "peer": true, "engines": { "node": ">=8" } @@ -3051,8 +3016,7 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -3347,7 +3311,6 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.2.tgz", "integrity": "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "15.5.2", "@swc/helpers": "0.5.15", @@ -3425,7 +3388,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -3589,6 +3551,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3745,6 +3708,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3757,6 +3721,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4009,7 +3974,6 @@ "hasInstallScript": true, "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.4", @@ -4052,7 +4016,6 @@ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "optional": true, - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -4102,7 +4065,6 @@ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "is-arrayish": "^0.3.1" } @@ -4114,7 +4076,6 @@ "dev": true, "license": "BSD-3-Clause", "optional": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4135,7 +4096,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -4258,7 +4218,6 @@ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", - "peer": true, "dependencies": { "client-only": "0.0.1" }, @@ -4329,6 +4288,7 @@ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -4378,7 +4338,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.14.0", @@ -4399,7 +4358,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4413,8 +4371,7 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/thenify": { "version": "3.3.1", @@ -4566,6 +4523,7 @@ "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.15.9", "postcss": "^8.4.18", diff --git a/frontend/src/pages/NewTranscriptionPage.tsx b/frontend/src/pages/NewTranscriptionPage.tsx index a6f81cf..5418a2a 100644 --- a/frontend/src/pages/NewTranscriptionPage.tsx +++ b/frontend/src/pages/NewTranscriptionPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useNavigate } from 'react-router-dom' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' @@ -7,7 +7,7 @@ import { AlertCircle, ChevronLeft, Download } from 'lucide-react' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import VideoPreview, { VideoMetadata } from '@/components/VideoPreview' import TaskProgress, { TaskStage } from '@/components/TaskProgress' -import { CheckDependencies, StartTranscription, GetTask, DetectPlatform } from '../../wailsjs/go/main/App' +import { CheckDependencies, StartTranscription, GetTask, DetectPlatform, GetChannelLanguagePreference } from '../../wailsjs/go/main/App' import { useDebounce } from '@/hooks' export default function NewTranscriptionPage() { @@ -182,6 +182,24 @@ export default function NewTranscriptionPage() { startProcessing() } + const handleMetadataLoaded = useCallback(async (metadata: VideoMetadata) => { + setVideoMetadata(metadata) + if (metadata.isValid && metadata.channel) { + try { + const preferredLang = await GetChannelLanguagePreference( + platform, + metadata.channelId || '', + metadata.channel + ) + if (preferredLang) { + setSourceLang(preferredLang) + } + } catch (err) { + console.error('Failed to get channel language preference:', err) + } + } + }, [platform]) + return (
{/* Header */} @@ -219,9 +237,9 @@ export default function NewTranscriptionPage() { {/* Video Preview */} {url && ( - )} diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 5f576c1..4a8ba29 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -23,7 +23,8 @@ export default function SettingsPage() { summaryLength: 'medium', summaryLanguage: 'en', temperature: 0.3, - maxTokens: 4096 + maxTokens: 4096, + channelLanguagePrefs: {} }) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 04ffef6..4338f31 100755 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -13,6 +13,8 @@ export function DownloadTask(arg1:string):Promise; export function GetAllTasks():Promise>; +export function GetChannelLanguagePreference(arg1:string,arg2:string,arg3:string):Promise; + export function GetDebugInfo():Promise>; export function GetSettings():Promise; @@ -27,6 +29,8 @@ export function ParseVideoUrl(arg1:string):Promise; export function RetryTask(arg1:string):Promise; +export function SetChannelLanguagePreference(arg1:string,arg2:string,arg3:string,arg4:string):Promise; + export function StartTranscription(arg1:string,arg2:string):Promise; export function SummarizeTask(arg1:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 0cef1cb..9f376cb 100755 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -22,6 +22,10 @@ export function GetAllTasks() { return window['go']['main']['App']['GetAllTasks'](); } +export function GetChannelLanguagePreference(arg1, arg2, arg3) { + return window['go']['main']['App']['GetChannelLanguagePreference'](arg1, arg2, arg3); +} + export function GetDebugInfo() { return window['go']['main']['App']['GetDebugInfo'](); } @@ -50,6 +54,10 @@ export function RetryTask(arg1) { return window['go']['main']['App']['RetryTask'](arg1); } +export function SetChannelLanguagePreference(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['SetChannelLanguagePreference'](arg1, arg2, arg3, arg4); +} + export function StartTranscription(arg1, arg2) { return window['go']['main']['App']['StartTranscription'](arg1, arg2); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index e45c874..95b282d 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -52,6 +52,7 @@ export namespace types { summaryLanguage: string; temperature: number; maxTokens: number; + channelLanguagePrefs: Record; static createFrom(source: any = {}) { return new Settings(source); @@ -67,6 +68,7 @@ export namespace types { this.summaryLanguage = source["summaryLanguage"]; this.temperature = source["temperature"]; this.maxTokens = source["maxTokens"]; + this.channelLanguagePrefs = source["channelLanguagePrefs"]; } } export class Task { diff --git a/internal/types/types.go b/internal/types/types.go index f31064b..0520e14 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -91,12 +91,13 @@ type DependencyStatus struct { // Settings represents user configuration type Settings struct { - Workspace string `json:"workspace"` - SourceLang string `json:"sourceLang"` - APIProvider string `json:"apiProvider"` // "gemini", "openai", "openrouter" - APIKey string `json:"apiKey"` - SummaryLength string `json:"summaryLength"` // "short", "medium", "long" - SummaryLanguage string `json:"summaryLanguage"` // Language for summaries (e.g., "en", "zh", "ja") - Temperature float64 `json:"temperature"` - MaxTokens int `json:"maxTokens"` + Workspace string `json:"workspace"` + SourceLang string `json:"sourceLang"` + APIProvider string `json:"apiProvider"` + APIKey string `json:"apiKey"` + SummaryLength string `json:"summaryLength"` + SummaryLanguage string `json:"summaryLanguage"` + Temperature float64 `json:"temperature"` + MaxTokens int `json:"maxTokens"` + ChannelLanguagePrefs map[string]string `json:"channelLanguagePrefs"` }