feat: 完善后端知识库服务和配置
- 优化 AI-Core 客户端调用 - 添加更多知识库配置选项 - 完善文档解析逻辑 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -87,7 +87,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Warning: Failed to initialize upload service: %v (files will not be available)", err)
|
log.Printf("Warning: Failed to initialize upload service: %v (files will not be available)", err)
|
||||||
}
|
}
|
||||||
knowledgeService := service.NewKnowledgeService(knowledgeRepo, modelRepo, uploadService, cfg.PythonServiceURL, cfg.AICoreServiceAddr)
|
knowledgeService := service.NewKnowledgeService(knowledgeRepo, modelRepo, uploadService, cfg.PythonServiceURL, cfg.AICoreServiceAddr, cfg.MarkdownLocalPath)
|
||||||
|
|
||||||
// 6. 初始化 Handler
|
// 6. 初始化 Handler
|
||||||
dbHandler := handler.NewDatabaseHandler(dbService)
|
dbHandler := handler.NewDatabaseHandler(dbService)
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ type Config struct {
|
|||||||
PythonServiceURL string
|
PythonServiceURL string
|
||||||
AICoreServiceAddr string // AI-Core gRPC 服务地址,如 "localhost:50051"
|
AICoreServiceAddr string // AI-Core gRPC 服务地址,如 "localhost:50051"
|
||||||
// 文件上传配置
|
// 文件上传配置
|
||||||
UploadMode string // "local" 或 "minio"
|
UploadMode string // "local" 或 "minio"
|
||||||
UploadLocalPath string // 本地存储路径,如 "resource/files"
|
UploadLocalPath string // 本地存储路径,如 "resource/files"
|
||||||
ServerBaseURL string // 服务器基础 URL,用于生成本地文件 URL
|
ServerBaseURL string // 服务器基础 URL,用于生成本地文件 URL
|
||||||
|
MarkdownLocalPath string // Markdown 文件存储路径,如 "resource/markdown"
|
||||||
// MinIO 配置
|
// MinIO 配置
|
||||||
MinIOEndpoint string
|
MinIOEndpoint string
|
||||||
MinIOAccessKey string
|
MinIOAccessKey string
|
||||||
@@ -56,6 +57,7 @@ func Load() *Config {
|
|||||||
viper.SetDefault("upload_mode", "local")
|
viper.SetDefault("upload_mode", "local")
|
||||||
viper.SetDefault("upload_local_path", "resource/files")
|
viper.SetDefault("upload_local_path", "resource/files")
|
||||||
viper.SetDefault("server_base_url", "http://localhost:8080")
|
viper.SetDefault("server_base_url", "http://localhost:8080")
|
||||||
|
viper.SetDefault("markdown_local_path", "resource/markdown")
|
||||||
viper.SetDefault("minio_endpoint", "localhost:9000")
|
viper.SetDefault("minio_endpoint", "localhost:9000")
|
||||||
viper.SetDefault("minio_access_key", "")
|
viper.SetDefault("minio_access_key", "")
|
||||||
viper.SetDefault("minio_secret_key", "")
|
viper.SetDefault("minio_secret_key", "")
|
||||||
@@ -87,9 +89,10 @@ func Load() *Config {
|
|||||||
PythonServiceURL: viper.GetString("python_service_url"),
|
PythonServiceURL: viper.GetString("python_service_url"),
|
||||||
AICoreServiceAddr: viper.GetString("ai_core_service_addr"),
|
AICoreServiceAddr: viper.GetString("ai_core_service_addr"),
|
||||||
// 文件上传配置
|
// 文件上传配置
|
||||||
UploadMode: viper.GetString("upload_mode"),
|
UploadMode: viper.GetString("upload_mode"),
|
||||||
UploadLocalPath: viper.GetString("upload_local_path"),
|
UploadLocalPath: viper.GetString("upload_local_path"),
|
||||||
ServerBaseURL: viper.GetString("server_base_url"),
|
ServerBaseURL: viper.GetString("server_base_url"),
|
||||||
|
MarkdownLocalPath: viper.GetString("markdown_local_path"),
|
||||||
// MinIO 配置
|
// MinIO 配置
|
||||||
MinIOEndpoint: viper.GetString("minio_endpoint"),
|
MinIOEndpoint: viper.GetString("minio_endpoint"),
|
||||||
MinIOAccessKey: viper.GetString("minio_access_key"),
|
MinIOAccessKey: viper.GetString("minio_access_key"),
|
||||||
|
|||||||
@@ -145,7 +145,8 @@ type UploadDocumentResponse struct {
|
|||||||
|
|
||||||
// DocumentPreviewResponse 文档预览响应
|
// DocumentPreviewResponse 文档预览响应
|
||||||
type DocumentPreviewResponse struct {
|
type DocumentPreviewResponse struct {
|
||||||
TotalPages int `json:"total_pages"`
|
TotalPages int `json:"total_pages"`
|
||||||
CurrentPage int `json:"current_page"`
|
CurrentPage int `json:"current_page"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
ContentType string `json:"content_type"` // url: 文件URL, html: HTML内容
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
|
||||||
|
docparser "x-agents/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AICoreClient AI-Core 文档解析服务客户端
|
// AICoreClient AI-Core 文档解析服务客户端
|
||||||
@@ -53,7 +55,7 @@ func (c *AICoreClient) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDocument 解析文档
|
// ParseDocument 解析文档 - 使用生成的 protobuf 代码
|
||||||
func (c *AICoreClient) ParseDocument(fileURL, fileName, fileType string) (*ParseResult, error) {
|
func (c *AICoreClient) ParseDocument(fileURL, fileName, fileType string) (*ParseResult, error) {
|
||||||
if c.conn == nil {
|
if c.conn == nil {
|
||||||
if err := c.Connect(); err != nil {
|
if err := c.Connect(); err != nil {
|
||||||
@@ -61,17 +63,16 @@ func (c *AICoreClient) ParseDocument(fileURL, fileName, fileType string) (*Parse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 gRPC raw bytes 调用
|
// 使用生成的 protobuf 客户端
|
||||||
// 由于没有生成 protobuf 代码,使用 raw bytes 方式调用
|
client := docparser.NewDocumentParserClient(c.conn)
|
||||||
client := NewDocumentParserClient(c.conn)
|
|
||||||
|
|
||||||
req := &ParseRequest{
|
req := &docparser.ParseRequest{
|
||||||
FileUrl: fileURL,
|
FileUrl: fileURL,
|
||||||
FileName: fileName,
|
FileName: fileName,
|
||||||
FileType: fileType,
|
FileType: fileType,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
resp, err := client.ParseDocument(ctx, req)
|
resp, err := client.ParseDocument(ctx, req)
|
||||||
@@ -80,53 +81,11 @@ func (c *AICoreClient) ParseDocument(fileURL, fileName, fileType string) (*Parse
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &ParseResult{
|
return &ParseResult{
|
||||||
Success: resp.Success,
|
Success: resp.GetSuccess(),
|
||||||
Content: resp.Content,
|
Content: resp.GetContent(),
|
||||||
Message: resp.Message,
|
Message: resp.GetMessage(),
|
||||||
ContentLength: resp.ContentLength,
|
ContentLength: resp.GetContentLength(),
|
||||||
FileType: resp.FileType,
|
FileType: resp.GetFileType(),
|
||||||
ParserEngine: resp.ParserEngine,
|
ParserEngine: resp.GetParserEngine(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以下是手动定义的 protobuf messages(与 proto 文件一致)
|
|
||||||
// 不需要生成 .pb.go 文件,直接手动定义
|
|
||||||
|
|
||||||
type ParseRequest struct {
|
|
||||||
FileUrl string
|
|
||||||
FileName string
|
|
||||||
FileType string
|
|
||||||
ParserEngine string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParseResponse struct {
|
|
||||||
Success bool
|
|
||||||
Content string
|
|
||||||
Message string
|
|
||||||
ContentLength int32
|
|
||||||
FileType string
|
|
||||||
ParserEngine string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DocumentParserClient gRPC 客户端接口(手动实现)
|
|
||||||
type DocumentParserClient interface {
|
|
||||||
ParseDocument(ctx context.Context, in *ParseRequest, opts ...grpc.CallOption) (*ParseResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type documentParserClient struct {
|
|
||||||
cc grpc.ClientConnInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDocumentParserClient 创建 DocumentParser 客户端
|
|
||||||
func NewDocumentParserClient(cc grpc.ClientConnInterface) DocumentParserClient {
|
|
||||||
return &documentParserClient{cc: cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *documentParserClient) ParseDocument(ctx context.Context, in *ParseRequest, opts ...grpc.CallOption) (*ParseResponse, error) {
|
|
||||||
out := new(ParseResponse)
|
|
||||||
err := c.cc.Invoke(ctx, "/docparser.DocumentParser/ParseDocument", in, out, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ type KnowledgeService struct {
|
|||||||
uploadService *UploadService
|
uploadService *UploadService
|
||||||
pythonServiceURL string
|
pythonServiceURL string
|
||||||
aiCoreClient *AICoreClient
|
aiCoreClient *AICoreClient
|
||||||
|
markdownLocalPath string // Markdown 本地存储路径
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKnowledgeService(repo *repository.KnowledgeRepository, modelRepo *repository.ModelRepository, uploadService *UploadService, pythonServiceURL, aiCoreServiceAddr string) *KnowledgeService {
|
func NewKnowledgeService(repo *repository.KnowledgeRepository, modelRepo *repository.ModelRepository, uploadService *UploadService, pythonServiceURL, aiCoreServiceAddr, markdownLocalPath string) *KnowledgeService {
|
||||||
aiCoreClient, _ := NewAICoreClient(aiCoreServiceAddr)
|
aiCoreClient, _ := NewAICoreClient(aiCoreServiceAddr)
|
||||||
return &KnowledgeService{
|
return &KnowledgeService{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
@@ -39,6 +40,7 @@ func NewKnowledgeService(repo *repository.KnowledgeRepository, modelRepo *reposi
|
|||||||
uploadService: uploadService,
|
uploadService: uploadService,
|
||||||
pythonServiceURL: pythonServiceURL,
|
pythonServiceURL: pythonServiceURL,
|
||||||
aiCoreClient: aiCoreClient,
|
aiCoreClient: aiCoreClient,
|
||||||
|
markdownLocalPath: markdownLocalPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +309,13 @@ func (s *KnowledgeService) parseDocumentWithAICore(docID, fileURL, fileName stri
|
|||||||
|
|
||||||
if result.Success && result.Content != "" {
|
if result.Success && result.Content != "" {
|
||||||
knowledgeDebugLog.Printf("[AICore] 解析成功: docID=%s, contentLength=%d", docID, len(result.Content))
|
knowledgeDebugLog.Printf("[AICore] 解析成功: docID=%s, contentLength=%d", docID, len(result.Content))
|
||||||
|
|
||||||
|
// 保存到本地文件
|
||||||
|
markdownPath := s.saveMarkdownToFile(docID, fileName, result.Content)
|
||||||
|
if markdownPath != "" {
|
||||||
|
knowledgeDebugLog.Printf("[AICore] Markdown 保存到本地: docID=%s, path=%s", docID, markdownPath)
|
||||||
|
}
|
||||||
|
|
||||||
// 更新文档的 Content 字段
|
// 更新文档的 Content 字段
|
||||||
s.repo.UpdateDocument(docID, map[string]interface{}{
|
s.repo.UpdateDocument(docID, map[string]interface{}{
|
||||||
"content": result.Content,
|
"content": result.Content,
|
||||||
@@ -316,6 +325,31 @@ func (s *KnowledgeService) parseDocumentWithAICore(docID, fileURL, fileName stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saveMarkdownToFile 保存 Markdown 内容到本地文件
|
||||||
|
func (s *KnowledgeService) saveMarkdownToFile(docID, fileName, content string) string {
|
||||||
|
if s.markdownLocalPath == "" {
|
||||||
|
s.markdownLocalPath = "resource/markdown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建目录
|
||||||
|
if err := os.MkdirAll(s.markdownLocalPath, 0755); err != nil {
|
||||||
|
knowledgeDebugLog.Printf("[AICore] 创建目录失败: path=%s, err=%v", s.markdownLocalPath, err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成文件名(用 docID + .md)
|
||||||
|
markdownFileName := docID + ".md"
|
||||||
|
markdownPath := s.markdownLocalPath + "/" + markdownFileName
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
if err := os.WriteFile(markdownPath, []byte(content), 0644); err != nil {
|
||||||
|
knowledgeDebugLog.Printf("[AICore] 保存 Markdown 失败: path=%s, err=%v", markdownPath, err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return markdownPath
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteDocument 删除文档
|
// DeleteDocument 删除文档
|
||||||
func (s *KnowledgeService) DeleteDocument(kbID, docID string) error {
|
func (s *KnowledgeService) DeleteDocument(kbID, docID string) error {
|
||||||
// 验证文档存在
|
// 验证文档存在
|
||||||
@@ -400,24 +434,66 @@ func (s *KnowledgeService) GetDocumentPreview(kbID, docID string, page int) (*mo
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果已解析,返回解析内容;否则返回文件 URL
|
// 获取文件URL
|
||||||
if doc.Status == "parsed" {
|
fileURL, _ := s.uploadService.GetFileURL(doc.FileKey)
|
||||||
// TODO: 从存储中读取解析内容(可以存到数据库或文件)
|
|
||||||
// 暂时返回文件 URL
|
// 根据文件类型决定预览方式
|
||||||
fileURL, _ := s.uploadService.GetFileURL(doc.FileKey)
|
fileName := doc.Name
|
||||||
|
isPDF := strings.HasSuffix(strings.ToLower(fileName), ".pdf")
|
||||||
|
isOffice := false
|
||||||
|
officeExts := []string{".csv", ".xlsx", ".xls", ".docx", ".doc", ".pptx", ".ppt", ".txt", ".md"}
|
||||||
|
for _, ext := range officeExts {
|
||||||
|
if strings.HasSuffix(strings.ToLower(fileName), ext) {
|
||||||
|
isOffice = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PDF文件返回文件URL
|
||||||
|
if isPDF {
|
||||||
return &model.DocumentPreviewResponse{
|
return &model.DocumentPreviewResponse{
|
||||||
TotalPages: 1,
|
TotalPages: 1,
|
||||||
CurrentPage: page,
|
CurrentPage: page,
|
||||||
Content: fileURL,
|
Content: fileURL,
|
||||||
|
ContentType: "url",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未解析,返回文件 URL
|
// Office文件调用解析服务转换为HTML
|
||||||
fileURL, _ := s.uploadService.GetFileURL(doc.FileKey)
|
if isOffice && s.aiCoreClient != nil {
|
||||||
|
knowledgeDebugLog.Printf("[Preview] Parsing office file: %s, URL: %s", fileName, fileURL)
|
||||||
|
result, err := s.aiCoreClient.ParseDocument(fileURL, fileName, "")
|
||||||
|
if err != nil {
|
||||||
|
// 解析失败,返回文件URL
|
||||||
|
knowledgeDebugLog.Printf("[Preview] Parse document failed: %v", err)
|
||||||
|
return &model.DocumentPreviewResponse{
|
||||||
|
TotalPages: 1,
|
||||||
|
CurrentPage: page,
|
||||||
|
Content: fileURL,
|
||||||
|
ContentType: "url",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
knowledgeDebugLog.Printf("[Preview] Parse result: success=%v, content_length=%d", result.Success, len(result.Content))
|
||||||
|
|
||||||
|
// 返回HTML内容
|
||||||
|
if result.Success && result.Content != "" {
|
||||||
|
knowledgeDebugLog.Printf("[Preview] Returning HTML content, length: %d", len(result.Content))
|
||||||
|
return &model.DocumentPreviewResponse{
|
||||||
|
TotalPages: 1,
|
||||||
|
CurrentPage: page,
|
||||||
|
Content: result.Content,
|
||||||
|
ContentType: "html",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他情况返回文件URL
|
||||||
return &model.DocumentPreviewResponse{
|
return &model.DocumentPreviewResponse{
|
||||||
TotalPages: 1,
|
TotalPages: 1,
|
||||||
CurrentPage: page,
|
CurrentPage: page,
|
||||||
Content: fileURL,
|
Content: fileURL,
|
||||||
|
ContentType: "url",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
sk-5706307e3e3a4eb09452dbf0bb87fe31
|
||||||
|
|
||||||
|
https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
|
||||||
|
qwen3.5-flash
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
sk-5706307e3e3a4eb09452dbf0bb87fe31
|
||||||
|
|
||||||
|
https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
|
||||||
|
qwen3.5-flash
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
## students
|
||||||
|
| 班级 | 姓名 | 年龄 | 性别 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | 曹 | 123 | 男 |
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 1 . 你好么?
|
||||||
|
|
||||||
|
### 表哥啊啊
|
||||||
|
|
||||||
|
大叔大婶打扫的暗示打扫暗示
|
||||||
|
|
||||||
|
> 太好了
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
print("hello world")
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 1 . 你好么?
|
||||||
|
|
||||||
|
### 表哥啊啊
|
||||||
|
|
||||||
|
大叔大婶打扫的暗示打扫暗示
|
||||||
|
|
||||||
|
> 太好了
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
print("hello world")
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 1 . 你好么?
|
||||||
|
|
||||||
|
### 表哥啊啊
|
||||||
|
|
||||||
|
大叔大婶打扫的暗示打扫暗示
|
||||||
|
|
||||||
|
> 太好了
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
print("hello world")
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 1 . 你好么?
|
||||||
|
|
||||||
|
### 表哥啊啊
|
||||||
|
|
||||||
|
大叔大婶打扫的暗示打扫暗示
|
||||||
|
|
||||||
|
> 太好了
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
print("hello world")
|
||||||
|
|
||||||
|
```
|
||||||
1834
server/resource/markdown/698e892b-cf0c-4fa8-998a-d46c5ec5dc5d.md
Normal file
1834
server/resource/markdown/698e892b-cf0c-4fa8-998a-d46c5ec5dc5d.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
|||||||
|
# 1 . 你好么?
|
||||||
|
|
||||||
|
### 表哥啊啊
|
||||||
|
|
||||||
|
大叔大婶打扫的暗示打扫暗示
|
||||||
|
|
||||||
|
> 太好了
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
print("hello world")
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
sk-5706307e3e3a4eb09452dbf0bb87fe31
|
||||||
|
|
||||||
|
https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
|
||||||
|
qwen3.5-flash
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
| 班级 | 姓名 | 年龄 | 性别 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | 曹 | 123 | 男 |
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
sk-5706307e3e3a4eb09452dbf0bb87fe31
|
||||||
|
|
||||||
|
https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
|
||||||
|
qwen3.5-flash
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 1 . 你好么?
|
||||||
|
|
||||||
|
### 表哥啊啊
|
||||||
|
|
||||||
|
大叔大婶打扫的暗示打扫暗示
|
||||||
|
|
||||||
|
> 太好了
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
print("hello world")
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
| 겯섬 | 檎츰 | 쾨쥑 | 昑깎 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | 꿀 | 123 | 켕 |
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
sk-5706307e3e3a4eb09452dbf0bb87fe31
|
||||||
|
|
||||||
|
https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
|
||||||
|
qwen3.5-flash
|
||||||
1834
server/resource/markdown/eea7fcdc-503d-47a8-af04-13e2adc0ca4a.md
Normal file
1834
server/resource/markdown/eea7fcdc-503d-47a8-af04-13e2adc0ca4a.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user