Skip to content
Merged
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
130 changes: 1 addition & 129 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,125 +37,6 @@ yarn add @yokowu/modelkit-ui

需要实现以下4个接口,其中 `listModel` 和 `checkModel` 已提供业务逻辑,在handler中调用即可:

#### ListModel 接口
- **请求参数** (`domain.ModelListReq`):
```go
type ModelListReq struct {
Provider string `json:"provider" validate:"required,oneof=SiliconFlow OpenAI Ollama DeepSeek Moonshot AzureOpenAI BaiZhiCloud Hunyuan BaiLian Volcengine Gemini ZhiPu"`
BaseURL string `json:"base_url" validate:"required"`
APIKey string `json:"api_key"`
APIHeader string `json:"api_header"`
Type string `json:"type" validate:"required,oneof=chat embedding rerank"`
}
```
- **响应参数** (`domain.ModelListResp`):
```go
type ModelListResp struct {
Models []ModelListItem `json:"models"`
}
type ModelListItem struct {
Model string `json:"model"`
}
```

#### CheckModel 接口
- **请求参数** (`domain.CheckModelReq`):
```go
type CheckModelReq struct {
Provider string `json:"provider" validate:"required,oneof=OpenAI Ollama DeepSeek SiliconFlow Moonshot Other AzureOpenAI BaiZhiCloud Hunyuan BaiLian Volcengine Gemini ZhiPu"`
Model string `json:"model" validate:"required"`
BaseURL string `json:"base_url" validate:"required"`
APIKey string `json:"api_key"`
APIHeader string `json:"api_header"`
APIVersion string `json:"api_version"` // for azure openai
Type string `json:"type" validate:"required,oneof=chat embedding rerank"`
}
```
- **响应参数** (`domain.CheckModelResp`):
```go
type CheckModelResp struct {
Error string `json:"error"`
Content string `json:"content"`
}
```

#### CreateModel 接口
- **请求参数** (`CreateModelReq`):
```go
type CreateModelReq struct {
APIBase string `json:"api_base" validate:"required"`
APIHeader string `json:"api_header"`
APIKey string `json:"api_key"`
APIVersion string `json:"api_version"`
ModelName string `json:"model_name" validate:"required"`
ModelType string `json:"model_type" validate:"oneof=llm coder embedding audio reranker"`
Param *ModelParam `json:"param"`
Provider string `json:"provider" validate:"required,oneof=SiliconFlow OpenAI Ollama DeepSeek Moonshot AzureOpenAI BaiZhiCloud Hunyuan BaiLian Volcengine Other"`
ShowName string `json:"show_name"`
}

type ModelParam struct {
ContextWindow int `json:"context_window"`
MaxTokens int `json:"max_tokens"`
R1Enabled bool `json:"r1_enabled"`
SupportComputerUse bool `json:"support_computer_use"`
SupportImages bool `json:"support_images"`
SupportPromptCache bool `json:"support_prompt_cache"`
}
```
- **响应参数** (`CreateModelResp`):
```go
type CreateModelResp struct {
Model Model `json:"model"`
}
```

#### UpdateModel 接口
- **请求参数** (`UpdateModelReq`):
```go
type UpdateModelReq struct {
ID string `json:"id" validate:"required"`
APIBase string `json:"api_base"`
APIHeader string `json:"api_header"`
APIKey string `json:"api_key"`
APIVersion string `json:"api_version"`
ModelName string `json:"model_name"`
Param *ModelParam `json:"param"`
Provider string `json:"provider" validate:"oneof=SiliconFlow OpenAI Ollama DeepSeek Moonshot AzureOpenAI BaiZhiCloud Hunyuan BaiLian Volcengine Other"`
ShowName string `json:"show_name"`
Status string `json:"status" validate:"oneof=active inactive"`
}
```
- **响应参数** (`UpdateModelResp`):
```go
type UpdateModelResp struct {
Model Model `json:"model"`
}
```

#### 通用Model结构
```go
type Model struct {
ID string `json:"id"`
APIBase string `json:"api_base"`
APIHeader string `json:"api_header"`
APIKey string `json:"api_key"`
APIVersion string `json:"api_version"`
CreatedAt int64 `json:"created_at"`
Input int `json:"input"`
IsActive bool `json:"is_active"`
IsInternal bool `json:"is_internal"`
ModelName string `json:"model_name"`
ModelType string `json:"model_type"`
Output int `json:"output"`
Param *ModelParam `json:"param"`
Provider string `json:"provider"`
ShowName string `json:"show_name"`
Status string `json:"status"`
UpdatedAt int64 `json:"updated_at"`
}
```

### 3. 后端使用方式

在handler中调用 `listModel` 与 `checkModel` 业务逻辑:
Expand Down Expand Up @@ -340,26 +221,17 @@ function App() {
- 说明更改的目的和影响
- 关联相关的Issue(如果有)

### 代码规范

- **Go代码**: 遵循 `gofmt` 和 `golint` 标准
- **TypeScript/React代码**: 遵循 ESLint 和 Prettier 配置
- **提交信息**: 使用 [Conventional Commits](https://www.conventionalcommits.org/) 格式
- **测试**: 新功能必须包含相应的单元测试

### 开发环境设置

1. **后端开发**
```bash
go mod tidy
go run main.go
```

2. **前端开发**
```bash
cd ui/ModelModal
npm install
npm run dev
pnpm install
```

## 📄 许可证
Expand Down
4 changes: 4 additions & 0 deletions consts/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,7 @@ func ParseModelProvider(s string) ModelProvider {
return ModelProviderOther
}
}


var ApiKeyBalanceKeyWords = []string{"quota", "billing", "balance", "payment required"}

2 changes: 0 additions & 2 deletions test/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ func (p *ModelKit) GetModelList(c echo.Context) error {
Data: nil,
})
}
fmt.Println("list model req:", req)

