Compare commits

...

5 Commits

Author SHA1 Message Date
dayuan.jiang
32f7383002 fix: OpenAI reasoning/thinking blocks not showing
- Use Responses API instead of Chat Completions API for OpenAI
  (.chat() -> default call) to support reasoning events
- Add o4 to reasoning model detection
- Change default reasoningSummary from 'detailed' to 'auto'
  (not all models support 'detailed')
- Update types to match AI SDK: 'auto' | 'detailed'
2025-12-23 13:10:15 +09:00
Dayuan Jiang
8cb7494d16 feat(i18n): add translations for model configuration UI (#368)
- Add ~40 new translation keys for model-config-dialog and model-selector
- Support English, Chinese, and Japanese translations
- Replace all hardcoded strings with dictionary lookups
2025-12-23 11:42:27 +09:00
Dayuan Jiang
98625dd72a docs: update about page model info to Haiku 4.5 (#367) 2025-12-23 10:22:31 +09:00
Dayuan Jiang
b5734aa5e1 chore: hide notice icon from header (#366) 2025-12-23 10:08:14 +09:00
Dayuan Jiang
87cdc53665 fix: improve Langfuse span filter to exclude all Next.js infrastructure traces (#365)
* debug: add log to verify instrumentation initialization

* fix: improve Langfuse span filter to exclude all Next.js infrastructure traces
2025-12-23 09:47:23 +09:00
11 changed files with 325 additions and 104 deletions

View File

@@ -117,9 +117,9 @@ export default function AboutCN() {
(TPS/TPM) (TPS/TPM)
</p> </p>
<p> <p>
使 Claude {" "} 使 Opus 4.5 {" "}
<span className="font-semibold text-amber-700"> <span className="font-semibold text-amber-700">
minimax-m2 Haiku 4.5
</span> </span>
</p> </p>

View File

@@ -126,9 +126,9 @@ export default function AboutJA() {
</p> </p>
<p> <p>
Claude {" "} Opus 4.5 {" "}
<span className="font-semibold text-amber-700"> <span className="font-semibold text-amber-700">
minimax-m2 Haiku 4.5
</span>{" "} </span>{" "}
</p> </p>

View File

@@ -129,9 +129,9 @@ export default function About() {
</p> </p>
<p> <p>
Due to the high usage, I have changed the Due to the high usage, I have changed the
model from Claude to{" "} model from Opus 4.5 to{" "}
<span className="font-semibold text-amber-700"> <span className="font-semibold text-amber-700">
minimax-m2 Haiku 4.5
</span> </span>
, which is more cost-effective. , which is more cost-effective.
</p> </p>

View File

@@ -1281,24 +1281,6 @@ Continue from EXACTLY where you stopped.`,
About About
</Link> </Link>
)} )}
{!isMobile &&
process.env.NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE ===
"true" && (
<Link
href="/about"
target="_blank"
rel="noopener noreferrer"
>
<ButtonWithTooltip
tooltipContent="Due to high usage, I have changed the model to minimax-m2 and added some usage limits. See About page for details."
variant="ghost"
size="icon"
className="h-6 w-6 text-amber-500 hover:text-amber-600"
>
<AlertTriangle className="h-4 w-4" />
</ButtonWithTooltip>
</Link>
)}
</div> </div>
<div className="flex items-center gap-1 justify-end overflow-visible"> <div className="flex items-center gap-1 justify-end overflow-visible">
<ButtonWithTooltip <ButtonWithTooltip

View File

@@ -52,6 +52,7 @@ import {
} from "@/components/ui/select" } from "@/components/ui/select"
import { useDictionary } from "@/hooks/use-dictionary" import { useDictionary } from "@/hooks/use-dictionary"
import type { UseModelConfigReturn } from "@/hooks/use-model-config" import type { UseModelConfigReturn } from "@/hooks/use-model-config"
import { formatMessage } from "@/lib/i18n/utils"
import type { ProviderConfig, ProviderName } from "@/lib/types/model-config" import type { ProviderConfig, ProviderName } from "@/lib/types/model-config"
import { PROVIDER_INFO, SUGGESTED_MODELS } from "@/lib/types/model-config" import { PROVIDER_INFO, SUGGESTED_MODELS } from "@/lib/types/model-config"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@@ -107,10 +108,12 @@ function ValidationButton({
status, status,
onClick, onClick,
disabled, disabled,
dict,
}: { }: {
status: ValidationStatus status: ValidationStatus
onClick: () => void onClick: () => void
disabled: boolean disabled: boolean
dict: ReturnType<typeof useDictionary>
}) { }) {
return ( return (
<Button <Button
@@ -129,10 +132,10 @@ function ValidationButton({
) : status === "success" ? ( ) : status === "success" ? (
<> <>
<Check className="h-4 w-4 mr-1.5" /> <Check className="h-4 w-4 mr-1.5" />
Verified {dict.modelConfig.verified}
</> </>
) : ( ) : (
"Test" dict.modelConfig.test
)} )}
</Button> </Button>
) )
@@ -406,7 +409,7 @@ export function ModelConfigDialog({
<div className="w-56 flex-shrink-0 flex flex-col border-r bg-muted/20"> <div className="w-56 flex-shrink-0 flex flex-col border-r bg-muted/20">
<div className="px-4 py-3 border-b"> <div className="px-4 py-3 border-b">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider"> <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
Providers {dict.modelConfig.providers}
</span> </span>
</div> </div>
@@ -418,7 +421,7 @@ export function ModelConfigDialog({
<Plus className="h-5 w-5 text-muted-foreground" /> <Plus className="h-5 w-5 text-muted-foreground" />
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Add a provider to get started {dict.modelConfig.addProviderHint}
</p> </p>
</div> </div>
) : ( ) : (
@@ -484,7 +487,11 @@ export function ModelConfigDialog({
> >
<SelectTrigger className="h-9 bg-background hover:bg-accent"> <SelectTrigger className="h-9 bg-background hover:bg-accent">
<Plus className="h-4 w-4 mr-2 text-muted-foreground" /> <Plus className="h-4 w-4 mr-2 text-muted-foreground" />
<SelectValue placeholder="Add Provider" /> <SelectValue
placeholder={
dict.modelConfig.addProvider
}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{availableProviders.map((p) => ( {availableProviders.map((p) => (
@@ -552,15 +559,27 @@ export function ModelConfigDialog({
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
{selectedProvider.models {selectedProvider.models
.length === 0 .length === 0
? "No models configured" ? dict.modelConfig
: `${selectedProvider.models.length} model${selectedProvider.models.length > 1 ? "s" : ""} configured`} .noModelsConfigured
: formatMessage(
dict.modelConfig
.modelsConfiguredCount,
{
count: selectedProvider
.models
.length,
},
)}
</p> </p>
</div> </div>
{selectedProvider.validated && ( {selectedProvider.validated && (
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"> <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<Check className="h-3.5 w-3.5" /> <Check className="h-3.5 w-3.5" />
<span className="text-xs font-medium"> <span className="text-xs font-medium">
Verified {
dict.modelConfig
.verified
}
</span> </span>
</div> </div>
)} )}
@@ -570,7 +589,12 @@ export function ModelConfigDialog({
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground"> <div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
<Settings2 className="h-4 w-4" /> <Settings2 className="h-4 w-4" />
<span>Configuration</span> <span>
{
dict.modelConfig
.configuration
}
</span>
</div> </div>
<div className="rounded-xl border bg-card p-4 space-y-4"> <div className="rounded-xl border bg-card p-4 space-y-4">
@@ -581,7 +605,10 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5" className="text-xs font-medium flex items-center gap-1.5"
> >
<Tag className="h-3.5 w-3.5 text-muted-foreground" /> <Tag className="h-3.5 w-3.5 text-muted-foreground" />
Display Name {
dict.modelConfig
.displayName
}
</Label> </Label>
<Input <Input
id="provider-name" id="provider-name"
@@ -616,8 +643,11 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5" className="text-xs font-medium flex items-center gap-1.5"
> >
<Key className="h-3.5 w-3.5 text-muted-foreground" /> <Key className="h-3.5 w-3.5 text-muted-foreground" />
AWS Access Key {
ID dict
.modelConfig
.awsAccessKeyId
}
</Label> </Label>
<Input <Input
id="aws-access-key-id" id="aws-access-key-id"
@@ -649,8 +679,11 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5" className="text-xs font-medium flex items-center gap-1.5"
> >
<Key className="h-3.5 w-3.5 text-muted-foreground" /> <Key className="h-3.5 w-3.5 text-muted-foreground" />
AWS Secret {
Access Key dict
.modelConfig
.awsSecretAccessKey
}
</Label> </Label>
<div className="relative"> <div className="relative">
<Input <Input
@@ -674,7 +707,11 @@ export function ModelConfigDialog({
.value, .value,
) )
} }
placeholder="Enter your secret access key" placeholder={
dict
.modelConfig
.enterSecretKey
}
className="h-9 pr-10 font-mono text-xs" className="h-9 pr-10 font-mono text-xs"
/> />
<button <button
@@ -707,7 +744,11 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5" className="text-xs font-medium flex items-center gap-1.5"
> >
<Link2 className="h-3.5 w-3.5 text-muted-foreground" /> <Link2 className="h-3.5 w-3.5 text-muted-foreground" />
AWS Region {
dict
.modelConfig
.awsRegion
}
</Label> </Label>
<Select <Select
value={ value={
@@ -724,7 +765,13 @@ export function ModelConfigDialog({
} }
> >
<SelectTrigger className="h-9 font-mono text-xs hover:bg-accent"> <SelectTrigger className="h-9 font-mono text-xs hover:bg-accent">
<SelectValue placeholder="Select region" /> <SelectValue
placeholder={
dict
.modelConfig
.selectRegion
}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-64"> <SelectContent className="max-h-64">
<SelectItem value="us-east-1"> <SelectItem value="us-east-1">
@@ -819,10 +866,16 @@ export function ModelConfigDialog({
"success" ? ( "success" ? (
<> <>
<Check className="h-4 w-4 mr-1.5" /> <Check className="h-4 w-4 mr-1.5" />
Verified {
dict
.modelConfig
.verified
}
</> </>
) : ( ) : (
"Test" dict
.modelConfig
.test
)} )}
</Button> </Button>
{validationStatus === {validationStatus ===
@@ -846,7 +899,11 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5" className="text-xs font-medium flex items-center gap-1.5"
> >
<Key className="h-3.5 w-3.5 text-muted-foreground" /> <Key className="h-3.5 w-3.5 text-muted-foreground" />
API Key {
dict
.modelConfig
.apiKey
}
</Label> </Label>
<div className="flex gap-2"> <div className="flex gap-2">
<div className="relative flex-1"> <div className="relative flex-1">
@@ -870,7 +927,11 @@ export function ModelConfigDialog({
.value, .value,
) )
} }
placeholder="Enter your API key" placeholder={
dict
.modelConfig
.enterApiKey
}
className="h-9 pr-10 font-mono text-xs" className="h-9 pr-10 font-mono text-xs"
/> />
<button <button
@@ -924,10 +985,16 @@ export function ModelConfigDialog({
"success" ? ( "success" ? (
<> <>
<Check className="h-4 w-4 mr-1.5" /> <Check className="h-4 w-4 mr-1.5" />
Verified {
dict
.modelConfig
.verified
}
</> </>
) : ( ) : (
"Test" dict
.modelConfig
.test
)} )}
</Button> </Button>
</div> </div>
@@ -950,9 +1017,17 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5" className="text-xs font-medium flex items-center gap-1.5"
> >
<Link2 className="h-3.5 w-3.5 text-muted-foreground" /> <Link2 className="h-3.5 w-3.5 text-muted-foreground" />
Base URL {
dict
.modelConfig
.baseUrl
}
<span className="text-muted-foreground font-normal"> <span className="text-muted-foreground font-normal">
(optional) {
dict
.modelConfig
.optional
}
</span> </span>
</Label> </Label>
<Input <Input
@@ -974,7 +1049,9 @@ export function ModelConfigDialog({
.provider .provider
] ]
.defaultBaseUrl || .defaultBaseUrl ||
"Custom endpoint URL" dict
.modelConfig
.customEndpoint
} }
className="h-9 font-mono text-xs" className="h-9 font-mono text-xs"
/> />
@@ -989,12 +1066,20 @@ export function ModelConfigDialog({
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground"> <div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
<Sparkles className="h-4 w-4" /> <Sparkles className="h-4 w-4" />
<span>Models</span> <span>
{
dict.modelConfig
.models
}
</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="relative"> <div className="relative">
<Input <Input
placeholder="Custom model ID..." placeholder={
dict.modelConfig
.customModelId
}
value={ value={
customModelInput customModelInput
} }
@@ -1088,8 +1173,12 @@ export function ModelConfigDialog({
<span className="text-xs"> <span className="text-xs">
{availableSuggestions.length === {availableSuggestions.length ===
0 0
? "All added" ? dict
: "Suggested"} .modelConfig
.allAdded
: dict
.modelConfig
.suggested}
</span> </span>
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-72"> <SelectContent className="max-h-72">
@@ -1124,7 +1213,10 @@ export function ModelConfigDialog({
<Sparkles className="h-5 w-5 text-muted-foreground" /> <Sparkles className="h-5 w-5 text-muted-foreground" />
</div> </div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
No models configured {
dict.modelConfig
.noModelsConfigured
}
</p> </p>
</div> </div>
) : ( ) : (
@@ -1291,7 +1383,9 @@ export function ModelConfigDialog({
!newModelId !newModelId
) { ) {
showError( showError(
"Model ID cannot be empty", dict
.modelConfig
.modelIdEmpty,
) )
return return
} }
@@ -1319,7 +1413,9 @@ export function ModelConfigDialog({
) )
) { ) {
showError( showError(
"This model ID already exists", dict
.modelConfig
.modelIdExists,
) )
return return
} }
@@ -1383,7 +1479,10 @@ export function ModelConfigDialog({
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10" className="text-muted-foreground hover:text-destructive hover:bg-destructive/10"
> >
<Trash2 className="h-4 w-4 mr-2" /> <Trash2 className="h-4 w-4 mr-2" />
Delete Provider {
dict.modelConfig
.deleteProvider
}
</Button> </Button>
</div> </div>
</div> </div>
@@ -1395,11 +1494,10 @@ export function ModelConfigDialog({
<Server className="h-8 w-8 text-primary/60" /> <Server className="h-8 w-8 text-primary/60" />
</div> </div>
<h3 className="font-semibold mb-1"> <h3 className="font-semibold mb-1">
Configure AI Providers {dict.modelConfig.configureProviders}
</h3> </h3>
<p className="text-sm text-muted-foreground max-w-xs"> <p className="text-sm text-muted-foreground max-w-xs">
Select a provider from the list or add a new {dict.modelConfig.selectProviderHint}
one to configure API keys and models
</p> </p>
</div> </div>
)} )}
@@ -1410,7 +1508,7 @@ export function ModelConfigDialog({
<div className="px-6 py-3 border-t bg-muted/20"> <div className="px-6 py-3 border-t bg-muted/20">
<p className="text-xs text-muted-foreground text-center flex items-center justify-center gap-1.5"> <p className="text-xs text-muted-foreground text-center flex items-center justify-center gap-1.5">
<Key className="h-3 w-3" /> <Key className="h-3 w-3" />
API keys are stored locally in your browser {dict.modelConfig.apiKeyStored}
</p> </p>
</div> </div>
</DialogContent> </DialogContent>
@@ -1429,19 +1527,16 @@ export function ModelConfigDialog({
<AlertCircle className="h-6 w-6 text-destructive" /> <AlertCircle className="h-6 w-6 text-destructive" />
</div> </div>
<AlertDialogTitle className="text-center"> <AlertDialogTitle className="text-center">
Delete Provider {dict.modelConfig.deleteProvider}
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription className="text-center"> <AlertDialogDescription className="text-center">
Are you sure you want to delete{" "} {formatMessage(dict.modelConfig.deleteConfirmDesc, {
<span className="font-medium text-foreground"> name: selectedProvider
{selectedProvider
? selectedProvider.name || ? selectedProvider.name ||
PROVIDER_INFO[selectedProvider.provider] PROVIDER_INFO[selectedProvider.provider]
.label .label
: "this provider"} : "this provider",
</span> })}
? This will remove all configured models and cannot
be undone.
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
{selectedProvider && {selectedProvider &&
@@ -1451,11 +1546,16 @@ export function ModelConfigDialog({
htmlFor="delete-confirm" htmlFor="delete-confirm"
className="text-sm text-muted-foreground" className="text-sm text-muted-foreground"
> >
Type &quot; {formatMessage(
{selectedProvider.name || dict.modelConfig.typeToConfirm,
PROVIDER_INFO[selectedProvider.provider] {
.label} name:
&quot; to confirm selectedProvider.name ||
PROVIDER_INFO[
selectedProvider.provider
].label,
},
)}
</Label> </Label>
<Input <Input
id="delete-confirm" id="delete-confirm"
@@ -1463,13 +1563,17 @@ export function ModelConfigDialog({
onChange={(e) => onChange={(e) =>
setDeleteConfirmText(e.target.value) setDeleteConfirmText(e.target.value)
} }
placeholder="Type provider name..." placeholder={
dict.modelConfig.typeProviderName
}
className="h-9" className="h-9"
/> />
</div> </div>
)} )}
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>
{dict.modelConfig.cancel}
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={handleDeleteProvider} onClick={handleDeleteProvider}
disabled={ disabled={
@@ -1482,7 +1586,7 @@ export function ModelConfigDialog({
} }
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50" className="bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50"
> >
Delete {dict.modelConfig.delete}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>

View File

@@ -16,6 +16,7 @@ import {
ModelSelectorTrigger, ModelSelectorTrigger,
} from "@/components/ai-elements/model-selector" } from "@/components/ai-elements/model-selector"
import { ButtonWithTooltip } from "@/components/button-with-tooltip" import { ButtonWithTooltip } from "@/components/button-with-tooltip"
import { useDictionary } from "@/hooks/use-dictionary"
import type { FlattenedModel } from "@/lib/types/model-config" import type { FlattenedModel } from "@/lib/types/model-config"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@@ -67,6 +68,7 @@ export function ModelSelector({
onConfigure, onConfigure,
disabled = false, disabled = false,
}: ModelSelectorProps) { }: ModelSelectorProps) {
const dict = useDictionary()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
// Only show validated models in the selector // Only show validated models in the selector
const validatedModels = useMemo( const validatedModels = useMemo(
@@ -96,8 +98,8 @@ export function ModelSelector({
} }
const tooltipContent = selectedModel const tooltipContent = selectedModel
? `${selectedModel.modelId} (click to change)` ? `${selectedModel.modelId} ${dict.modelConfig.clickToChange}`
: "Using server default model (click to change)" : `${dict.modelConfig.usingServerDefault} ${dict.modelConfig.clickToChange}`
return ( return (
<ModelSelectorRoot open={open} onOpenChange={setOpen}> <ModelSelectorRoot open={open} onOpenChange={setOpen}>
@@ -111,22 +113,26 @@ export function ModelSelector({
> >
<Bot className="h-4 w-4 flex-shrink-0 text-muted-foreground" /> <Bot className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
<span className="text-xs truncate"> <span className="text-xs truncate">
{selectedModel ? selectedModel.modelId : "Default"} {selectedModel
? selectedModel.modelId
: dict.modelConfig.default}
</span> </span>
<ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground" /> <ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
</ButtonWithTooltip> </ButtonWithTooltip>
</ModelSelectorTrigger> </ModelSelectorTrigger>
<ModelSelectorContent title="Select Model"> <ModelSelectorContent title={dict.modelConfig.selectModel}>
<ModelSelectorInput placeholder="Search models..." /> <ModelSelectorInput
placeholder={dict.modelConfig.searchModels}
/>
<ModelSelectorList> <ModelSelectorList>
<ModelSelectorEmpty> <ModelSelectorEmpty>
{validatedModels.length === 0 && models.length > 0 {validatedModels.length === 0 && models.length > 0
? "No verified models. Test your models first." ? dict.modelConfig.noVerifiedModels
: "No models found."} : dict.modelConfig.noModelsFound}
</ModelSelectorEmpty> </ModelSelectorEmpty>
{/* Server Default Option */} {/* Server Default Option */}
<ModelSelectorGroup heading="Default"> <ModelSelectorGroup heading={dict.modelConfig.default}>
<ModelSelectorItem <ModelSelectorItem
value="__server_default__" value="__server_default__"
onSelect={handleSelect} onSelect={handleSelect}
@@ -145,7 +151,7 @@ export function ModelSelector({
/> />
<Server className="mr-2 h-4 w-4 text-muted-foreground" /> <Server className="mr-2 h-4 w-4 text-muted-foreground" />
<ModelSelectorName> <ModelSelectorName>
Server Default {dict.modelConfig.serverDefault}
</ModelSelectorName> </ModelSelectorName>
</ModelSelectorItem> </ModelSelectorItem>
</ModelSelectorGroup> </ModelSelectorGroup>
@@ -201,13 +207,13 @@ export function ModelSelector({
> >
<Settings2 className="mr-2 h-4 w-4" /> <Settings2 className="mr-2 h-4 w-4" />
<ModelSelectorName> <ModelSelectorName>
Configure Models... {dict.modelConfig.configureModels}
</ModelSelectorName> </ModelSelectorName>
</ModelSelectorItem> </ModelSelectorItem>
</ModelSelectorGroup> </ModelSelectorGroup>
{/* Info text */} {/* Info text */}
<div className="px-3 py-2 text-xs text-muted-foreground border-t"> <div className="px-3 py-2 text-xs text-muted-foreground border-t">
Only verified models are shown {dict.modelConfig.onlyVerifiedShown}
</div> </div>
</ModelSelectorList> </ModelSelectorList>
</ModelSelectorContent> </ModelSelectorContent>

View File

@@ -19,10 +19,13 @@ export function register() {
const spanName = otelSpan.name const spanName = otelSpan.name
// Skip Next.js HTTP infrastructure spans // Skip Next.js HTTP infrastructure spans
if ( if (
spanName.startsWith("POST /") || spanName.startsWith("POST") ||
spanName.startsWith("GET /") || spanName.startsWith("GET") ||
spanName.startsWith("RSC") ||
spanName.includes("BaseServer") || spanName.includes("BaseServer") ||
spanName.includes("handleRequest") spanName.includes("handleRequest") ||
spanName.includes("resolve page") ||
spanName.includes("start response")
) { ) {
return false return false
} }
@@ -36,4 +39,5 @@ export function register() {
// Register globally so AI SDK's telemetry also uses this processor // Register globally so AI SDK's telemetry also uses this processor
tracerProvider.register() tracerProvider.register()
console.log("[Langfuse] Instrumentation initialized successfully")
} }

View File

@@ -95,8 +95,8 @@ function parseIntSafe(
* Supports various AI SDK providers with their unique configuration options * Supports various AI SDK providers with their unique configuration options
* *
* Environment variables: * Environment variables:
* - OPENAI_REASONING_EFFORT: OpenAI reasoning effort level (minimal/low/medium/high) - for o1/o3/gpt-5 * - OPENAI_REASONING_EFFORT: OpenAI reasoning effort level (minimal/low/medium/high) - for o1/o3/o4/gpt-5
* - OPENAI_REASONING_SUMMARY: OpenAI reasoning summary (none/brief/detailed) - auto-enabled for o1/o3/gpt-5 * - OPENAI_REASONING_SUMMARY: OpenAI reasoning summary (auto/detailed) - auto-enabled for o1/o3/o4/gpt-5
* - ANTHROPIC_THINKING_BUDGET_TOKENS: Anthropic thinking budget in tokens (1024-64000) * - ANTHROPIC_THINKING_BUDGET_TOKENS: Anthropic thinking budget in tokens (1024-64000)
* - ANTHROPIC_THINKING_TYPE: Anthropic thinking type (enabled) * - ANTHROPIC_THINKING_TYPE: Anthropic thinking type (enabled)
* - GOOGLE_THINKING_BUDGET: Google Gemini 2.5 thinking budget in tokens (1024-100000) * - GOOGLE_THINKING_BUDGET: Google Gemini 2.5 thinking budget in tokens (1024-100000)
@@ -118,18 +118,19 @@ function buildProviderOptions(
const reasoningEffort = process.env.OPENAI_REASONING_EFFORT const reasoningEffort = process.env.OPENAI_REASONING_EFFORT
const reasoningSummary = process.env.OPENAI_REASONING_SUMMARY const reasoningSummary = process.env.OPENAI_REASONING_SUMMARY
// OpenAI reasoning models (o1, o3, gpt-5) need reasoningSummary to return thoughts // OpenAI reasoning models (o1, o3, o4, gpt-5) need reasoningSummary to return thoughts
if ( if (
modelId && modelId &&
(modelId.includes("o1") || (modelId.includes("o1") ||
modelId.includes("o3") || modelId.includes("o3") ||
modelId.includes("o4") ||
modelId.includes("gpt-5")) modelId.includes("gpt-5"))
) { ) {
options.openai = { options.openai = {
// Auto-enable reasoning summary for reasoning models (default: detailed) // Auto-enable reasoning summary for reasoning models
// Use 'auto' as default since not all models support 'detailed'
reasoningSummary: reasoningSummary:
(reasoningSummary as "none" | "brief" | "detailed") || (reasoningSummary as "auto" | "detailed") || "auto",
"detailed",
} }
// Optionally configure reasoning effort // Optionally configure reasoning effort
@@ -152,8 +153,7 @@ function buildProviderOptions(
} }
if (reasoningSummary) { if (reasoningSummary) {
options.openai.reasoningSummary = reasoningSummary as options.openai.reasoningSummary = reasoningSummary as
| "none" | "auto"
| "brief"
| "detailed" | "detailed"
} }
} }
@@ -593,7 +593,9 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
apiKey, apiKey,
...(baseURL && { baseURL }), ...(baseURL && { baseURL }),
}) })
model = customOpenAI.chat(modelId) // Use Responses API (default) instead of .chat() to support reasoning
// for gpt-5, o1, o3, o4 models. Chat Completions API does not emit reasoning events.
model = customOpenAI(modelId)
} else { } else {
model = openai(modelId) model = openai(modelId)
} }

View File

@@ -202,6 +202,47 @@
"apiKeyStored": "API keys are stored locally in your browser", "apiKeyStored": "API keys are stored locally in your browser",
"test": "Test", "test": "Test",
"validationError": "Validation failed", "validationError": "Validation failed",
"addModelFirst": "Add at least one model to validate" "addModelFirst": "Add at least one model to validate",
"providers": "Providers",
"addProviderHint": "Add a provider to get started",
"verified": "Verified",
"configuration": "Configuration",
"displayName": "Display Name",
"awsAccessKeyId": "AWS Access Key ID",
"awsSecretAccessKey": "AWS Secret Access Key",
"awsRegion": "AWS Region",
"selectRegion": "Select region",
"apiKey": "API Key",
"enterApiKey": "Enter your API key",
"enterSecretKey": "Enter your secret access key",
"baseUrl": "Base URL",
"optional": "(optional)",
"customEndpoint": "Custom endpoint URL",
"models": "Models",
"customModelId": "Custom model ID...",
"allAdded": "All added",
"suggested": "Suggested",
"noModelsConfigured": "No models configured",
"modelIdEmpty": "Model ID cannot be empty",
"modelIdExists": "This model ID already exists",
"configureProviders": "Configure AI Providers",
"selectProviderHint": "Select a provider from the list or add a new one to configure API keys and models",
"deleteConfirmDesc": "Are you sure you want to delete {name}? This will remove all configured models and cannot be undone.",
"typeToConfirm": "Type \"{name}\" to confirm",
"typeProviderName": "Type provider name...",
"modelsConfiguredCount": "{count} model(s) configured",
"validationFailedCount": "{count} model(s) failed validation",
"cancel": "Cancel",
"delete": "Delete",
"clickToChange": "(click to change)",
"usingServerDefault": "Using server default model",
"selectModel": "Select Model",
"searchModels": "Search models...",
"noVerifiedModels": "No verified models. Test your models first.",
"noModelsFound": "No models found.",
"default": "Default",
"serverDefault": "Server Default",
"configureModels": "Configure Models...",
"onlyVerifiedShown": "Only verified models are shown"
} }
} }

View File

@@ -202,6 +202,47 @@
"apiKeyStored": "APIキーはブラウザにローカル保存されます", "apiKeyStored": "APIキーはブラウザにローカル保存されます",
"test": "テスト", "test": "テスト",
"validationError": "検証に失敗しました", "validationError": "検証に失敗しました",
"addModelFirst": "検証するには少なくとも1つのモデルを追加してください" "addModelFirst": "検証するには少なくとも1つのモデルを追加してください",
"providers": "プロバイダー",
"addProviderHint": "プロバイダーを追加して開始",
"verified": "検証済み",
"configuration": "設定",
"displayName": "表示名",
"awsAccessKeyId": "AWS アクセスキー ID",
"awsSecretAccessKey": "AWS シークレットアクセスキー",
"awsRegion": "AWS リージョン",
"selectRegion": "リージョンを選択",
"apiKey": "API キー",
"enterApiKey": "API キーを入力",
"enterSecretKey": "シークレットアクセスキーを入力",
"baseUrl": "ベース URL",
"optional": "(オプション)",
"customEndpoint": "カスタムエンドポイント URL",
"models": "モデル",
"customModelId": "カスタムモデル ID...",
"allAdded": "すべて追加済み",
"suggested": "おすすめ",
"noModelsConfigured": "モデルが設定されていません",
"modelIdEmpty": "モデル ID は空にできません",
"modelIdExists": "このモデル ID は既に存在します",
"configureProviders": "AI プロバイダーを設定",
"selectProviderHint": "リストからプロバイダーを選択するか、新規追加して API キーとモデルを設定",
"deleteConfirmDesc": "{name} を削除してもよろしいですか?設定されたすべてのモデルが削除され、元に戻せません。",
"typeToConfirm": "確認のため「{name}」と入力",
"typeProviderName": "プロバイダー名を入力...",
"modelsConfiguredCount": "{count} 個のモデルを設定済み",
"validationFailedCount": "{count} 個のモデルの検証に失敗",
"cancel": "キャンセル",
"delete": "削除",
"clickToChange": "(クリックして変更)",
"usingServerDefault": "サーバーデフォルトモデルを使用中",
"selectModel": "モデルを選択",
"searchModels": "モデルを検索...",
"noVerifiedModels": "検証済みのモデルがありません。先にモデルをテストしてください。",
"noModelsFound": "モデルが見つかりません。",
"default": "デフォルト",
"serverDefault": "サーバーデフォルト",
"configureModels": "モデルを設定...",
"onlyVerifiedShown": "検証済みのモデルのみ表示"
} }
} }

View File

@@ -202,6 +202,47 @@
"apiKeyStored": "API 密钥存储在您的浏览器本地", "apiKeyStored": "API 密钥存储在您的浏览器本地",
"test": "测试", "test": "测试",
"validationError": "验证失败", "validationError": "验证失败",
"addModelFirst": "请先添加至少一个模型以进行验证" "addModelFirst": "请先添加至少一个模型以进行验证",
"providers": "提供商",
"addProviderHint": "添加提供商即可开始使用",
"verified": "已验证",
"configuration": "配置",
"displayName": "显示名称",
"awsAccessKeyId": "AWS 访问密钥 ID",
"awsSecretAccessKey": "AWS Secret Access Key",
"awsRegion": "AWS 区域",
"selectRegion": "选择区域",
"apiKey": "API 密钥",
"enterApiKey": "输入您的 API 密钥",
"enterSecretKey": "输入您的 Secret Key",
"baseUrl": "基础 URL",
"optional": "(可选)",
"customEndpoint": "自定义端点 URL",
"models": "模型",
"customModelId": "自定义模型 ID...",
"allAdded": "已全部添加",
"suggested": "推荐",
"noModelsConfigured": "尚未配置模型",
"modelIdEmpty": "模型 ID 不能为空",
"modelIdExists": "此模型 ID 已存在",
"configureProviders": "配置 AI 提供商",
"selectProviderHint": "从列表中选择提供商或添加新的以配置 API 密钥和模型",
"deleteConfirmDesc": "确定要删除 {name} 吗?这将移除所有配置的模型且无法撤销。",
"typeToConfirm": "输入 \"{name}\" 以确认",
"typeProviderName": "输入提供商名称...",
"modelsConfiguredCount": "已配置 {count} 个模型",
"validationFailedCount": "{count} 个模型验证失败",
"cancel": "取消",
"delete": "删除",
"clickToChange": "(点击更改)",
"usingServerDefault": "使用服务器默认模型",
"selectModel": "选择模型",
"searchModels": "搜索模型...",
"noVerifiedModels": "没有已验证的模型。请先测试您的模型。",
"noModelsFound": "未找到模型。",
"default": "默认",
"serverDefault": "服务器默认",
"configureModels": "配置模型...",
"onlyVerifiedShown": "仅显示已验证的模型"
} }
} }