Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
69420f7
feat: 渠道编辑页增加复制所有模型功能
JoeyLearnsToCode May 19, 2025
66778ef
feat: enhance token usage details for upstream OpenRouter
neotf May 28, 2025
3d9587f
feat: enhance cache_create_tokens calculation for OpenRouter
neotf May 29, 2025
e77555a
Merge branch 'main' into feat-04
neotf Jun 5, 2025
52763c0
Merge branch 'main' into feat-04
neotf Jun 11, 2025
c4f25a7
format
neotf Jun 11, 2025
d67d5d8
format
neotf Jun 11, 2025
296da5d
feat: openrouter format for claude request
prnake Jun 16, 2025
81bc096
Merge branch 'main' into feat-04
neotf Jun 18, 2025
a6363a5
Update relay/channel/openai/adaptor.go
neotf Jun 18, 2025
37fbcb7
format
neotf Jun 18, 2025
16c63b3
fix(quota): refine cache token calculation for OpenRouter channel type
neotf Jun 18, 2025
f7f1be9
🎨 refactor: Refactor RatioSetting: integrate Group Ratio Settings int…
t0ng7u Jun 21, 2025
b43423b
✨ feat(ratio-sync): support /api/pricing parsing, confidence verifica…
t0ng7u Jun 21, 2025
7ea33c2
Merge remote-tracking branch 'origin/main' into alpha
t0ng7u Jun 21, 2025
44d20de
feat: add placeholder for kling AccessKey and SecretKey
feitianbubu Jun 21, 2025
e4def06
feat: kling apiKey format to use `|` delimiter
feitianbubu Jun 21, 2025
384fadf
✨ feat(gemini): enhance ThinkingAdapter and model handling
Calcium-Ion Jun 21, 2025
58c9c7d
✨ feat(settings-announcements): improve editor UX with modal & tooltips
t0ng7u Jun 21, 2025
b842baf
✨ feat(settings-faq): improve FAQ table readability with tooltip & te…
t0ng7u Jun 21, 2025
ea379e1
📝 feat(SettingsAnnouncements/SettingsFAQ): update placeholders & succ…
t0ng7u Jun 21, 2025
5ad9f8d
Merge remote-tracking branch 'origin/alpha' into alpha
t0ng7u Jun 21, 2025
ee8956b
Merge pull request #1279 from feitianbubu/add-kling-key-placeholder
Calcium-Ion Jun 21, 2025
ef32cc8
✨ fix(dto): change Created field type in OpenAITextResponse to any. (…
Calcium-Ion Jun 21, 2025
41b0cf4
Merge remote-tracking branch 'origin/alpha' into alpha
Calcium-Ion Jun 21, 2025
ea79d59
Merge pull request #1235 from prnake/thinking-fix-0616
Calcium-Ion Jun 21, 2025
fd4447f
Merge pull request #1120 from neotf/feat-04
Calcium-Ion Jun 21, 2025
df86273
fix: update JSON decoding and budget token handling in RequestOpenAI2…
Calcium-Ion Jun 21, 2025
7da224b
fix: mj userGroupRatio
creamlike1024 Jun 22, 2025
39617bc
task userGroupRatio
creamlike1024 Jun 22, 2025
093d860
🗑️ feat(token): implement batch token deletion API & front-end integr…
t0ng7u Jun 22, 2025
dce794d
Merge remote-tracking branch 'origin/alpha' into alpha
t0ng7u Jun 22, 2025
d7c97d4
✨ feat(tokens-table): add selectable copy modes for bulk token copy a…
t0ng7u Jun 22, 2025
93e7675
fix: reset channel key in UpdateChannel function
Calcium-Ion Jun 22, 2025
f7c3b04
Merge remote-tracking branch 'origin/alpha' into alpha
Calcium-Ion Jun 22, 2025
fd04098
refactor: streamline price calculation in RelaySwapFace and RelayMidj…
Calcium-Ion Jun 22, 2025
63640f6
Merge pull request #1281 from QuantumNous/mj_usergroupratio
Calcium-Ion Jun 22, 2025
014c945
✨ feat(ui): Implement unified compact/adaptive table mode + icon refi…
t0ng7u Jun 22, 2025
a85a594
Merge remote-tracking branch 'origin/alpha' into alpha
t0ng7u Jun 22, 2025
6192aeb
🍭style: update home main title style `font-semibold` to `font-bold`
t0ng7u Jun 22, 2025
75c71c3
🔧 chore: update STREAMING_TIMEOUT default value to 120 seconds in con…
Calcium-Ion Jun 22, 2025
5367015
🎛️ feat(web): add “Conflict Rates” filter & highlight in Model Settin…
t0ng7u Jun 23, 2025
ffc22b8
Merge branch 'main' into feat-copy-models
JoeyLearnsToCode Jun 23, 2025
c281903
🐛 fix(ratio-sync): reset pagination when filter/search changes
t0ng7u Jun 23, 2025
2591ca3
🚀 chore(ui): Refactor UpstreamRatioSync with conflict-modal component…
t0ng7u Jun 23, 2025
7c72545
Merge remote-tracking branch 'origin/alpha' into alpha
t0ng7u Jun 23, 2025
cd2870a
feat: add origin kling api
feitianbubu Jun 23, 2025
bc37177
🚀 feat: add enabled/disabled channel filtering & optimize type-based …
t0ng7u Jun 23, 2025
75e3959
🧬merge: Add a button to copy the selected model in the channel (#1290)
t0ng7u Jun 23, 2025
1ab75b8
🎨 feat(EditChannel): improve model selection UX, clipboard feedback &…
t0ng7u Jun 23, 2025
c974b10
🐛 fix(channel): remove duplicate model names in “Edit Channel” model …
t0ng7u Jun 23, 2025
a8ba2eb
🐛 fix: preserve group filter when switching channel type/status
t0ng7u Jun 23, 2025
6c31151
💄 i18n: shorten channel search placeholder and update i18n
t0ng7u Jun 23, 2025
5a5e8ce
Revert "🐛 fix: preserve group filter when switching channel type/status"
t0ng7u Jun 23, 2025
db1b11d
✨ fix(channels-table): preserve group filter when switching type or s…
t0ng7u Jun 23, 2025
aeb393e
🚀 feat: Align search API with channel listing & fix sorting toggle
t0ng7u Jun 23, 2025
de6e551
✨ fix: ensure table shows correct loading state on first render & dur…
t0ng7u Jun 23, 2025
44495b1
🚀 feat: enhance model testing UI with bulk selection, copy & success-…
t0ng7u Jun 24, 2025
0c5d4ca
🎨 style(channels-table): standardize operation component size to `small`
t0ng7u Jun 24, 2025
3f53e4f
🔧 fix(model_ratio): adjust return values for gemini-2.5-pro and gemin…
Calcium-Ion Jun 24, 2025
277645d
🔧 style(ui): Inline tag edit action in ChannelsTable
t0ng7u Jun 24, 2025
6478202
Merge remote-tracking branch 'origin/alpha' into alpha
t0ng7u Jun 24, 2025
048a625
🚀 feat(auth): support new model API paths in authentication and routing
Calcium-Ion Jun 24, 2025
29a44eb
✨ feat(homepage): enhance banner visuals & UX
t0ng7u Jun 25, 2025
d6276c4
Merge remote-tracking branch 'origin/alpha' into alpha
t0ng7u Jun 25, 2025
5a6f32c
🎨 style(ui): refactor Tabs in ModelPricing to use native Semi UI styling
t0ng7u Jun 25, 2025
0689670
🔧 fix(xinference): update Document type to 'any' for flexibility
Calcium-Ion Jun 25, 2025
ad5f731
🍭style: add `mt-[64px]` in class auth componets
t0ng7u Jun 25, 2025
fcefac9
🐛 fix(auth): prevent initial render flicker & clean up state usage
t0ng7u Jun 25, 2025
6d7d429
💫 feat(ui): introduce dispersed blur-ball background to all auth views
t0ng7u Jun 25, 2025
f0d8887
🐛 fix(auth): restore proper state & context destructuring in Login- a…
t0ng7u Jun 25, 2025
6935260
🧶style(TokensTable): add `IconDelete` in `Delete selected token` button
t0ng7u Jun 25, 2025
86f374d
🐛 fix(auth): prevent duplicate “session expired” toast on login
t0ng7u Jun 25, 2025
05d9aa5
🔒 style: Hide registration link when Self-Use Mode is enabled
t0ng7u Jun 25, 2025
39cb45c
🎨 style: unify card header UI, switch to Avatar icons & remove oversi…
t0ng7u Jun 26, 2025
1a0aac8
🎨 style: remove all prefix icons to simplify the layout of the sidesh…
t0ng7u Jun 26, 2025
aa15d45
✨ refactor(ui/token): migrate EditToken page to Semi Form API and pol…
t0ng7u Jun 26, 2025
d07224e
🎁 refactor(ui/redemption): migrate EditRedemption page to Semi Form &…
t0ng7u Jun 26, 2025
25e17b9
🐛 fix(redemptions-table): show loading indicator while refetching data
t0ng7u Jun 26, 2025
ab32e15
🐛 fix(redemptions-table): correct initial page index and pagination s…
t0ng7u Jun 26, 2025
38d3ab5
💄refactor: enhance `EditUser` and `AddUser` form validation & UX
t0ng7u Jun 27, 2025
3ced5ff
✨ chore: Improve channel creation UX: defer "Fetch Model List" action…
t0ng7u Jun 27, 2025
2171117
Merge pull request #1291 from feitianbubu/pr/add-origin-kling-api
xyfacai Jun 27, 2025
1dad04e
✨ feat: add Function and Container fields to ResponsesToolsCall struc…
Calcium-Ion Jun 27, 2025
05ea0dd
feat: add video channel jimeng
feitianbubu Jun 20, 2025
5d289d3
🐛 fix: handle response body errors more gracefully in OpenAI handler
Calcium-Ion Jun 27, 2025
647f8d7
Merge pull request #1274 from feitianbubu/feat/add-channel-jimeng
Calcium-Ion Jun 27, 2025
3002659
✨ feat: add CloseResponseBodyGracefully function to handle HTTP respo…
Calcium-Ion Jun 27, 2025
281054f
🐛 fix: replace direct response body closure with common.CloseResponse…
Calcium-Ion Jun 27, 2025
9c08d8c
✨ feat: introduce IOCopyBytesGracefully function for streamlined resp…
Calcium-Ion Jun 27, 2025
bfb6fbb
🐛 fix: update hardcoded completion model ratio for gemini-2.5-flash-lite
Calcium-Ion Jun 27, 2025
9e6bc51
🐛 fix: refactor OaiStreamHandler to improve last response handling an…
Calcium-Ion Jun 27, 2025
0a04a76
🐛 fix: refactor JSON encoding and decoding in OpenAI handlers for imp…
Calcium-Ion Jun 27, 2025
59a1f4c
feat: video action to constant
feitianbubu Jun 27, 2025
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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
# 设置 Dify 渠道是否输出工作流和节点信息到客户端
# DIFY_DEBUG=true
# 设置流式一次回复的超时时间
# STREAMING_TIMEOUT=90
# STREAMING_TIMEOUT=120


# 节点类型
Expand Down
2 changes: 1 addition & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ This version supports multiple models, please refer to [API Documentation-Relay
For detailed configuration instructions, please refer to [Installation Guide-Environment Variables Configuration](https://docs.newapi.pro/installation/environment-variables):

- `GENERATE_DEFAULT_TOKEN`: Whether to generate initial tokens for newly registered users, default is `false`
- `STREAMING_TIMEOUT`: Streaming response timeout, default is 60 seconds
- `STREAMING_TIMEOUT`: Streaming response timeout, default is 120 seconds
- `DIFY_DEBUG`: Whether to output workflow and node information for Dify channels, default is `true`
- `FORCE_STREAM_OPTION`: Whether to override client stream_options parameter, default is `true`
- `GET_MEDIA_TOKEN`: Whether to count image tokens, default is `true`
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ New API提供了丰富的功能,详细特性请参考[特性说明](https://do
详细配置说明请参考[安装指南-环境变量配置](https://docs.newapi.pro/installation/environment-variables):

- `GENERATE_DEFAULT_TOKEN`:是否为新注册用户生成初始令牌,默认为 `false`
- `STREAMING_TIMEOUT`:流式回复超时时间,默认60秒
- `STREAMING_TIMEOUT`:流式回复超时时间,默认120秒
- `DIFY_DEBUG`:Dify渠道是否输出工作流和节点信息,默认 `true`
- `FORCE_STREAM_OPTION`:是否覆盖客户端stream_options参数,默认 `true`
- `GET_MEDIA_TOKEN`:是否统计图片token,默认 `true`
Expand Down
2 changes: 2 additions & 0 deletions common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ const (
ChannelTypeXai = 48
ChannelTypeCoze = 49
ChannelTypeKling = 50
ChannelTypeJimeng = 51
ChannelTypeDummy // this one is only for count, do not add any channel after this

)
Expand Down Expand Up @@ -298,4 +299,5 @@ var ChannelBaseURLs = []string{
"https://api.x.ai", //48
"https://api.coze.cn", //49
"https://api.klingai.com", //50
"https://visual.volcengineapi.com", //51
}
56 changes: 56 additions & 0 deletions common/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package common

import (
"bytes"
"fmt"
"github.com/gin-gonic/gin"
"io"
"net/http"
)

func CloseResponseBodyGracefully(httpResponse *http.Response) {
if httpResponse == nil || httpResponse.Body == nil {
return
}
err := httpResponse.Body.Close()
if err != nil {
SysError("failed to close response body: " + err.Error())
}
}

func IOCopyBytesGracefully(c *gin.Context, src *http.Response, data []byte) {
if src == nil || src.Body == nil {
return
}

defer CloseResponseBodyGracefully(src)

if c.Writer == nil {
return
}

src.Body = io.NopCloser(bytes.NewBuffer(data))

// We shouldn't set the header before we parse the response body, because the parse part may fail.
// And then we will have to send an error response, but in this case, the header has already been set.
// So the httpClient will be confused by the response.
// For example, Postman will report error, and we cannot check the response at all.
for k, v := range src.Header {
// avoid setting Content-Length
if k == "Content-Length" {
continue
}
c.Writer.Header().Set(k, v[0])
}

// set Content-Length header manually
c.Writer.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))

c.Writer.WriteHeader(src.StatusCode)
c.Writer.WriteHeaderNow()

_, err := io.Copy(c.Writer, src.Body)
if err != nil {
LogError(c, fmt.Sprintf("failed to copy response body: %s", err.Error()))
}
}
1 change: 1 addition & 0 deletions constant/context_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ const (
ContextKeyUserStatus = "user_status"
ContextKeyUserEmail = "user_email"
ContextKeyUserGroup = "user_group"
ContextKeyUsingGroup = "group"
)
2 changes: 1 addition & 1 deletion constant/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var ErrorLogEnabled bool
//}

func InitEnv() {
StreamingTimeout = common.GetEnvOrDefault("STREAMING_TIMEOUT", 60)
StreamingTimeout = common.GetEnvOrDefault("STREAMING_TIMEOUT", 120)
DifyDebug = common.GetEnvOrDefaultBool("DIFY_DEBUG", true)
MaxFileDownloadMB = common.GetEnvOrDefault("MAX_FILE_DOWNLOAD_MB", 20)
// ForceStreamOption 覆盖请求参数,强制返回usage信息
Expand Down
4 changes: 4 additions & 0 deletions constant/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ const (
TaskPlatformSuno TaskPlatform = "suno"
TaskPlatformMidjourney = "mj"
TaskPlatformKling TaskPlatform = "kling"
TaskPlatformJimeng TaskPlatform = "jimeng"
)

const (
SunoActionMusic = "MUSIC"
SunoActionLyrics = "LYRICS"

TaskActionGenerate = "generate"
TaskActionTextGenerate = "textGenerate"
)

var SunoModel2Action = map[string]string{
Expand Down
5 changes: 4 additions & 1 deletion controller/channel-test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
if channel.Type == common.ChannelTypeKling {
return errors.New("kling channel test is not supported"), nil
}
if channel.Type == common.ChannelTypeJimeng {
return errors.New("jimeng channel test is not supported"), nil
}
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)

Expand Down Expand Up @@ -171,7 +174,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatioInfo.GroupRatio, priceData.CompletionRatio,
usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.GroupRatioInfo.GroupSpecialRatio)
model.RecordConsumeLog(c, 1, channel.Id, usage.PromptTokens, usage.CompletionTokens, info.OriginModelName, "模型测试",
quota, "模型测试", 0, quota, int(consumedTime), false, info.Group, other)
quota, "模型测试", 0, quota, int(consumedTime), false, info.UsingGroup, other)
common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))
return nil, nil
}
Expand Down
151 changes: 124 additions & 27 deletions controller/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ type OpenAIModelsResponse struct {
Success bool `json:"success"`
}