resp, err := usecase.ModelList(c.Request().Context(), &req)
if err != nil {
Expand All @@ -65,7 +64,6 @@ func (p *ModelKit) CheckModel(c echo.Context) error {
Message: "参数绑定失败: " + err.Error(),
})
}
fmt.Println("check model req:", req)

resp, err := usecase.CheckModel(c.Request().Context(), &req)
if err != nil {
Expand Down
83 changes: 74 additions & 9 deletions ui/ModelModal/src/ModelModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,47 @@ export const ModelModal: React.FC<ModelModalProps> = ({
api_header: value.api_header || header,
})
.then((res) => {
if (res.error) {
messageHandler.error("获取模型失败 " + res.error);
// 替换host即可成功请求的情况, 替换host继续请求
if (res.error && res.error.includes("请将host替换为host.docker.internal")) {
// 解析base_url,将host替换为host.docker.internal
const url = new URL(value.base_url);
url.hostname = 'host.docker.internal';
value.base_url = url.toString();
modelService.listModel({
model_type,
api_key: value.api_key,
base_url: value.base_url,
provider: value.provider as Exclude<typeof value.provider, 'Other'>,
api_header: value.api_header || header,
}).then((res) => {
if (res.error) {
messageHandler.error("获取模型失败");
setModelLoading(false);
} else {
setModelUserList(
(res.models || [])
.filter((item): item is { model: string } => !!item.model)
.sort((a, b) => a.model!.localeCompare(b.model!))
);
if (
data &&
(res.models || []).find((it) => it.model === data.model_name)
) {
setValue('model_name', data.model_name!);
} else {
setValue('model_name', res.models?.[0]?.model || '');
}
setSuccess(true);
}
}).
finally(() => {
setModelLoading(false);
}).
catch((res) => {
setModelLoading(false);
});
} else if (res.error) {
messageHandler.error("获取模型失败");
setModelLoading(false);
} else {
setModelUserList(
Expand Down Expand Up @@ -181,10 +220,28 @@ export const ModelModal: React.FC<ModelModalProps> = ({
}
)
.then((res) => {
if (res.error) {
// 错误处理
if (res.error && res.error.includes("API地址末尾添加/v1, host替换为host.docker.internal")){
// 解析base_url,将host替换为host.docker.internal
const url = new URL(value.base_url);
url.hostname = 'host.docker.internal';
value.base_url = url.toString();
value.base_url = value.base_url + '/v1';
} else if (res.error && res.error.includes("请在API地址末尾添加/v1")) {
value.base_url = value.base_url + '/v1';
} else if (res.error && res.error.includes("请将host替换为host.docker.internal")) {
// 解析base_url,将host替换为host.docker.internal
const url = new URL(value.base_url);
url.hostname = 'host.docker.internal';
value.base_url = url.toString();
} else if (res.error) {
messageHandler.error("模型检查失败 " + res.error);
setLoading(false);
} else if (data) {
return;
}
// end

if (data) {
modelService.updateModel({
api_key: value.api_key,
model_type,
Expand All @@ -207,7 +264,7 @@ export const ModelModal: React.FC<ModelModalProps> = ({
})
.then((res) => {
if (res.error) {
messageHandler.error("修改模型失败 " + res.error);
messageHandler.error("修改模型失败");
setLoading(false);
} else {
messageHandler.success('修改成功');
Expand All @@ -218,6 +275,7 @@ export const ModelModal: React.FC<ModelModalProps> = ({
setLoading(false);
})
.catch((res) => {
messageHandler.error("修改模型失败");
setLoading(false);
});
} else {
Expand All @@ -241,7 +299,7 @@ export const ModelModal: React.FC<ModelModalProps> = ({
})
.then((res) => {
if (res.error) {
messageHandler.error("添加模型失败 " + res.error);
messageHandler.error("添加模型失败");
setLoading(false);
} else {
messageHandler.success('添加成功');
Expand All @@ -252,11 +310,13 @@ export const ModelModal: React.FC<ModelModalProps> = ({
setLoading(false);
})
.catch((res) => {
messageHandler.error("添加模型失败");
setLoading(false);
});
}
})
.catch((res) => {
messageHandler.error("检查模型失败");
setLoading(false);
});
};
Expand Down Expand Up @@ -514,14 +574,19 @@ export const ModelModal: React.FC<ModelModalProps> = ({
/>
)}
/>
{providerBrand === 'Other' && (
<Box sx={{ fontSize: 12, color: 'error.main', mt: 1 }}>
模型供应商必须支持与 OpenAI 兼容的 API 格式
</Box>
)}
<Stack
direction={'row'}
alignItems={'center'}
justifyContent={'space-between'}
sx={{ fontSize: 14, lineHeight: '32px', mt: 2 }}
>
<Box>
API Secret
API Key
{providers[providerBrand].secretRequired && (
<Box component={'span'} sx={{ color: 'red' }}>
{' '}
Expand All @@ -545,7 +610,7 @@ export const ModelModal: React.FC<ModelModalProps> = ({
)
}
>
查看文档
获取API Key
</Box>
)}
</Stack>
Expand All @@ -555,7 +620,7 @@ export const ModelModal: React.FC<ModelModalProps> = ({
rules={{
required: {
value: providers[providerBrand].secretRequired,
message: 'API Secret 不能为空',
message: 'API Key 不能为空',
},
}}
render={({ field }) => (
Expand Down
Loading