Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dto/channel_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type ChannelOtherSettings struct {
UpstreamModelUpdateLastDetectedModels []string `json:"upstream_model_update_last_detected_models,omitempty"` // 上次检测到的可加入模型
UpstreamModelUpdateLastRemovedModels []string `json:"upstream_model_update_last_removed_models,omitempty"` // 上次检测到的可删除模型
UpstreamModelUpdateIgnoredModels []string `json:"upstream_model_update_ignored_models,omitempty"` // 手动忽略的模型
UseFullURL bool `json:"use_full_url,omitempty"` // 是否使用完整请求URL(不拼接路径)
}

func (s *ChannelOtherSettings) IsOpenRouterEnterprise() bool {
Expand Down
36 changes: 27 additions & 9 deletions relay/channel/api_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,15 @@ func applyHeaderOverrideToRequest(req *http.Request, headerOverride map[string]s
}

func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody io.Reader) (*http.Response, error) {
fullRequestURL, err := a.GetRequestURL(info)
if err != nil {
return nil, fmt.Errorf("get request url failed: %w", err)
var fullRequestURL string
var err error
if info.ChannelMeta.ChannelOtherSettings.UseFullURL {
fullRequestURL = info.ChannelBaseUrl
} else {
fullRequestURL, err = a.GetRequestURL(info)
if err != nil {
return nil, fmt.Errorf("get request url failed: %w", err)
}
}
if common2.DebugEnabled {
println("fullRequestURL:", fullRequestURL)
Expand Down Expand Up @@ -319,9 +325,15 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody
}

func DoFormRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody io.Reader) (*http.Response, error) {
fullRequestURL, err := a.GetRequestURL(info)
if err != nil {
return nil, fmt.Errorf("get request url failed: %w", err)
var fullRequestURL string
var err error
if info.ChannelMeta.ChannelOtherSettings.UseFullURL {
fullRequestURL = info.ChannelBaseUrl
} else {
fullRequestURL, err = a.GetRequestURL(info)
if err != nil {
return nil, fmt.Errorf("get request url failed: %w", err)
}
}
if common2.DebugEnabled {
println("fullRequestURL:", fullRequestURL)
Expand Down Expand Up @@ -352,9 +364,15 @@ func DoFormRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBod
}

func DoWssRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody io.Reader) (*websocket.Conn, error) {
fullRequestURL, err := a.GetRequestURL(info)
if err != nil {
return nil, fmt.Errorf("get request url failed: %w", err)
var fullRequestURL string
var err error
if info.ChannelMeta.ChannelOtherSettings.UseFullURL {
fullRequestURL = info.ChannelBaseUrl
} else {
fullRequestURL, err = a.GetRequestURL(info)
if err != nil {
return nil, fmt.Errorf("get request url failed: %w", err)
}
}
targetHeader := http.Header{}
err = a.SetupRequestHeader(c, &targetHeader, info)
Expand Down
41 changes: 31 additions & 10 deletions web/default/src/components/ui/combobox-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,33 @@ export function ComboboxInput({
const { t } = useTranslation()
const [open, setOpen] = React.useState(false)
const [highlightedIndex, setHighlightedIndex] = React.useState(-1)
const [editValue, setEditValue] = React.useState('')
const [isEditing, setIsEditing] = React.useState(false)
const containerRef = React.useRef<HTMLDivElement>(null)
const inputRef = React.useRef<HTMLInputElement>(null)
const listRef = React.useRef<HTMLUListElement>(null)

const selectedOption = React.useMemo(
() => options.find((option) => option.value === value),
[options, value]
)

const displayValue = isEditing ? editValue : (selectedOption?.label ?? value)

React.useEffect(() => {
setIsEditing(false)
}, [value])

const filteredOptions = React.useMemo(() => {
if (!value.trim()) return options
const search = value.toLowerCase().trim()
if (!isEditing) return options
const search = editValue.toLowerCase().trim()
if (!search) return options
return options.filter(
(option) =>
option.label.toLowerCase().includes(search) ||
option.value.toLowerCase().includes(search)
)
}, [options, value])
}, [options, editValue, isEditing])

// Reset highlight when filtered options change
React.useEffect(() => {
Expand All @@ -87,9 +101,11 @@ export function ComboboxInput({
}, [open])

const handleSelect = (selectedValue: string) => {
setEditValue('')
setIsEditing(false)
onValueChange(selectedValue)
setOpen(false)
inputRef.current?.focus()
inputRef.current?.blur()
}

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -117,8 +133,12 @@ export function ComboboxInput({
e.preventDefault()
if (highlightedIndex >= 0 && filteredOptions[highlightedIndex]) {
handleSelect(filteredOptions[highlightedIndex].value)
} else if (isEditing && editValue.trim()) {
setEditValue('')
setIsEditing(false)
onValueChange(editValue.trim())
setOpen(false)
} else {
// No highlighted option, just close the dropdown and keep current value
setOpen(false)
}
break
Expand All @@ -136,7 +156,7 @@ export function ComboboxInput({
item?.scrollIntoView({ block: 'nearest' })
}, [highlightedIndex])

const showDropdown = open && (filteredOptions.length > 0 || value.trim())
const showDropdown = open && (filteredOptions.length > 0 || (isEditing && editValue.trim()))

return (
<div ref={containerRef} className='relative'>
Expand All @@ -150,9 +170,10 @@ export function ComboboxInput({
aria-autocomplete='list'
autoComplete='off'
placeholder={placeholder}
value={value}
value={displayValue}
onChange={(e) => {
onValueChange(e.target.value)
setEditValue(e.target.value)
if (!isEditing) setIsEditing(true)
if (!open) setOpen(true)
}}
onFocus={() => setOpen(true)}
Expand Down Expand Up @@ -201,9 +222,9 @@ export function ComboboxInput({
) : (
<div className='px-2 py-6 text-center text-sm'>
{emptyText}
{value.trim() && (
{isEditing && editValue.trim() && (
<div className='text-muted-foreground mt-1 text-xs'>
{t('Press Enter to use "{{value}}"', { value: value.trim() })}
{t('Press Enter to use "{{value}}"', { value: editValue.trim() })}
</div>
)}
</div>
Expand Down
Loading