func parseStatusFilter(statusParam string) int {
switch strings.ToLower(statusParam) {
case "enabled", "1":
return common.ChannelStatusEnabled
case "disabled", "0":
return 0
default:
return -1
}
}

func GetAllChannels(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
Expand All @@ -52,6 +63,9 @@ func GetAllChannels(c *gin.Context) {
channelData := make([]*model.Channel, 0)
idSort, _ := strconv.ParseBool(c.Query("id_sort"))
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode"))
statusParam := c.Query("status")
// statusFilter: -1 all, 1 enabled, 0 disabled (include auto & manual)
statusFilter := parseStatusFilter(statusParam)
// type filter
typeStr := c.Query("type")
typeFilter := -1
Expand All @@ -64,42 +78,75 @@ func GetAllChannels(c *gin.Context) {
var total int64

if enableTagMode {
// tag 分页:先分页 tag,再取各 tag 下 channels
tags, err := model.GetPaginatedTags((p-1)*pageSize, pageSize)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
for _, tag := range tags {
if tag != nil && *tag != "" {
tagChannel, err := model.GetChannelsByTag(*tag, idSort)
if err == nil {
channelData = append(channelData, tagChannel...)
if tag == nil || *tag == "" {
continue
}
tagChannels, err := model.GetChannelsByTag(*tag, idSort)
if err != nil {
continue
}
filtered := make([]*model.Channel, 0)
for _, ch := range tagChannels {
if statusFilter == common.ChannelStatusEnabled && ch.Status != common.ChannelStatusEnabled {
continue
}
if statusFilter == 0 && ch.Status == common.ChannelStatusEnabled {
continue
}
if typeFilter >= 0 && ch.Type != typeFilter {
continue
}
filtered = append(filtered, ch)
}
channelData = append(channelData, filtered...)
}
// 计算 tag 总数用于分页
total, _ = model.CountAllTags()
} else if typeFilter >= 0 {
channels, err := model.GetChannelsByType((p-1)*pageSize, pageSize, idSort, typeFilter)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
channelData = channels
total, _ = model.CountChannelsByType(typeFilter)
} else {
channels, err := model.GetAllChannels((p-1)*pageSize, pageSize, false, idSort)
baseQuery := model.DB.Model(&model.Channel{})
if typeFilter >= 0 {
baseQuery = baseQuery.Where("type = ?", typeFilter)
}
if statusFilter == common.ChannelStatusEnabled {
baseQuery = baseQuery.Where("status = ?", common.ChannelStatusEnabled)
} else if statusFilter == 0 {
baseQuery = baseQuery.Where("status != ?", common.ChannelStatusEnabled)
}

baseQuery.Count(&total)

order := "priority desc"
if idSort {
order = "id desc"
}

err := baseQuery.Order(order).Limit(pageSize).Offset((p-1)*pageSize).Omit("key").Find(&channelData).Error
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
channelData = channels
total, _ = model.CountAllChannels()
}

// calculate type counts
typeCounts, _ := model.CountChannelsGroupByType()
countQuery := model.DB.Model(&model.Channel{})
if statusFilter == common.ChannelStatusEnabled {
countQuery = countQuery.Where("status = ?", common.ChannelStatusEnabled)
} else if statusFilter == 0 {
countQuery = countQuery.Where("status != ?", common.ChannelStatusEnabled)
}
var results []struct {
Type int64
Count int64
}
_ = countQuery.Select("type, count(*) as count").Group("type").Find(&results).Error
typeCounts := make(map[int64]int64)
for _, r := range results {
typeCounts[r.Type] = r.Count
}

c.JSON(http.StatusOK, gin.H{
"success": true,
Expand Down Expand Up @@ -134,13 +181,6 @@ func FetchUpstreamModels(c *gin.Context) {
return
}

//if channel.Type != common.ChannelTypeOpenAI {
// c.JSON(http.StatusOK, gin.H{
// "success": false,
// "message": "仅支持 OpenAI 类型渠道",
// })
// return
//}
baseURL := common.ChannelBaseURLs[channel.Type]
if channel.GetBaseURL() != "" {
baseURL = channel.GetBaseURL()
Expand Down Expand Up @@ -206,6 +246,8 @@ func SearchChannels(c *gin.Context) {
keyword := c.Query("keyword")
group := c.Query("group")
modelKeyword := c.Query("model")
statusParam := c.Query("status")
statusFilter := parseStatusFilter(statusParam)
idSort, _ := strconv.ParseBool(c.Query("id_sort"))
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode"))
channelData := make([]*model.Channel, 0)
Expand Down Expand Up @@ -238,17 +280,71 @@ func SearchChannels(c *gin.Context) {
channelData = channels
}

if statusFilter == common.ChannelStatusEnabled || statusFilter == 0 {
filtered := make([]*model.Channel, 0, len(channelData))
for _, ch := range channelData {
if statusFilter == common.ChannelStatusEnabled && ch.Status != common.ChannelStatusEnabled {
continue
}
if statusFilter == 0 && ch.Status == common.ChannelStatusEnabled {
continue
}
filtered = append(filtered, ch)
}
channelData = filtered
}

// calculate type counts for search results
typeCounts := make(map[int64]int64)
for _, channel := range channelData {
typeCounts[int64(channel.Type)]++
}

typeParam := c.Query("type")
typeFilter := -1
if typeParam != "" {
if tp, err := strconv.Atoi(typeParam); err == nil {
typeFilter = tp
}
}

if typeFilter >= 0 {
filtered := make([]*model.Channel, 0, len(channelData))
for _, ch := range channelData {
if ch.Type == typeFilter {
filtered = append(filtered, ch)
}
}
channelData = filtered
}

page, _ := strconv.Atoi(c.DefaultQuery("p", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}

total := len(channelData)
startIdx := (page - 1) * pageSize
if startIdx > total {
startIdx = total
}
endIdx := startIdx + pageSize
if endIdx > total {
endIdx = total
}

pagedData := channelData[startIdx:endIdx]

c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": gin.H{
"items": channelData,
"items": pagedData,
"total": total,
"type_counts": typeCounts,
},
})
Expand Down Expand Up @@ -546,6 +642,7 @@ func UpdateChannel(c *gin.Context) {
})
return
}
channel.Key = ""
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
Expand Down
Loading