Skip to content

Commit 35e0f0f

Browse files
author
Working On It
committed
fix: Switch to the official Spark SDK
1 parent 00f07e1 commit 35e0f0f

File tree

15 files changed

+188
-28
lines changed

15 files changed

+188
-28
lines changed

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ require (
5454
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/hunyuan v1.0.1074
5555
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tbaas v1.0.1115
5656
github.com/ua-parser/uap-go v0.0.0-20230823213814-f77b3e91e9dc
57-
github.com/vogo/xfspark v0.1.2
5857
github.com/volcengine/volcengine-go-sdk v1.0.141
5958
github.com/wangbin/jiebago v0.3.2
6059
golang.org/x/net v0.38.0
@@ -87,6 +86,7 @@ require (
8786
github.com/hhrutter/pkcs7 v0.2.0 // indirect
8887
github.com/hhrutter/tiff v1.0.2 // indirect
8988
github.com/holiman/uint256 v1.3.2 // indirect
89+
github.com/iflytek/spark-ai-go v0.0.0-20240509090842-11decd0816f6 // indirect
9090
github.com/mattn/go-runewidth v0.0.16 // indirect
9191
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
9292
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
@@ -95,6 +95,7 @@ require (
9595
github.com/supranational/blst v0.3.14 // indirect
9696
github.com/tidwall/sjson v1.2.5 // indirect
9797
github.com/xlab/treeprint v1.2.0 // indirect
98+
go.uber.org/zap v1.27.0 // indirect
9899
go.yaml.in/yaml/v2 v2.4.2 // indirect
99100
go.yaml.in/yaml/v3 v3.0.3 // indirect
100101
golang.org/x/image v0.27.0 // indirect
@@ -250,7 +251,7 @@ require (
250251
go.opentelemetry.io/otel/trace v1.35.0 // indirect
251252
go.uber.org/atomic v1.9.0 // indirect
252253
go.uber.org/mock v0.4.0 // indirect
253-
go.uber.org/multierr v1.9.0 // indirect
254+
go.uber.org/multierr v1.10.0 // indirect
254255
golang.org/x/crypto v0.38.0 // indirect
255256
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
256257
golang.org/x/mod v0.22.0 // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,8 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
491491
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
492492
github.com/hupe1980/go-huggingface v0.0.15 h1:tTWmUGGunC/BYz4hrwS8SSVtMYVYjceG2uhL8HxeXvw=
493493
github.com/hupe1980/go-huggingface v0.0.15/go.mod h1:IRvsik3+b9BJyw9hCfw1arI6gDObcVto1UA8f3kt8mM=
494+
github.com/iflytek/spark-ai-go v0.0.0-20240509090842-11decd0816f6 h1:R1/gPjO0VeJ+12x/wWd2J59zNLCRmz6hay/PV0bqvk0=
495+
github.com/iflytek/spark-ai-go v0.0.0-20240509090842-11decd0816f6/go.mod h1:2w534as5XquG0kmDy4Fv6t4vtPrH75BlFHk1coKxJr4=
494496
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
495497
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
496498
github.com/imroc/req/v3 v3.35.1 h1:xrsuKq4FHWqxU2DM8lWbTwmxiObGfR7B/zgEBMmy8Wc=
@@ -971,8 +973,6 @@ github.com/vogo/gorun v1.1.0 h1:i/8HhmNyjMa79bFn1ZHGTO3KyiWQcZPBsFGpvgkcap8=
971973
github.com/vogo/gorun v1.1.0/go.mod h1:MyOjF/DbZSz40GQ5657Ou9NEfNlUbMhvpXjDvh4L8OY=
972974
github.com/vogo/logger v1.5.1 h1:voyVY69TpM/x1lml4LYy4jMe5z0kDh5jW1oatcikajM=
973975
github.com/vogo/logger v1.5.1/go.mod h1:9U+qupncHpWpt4ptlxFm9bvDb9EjbGIA+cY/tYtW4Kg=
974-
github.com/vogo/xfspark v0.1.2 h1:QdW7jvFL6bgHQ74xTuAlP2IBj4sIVCpBE/LS0LW4ePE=
975-
github.com/vogo/xfspark v0.1.2/go.mod h1:KHvAxw98fpLsFdmFYm9ps99eGaLbuLBTBa9rVG6SFyo=
976976
github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8=
977977
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
978978
github.com/volcengine/volcengine-go-sdk v1.0.141 h1:Bl5k1BR04YKPUVCuhqMPmTd2Ws317+YNF6QIXxdgO5k=
@@ -1040,10 +1040,14 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+
10401040
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
10411041
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
10421042
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
1043+
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
1044+
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
10431045
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
10441046
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
10451047
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
10461048
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
1049+
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
1050+
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
10471051
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
10481052
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
10491053
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=

model/iflytek.go

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,33 @@
1515
package model
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"io"
2021
"net/http"
2122
"strings"
2223

23-
iflytek "github.com/vogo/xfspark/chat"
24+
"github.com/iflytek/spark-ai-go/sparkai/llms/spark"
25+
"github.com/iflytek/spark-ai-go/sparkai/llms/spark/client/sparkclient"
26+
"github.com/iflytek/spark-ai-go/sparkai/messages"
2427
)
2528

2629
type iFlytekModelProvider struct {
2730
subType string
2831
appID string
2932
apiKey string
3033
secretKey string
31-
temperature string
34+
temperature float32
3235
topK int
3336
}
3437

35-
func NewiFlytekModelProvider(subType string, secretKey string, temperature float32, topK int) (*iFlytekModelProvider, error) {
38+
func NewiFlytekModelProvider(subType string, secretKey string, apiKey string, appId string, temperature float32, topK int) (*iFlytekModelProvider, error) {
3639
p := &iFlytekModelProvider{
3740
subType: subType,
38-
appID: "",
39-
apiKey: "",
41+
appID: appId,
42+
apiKey: apiKey,
4043
secretKey: secretKey,
41-
temperature: fmt.Sprintf("%f", temperature),
44+
temperature: temperature,
4245
topK: topK,
4346
}
4447
return p, nil
@@ -101,7 +104,7 @@ func (p *iFlytekModelProvider) calculatePrice(modelResult *ModelResult) error {
101104
modelResult.Currency = "CNY"
102105

103106
switch p.subType {
104-
case "spark-ultra":
107+
case "spark4.0-ultra":
105108
if tokenCount <= 3000000 {
106109
price = float64(tokenCount) / 10000 * 0.70
107110
} else if tokenCount <= 15000000 {
@@ -182,7 +185,13 @@ func (p *iFlytekModelProvider) calculatePrice(modelResult *ModelResult) error {
182185
}
183186

184187
func (p *iFlytekModelProvider) QueryText(question string, writer io.Writer, history []*RawMessage, prompt string, knowledgeMessages []*RawMessage, agentInfo *AgentInfo) (*ModelResult, error) {
185-
client := iflytek.NewServer(p.appID, p.apiKey, p.secretKey)
188+
baseUrl, domain, err := p.getBaseUrl()
189+
_, client, err := spark.NewClient(spark.WithBaseURL(baseUrl), spark.WithApiKey(p.apiKey), spark.WithApiSecret(p.secretKey), spark.WithAppId(p.appID), spark.WithAPIDomain(domain))
190+
if err != nil {
191+
return nil, err
192+
}
193+
ctx := context.Background()
194+
186195
flusher, ok := writer.(http.Flusher)
187196
if !ok {
188197
return nil, fmt.Errorf("writer does not implement http.Flusher")
@@ -199,19 +208,11 @@ func (p *iFlytekModelProvider) QueryText(question string, writer io.Writer, hist
199208
}
200209
}
201210

202-
session, err := client.GetSession("1")
203-
if err != nil {
204-
return nil, fmt.Errorf("iflytek get session error: %v", err)
205-
}
206-
if session == nil {
207-
return nil, fmt.Errorf("iflytek get session error: session is nil")
208-
}
211+
chatMessages := p.getChatMessages(question, history)
209212

210-
session.Req.Parameter.Chat.Temperature = p.temperature
211-
session.Req.Parameter.Chat.TopK = p.topK
212-
response, err := session.Send(question)
213-
if err != nil {
214-
return nil, fmt.Errorf("iflytek send error: %v", err)
213+
r := &sparkclient.ChatRequest{
214+
Domain: &domain,
215+
Messages: chatMessages,
215216
}
216217

217218
flushData := func(data string) error {
@@ -222,7 +223,17 @@ func (p *iFlytekModelProvider) QueryText(question string, writer io.Writer, hist
222223
return nil
223224
}
224225

225-
err = flushData(response)
226+
response := ""
227+
228+
_, err = client.CreateChatWithCallBack(ctx, r, func(msg messages.ChatMessage) error {
229+
content := msg.GetContent()
230+
response += content
231+
err = flushData(content)
232+
if err != nil {
233+
return err
234+
}
235+
return nil
236+
})
226237
if err != nil {
227238
return nil, err
228239
}
@@ -239,3 +250,44 @@ func (p *iFlytekModelProvider) QueryText(question string, writer io.Writer, hist
239250

240251
return modelResult, nil
241252
}
253+
254+
func (p *iFlytekModelProvider) getChatMessages(question string, history []*RawMessage) []messages.ChatMessage {
255+
var result []messages.ChatMessage
256+
257+
for i := len(history) - 1; i >= 0; i-- {
258+
msg := history[i]
259+
role := "user"
260+
if msg.Author == "AI" {
261+
role = "assistant"
262+
}
263+
result = append(result, &messages.GenericChatMessage{
264+
Role: role,
265+
Content: msg.Text,
266+
})
267+
}
268+
269+
result = append(result, &messages.GenericChatMessage{
270+
Role: "user",
271+
Content: question,
272+
})
273+
274+
return result
275+
}
276+
277+
func (p *iFlytekModelProvider) getBaseUrl() (string, string, error) {
278+
if p.subType == "spark4.0-ultra" {
279+
return "wss://spark-api.xf-yun.com/v4.0/chat", "4.0Ultra", nil
280+
} else if p.subType == "spark-max-32k" {
281+
return "wss://spark-api.xf-yun.com/chat/max-32k", "max-32k", nil
282+
} else if p.subType == "spark-max" {
283+
return "wss://spark-api.xf-yun.com/v3.5/chat", "generalv3.5", nil
284+
} else if p.subType == "spark-pro-128k" {
285+
return "wss://spark-api.xf-yun.com/chat/pro-128k", "pro-128k", nil
286+
} else if p.subType == "spark-pro" {
287+
return "wss://spark-api.xf-yun.com/v3.1/chat", "generalv3", nil
288+
} else if p.subType == "spark-lite" {
289+
return "wss://spark-api.xf-yun.com/v1.1/chat", "lite", nil
290+
} else {
291+
return "", "", fmt.Errorf("chat model not found")
292+
}
293+
}

model/provider.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type ModelProvider interface {
4040
QueryText(question string, writer io.Writer, history []*RawMessage, prompt string, knowledgeMessages []*RawMessage, agentInfo *AgentInfo) (*ModelResult, error)
4141
}
4242

43-
func GetModelProvider(typ string, subType string, clientId string, clientSecret string, temperature float32, topP float32, topK int, frequencyPenalty float32, presencePenalty float32, providerUrl string, apiVersion string, compatibleProvider string, inputPricePerThousandTokens float64, outputPricePerThousandTokens float64, Currency string, enableThinking bool) (ModelProvider, error) {
43+
func GetModelProvider(typ string, subType string, clientId string, clientSecret string, apiKey string, temperature float32, topP float32, topK int, frequencyPenalty float32, presencePenalty float32, providerUrl string, apiVersion string, compatibleProvider string, inputPricePerThousandTokens float64, outputPricePerThousandTokens float64, Currency string, enableThinking bool) (ModelProvider, error) {
4444
var p ModelProvider
4545
var err error
4646
if typ == "Ollama" {
@@ -64,7 +64,7 @@ func GetModelProvider(typ string, subType string, clientId string, clientSecret
6464
} else if typ == "Baidu Cloud" {
6565
p, err = NewBaiduCloudModelProvider(subType, clientSecret, temperature, topP)
6666
} else if typ == "iFlytek" {
67-
p, err = NewiFlytekModelProvider(subType, clientSecret, temperature, topK)
67+
p, err = NewiFlytekModelProvider(subType, clientSecret, apiKey, clientId, temperature, topK)
6868
} else if typ == "ChatGLM" {
6969
p, err = NewChatGLMModelProvider(subType, clientSecret)
7070
} else if typ == "MiniMax" {

object/provider.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Provider struct {
4141
Flavor string `xorm:"varchar(100)" json:"flavor"`
4242
ClientId string `xorm:"varchar(100)" json:"clientId"`
4343
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
44+
ApiKey string `xorm:"varchar(2000)" json:"apiKey"`
4445
Region string `xorm:"varchar(100)" json:"region"`
4546
ProviderKey string `xorm:"varchar(100)" json:"providerKey"`
4647
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
@@ -294,7 +295,7 @@ func (p *Provider) GetStorageProviderObj() (storage.StorageProvider, error) {
294295
}
295296

296297
func (p *Provider) GetModelProvider() (model.ModelProvider, error) {
297-
pProvider, err := model.GetModelProvider(p.Type, p.SubType, p.ClientId, p.ClientSecret, p.Temperature, p.TopP, p.TopK, p.FrequencyPenalty, p.PresencePenalty, p.ProviderUrl, p.ApiVersion, p.CompatibleProvider, p.InputPricePerThousandTokens, p.OutputPricePerThousandTokens, p.Currency, p.EnableThinking)
298+
pProvider, err := model.GetModelProvider(p.Type, p.SubType, p.ClientId, p.ClientSecret, p.ApiKey, p.Temperature, p.TopP, p.TopK, p.FrequencyPenalty, p.PresencePenalty, p.ProviderUrl, p.ApiVersion, p.CompatibleProvider, p.InputPricePerThousandTokens, p.OutputPricePerThousandTokens, p.Currency, p.EnableThinking)
298299
if err != nil {
299300
return nil, err
300301
}

web/src/ProviderEditPage.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ class ProviderEditPage extends React.Component {
119119
} else if (provider.category === "Model") {
120120
if (provider.type === "Baidu Cloud" || provider.type === "Tencent Cloud") {
121121
return Setting.getLabel(i18next.t("provider:API key"), i18next.t("provider:API key - Tooltip"));
122+
} else if (provider.type === "iFlytek") {
123+
return Setting.getLabel(i18next.t("provider:APISecret"), i18next.t("provider:APISecret - Tooltip"));
122124
}
123125
} else if (provider.category === "Blockchain") {
124126
if (provider.type === "Ethereum") {
@@ -128,6 +130,24 @@ class ProviderEditPage extends React.Component {
128130
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
129131
}
130132

133+
getApiKeyLabel(provider) {
134+
if (provider.category === "Model") {
135+
if (provider.type === "iFlytek") {
136+
return Setting.getLabel(i18next.t("provider:APIKey"), i18next.t("provider:APIKey - Tooltip"));
137+
}
138+
}
139+
return Setting.getLabel(i18next.t("provider:APIKey"), i18next.t("provider:APIKey - Tooltip"));
140+
}
141+
142+
getAppIdLabel(provider) {
143+
if (provider.category === "Model") {
144+
if (provider.type === "iFlytek") {
145+
return Setting.getLabel(i18next.t("provider:APPID"), i18next.t("provider:APPID - Tooltip"));
146+
}
147+
}
148+
return Setting.getLabel(i18next.t("provider:APPID"), i18next.t("provider:APPID - Tooltip"));
149+
}
150+
131151
getContractNameLabel(provider) {
132152
if (provider.category === "Blockchain") {
133153
if (provider.type === "Ethereum") {
@@ -628,6 +648,34 @@ class ProviderEditPage extends React.Component {
628648
</Row>
629649
)
630650
}
651+
{
652+
(this.state.provider.type === "iFlytek" && this.state.provider.category === "Model") && (
653+
<Row style={{marginTop: "20px"}} >
654+
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
655+
{this.getApiKeyLabel(this.state.provider)} :
656+
</Col>
657+
<Col span={22} >
658+
<Input value={this.state.provider.apiKey} onChange={e => {
659+
this.updateProviderField("apiKey", e.target.value);
660+
}} />
661+
</Col>
662+
</Row>
663+
)
664+
}
665+
{
666+
(this.state.provider.type === "iFlytek" && this.state.provider.category === "Model") && (
667+
<Row style={{marginTop: "20px"}} >
668+
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
669+
{this.getAppIdLabel(this.state.provider)} :
670+
</Col>
671+
<Col span={22} >
672+
<Input value={this.state.provider.clientId} onChange={e => {
673+
this.updateProviderField("clientId", e.target.value);
674+
}} />
675+
</Col>
676+
</Row>
677+
)
678+
}
631679
{
632680
(this.state.provider.category === "Model" && this.state.provider.type === "Claude" && Setting.getThinkingModelMaxTokens(this.state.provider.subType) !== 0) ? (
633681
<>

web/src/locales/de/data.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,12 @@
444444
"Add Storage Provider": "Speicheranbieter hinzufügen",
445445
"Auth type": "Authentifizierungstyp",
446446
"Auth type - Tooltip": "Authentifizierungstyp",
447+
"APIKey": "APIKey",
448+
"APIKey - Tooltip": "APIKey",
449+
"APISecret": "APISecret",
450+
"APISecret - Tooltip": "APISecret",
451+
"APPID": "APPID",
452+
"APPID - Tooltip": "APPID - Tooltip",
447453
"Browser URL": "Browser-URL",
448454
"Browser URL - Tooltip": "Blockchain-Browser-URL",
449455
"Category": "Kategorie",

web/src/locales/en/data.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,12 @@
444444
"Add Storage Provider": "Add Storage Provider",
445445
"Auth type": "Auth type",
446446
"Auth type - Tooltip": "Authentication type",
447+
"APIKey": "APIKey",
448+
"APIKey - Tooltip": "APIKey",
449+
"APISecret": "APISecret",
450+
"APISecret - Tooltip": "APISecret",
451+
"APPID": "APPID",
452+
"APPID - Tooltip": "APPID - Tooltip",
447453
"Browser URL": "Browser URL",
448454
"Browser URL - Tooltip": "Blockchain explorer URL",
449455
"Category": "Category",

web/src/locales/es/data.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,12 @@
444444
"Add Storage Provider": "Agregar proveedor de almacenamiento",
445445
"Auth type": "Tipo de autenticación",
446446
"Auth type - Tooltip": "Tipo de autenticación",
447+
"APIKey": "APIKey",
448+
"APIKey - Tooltip": "APIKey",
449+
"APISecret": "APISecret",
450+
"APISecret - Tooltip": "APISecret",
451+
"APPID": "APPID",
452+
"APPID - Tooltip": "APPID - Tooltip",
447453
"Browser URL": "URL del navegador",
448454
"Browser URL - Tooltip": "URL del navegador blockchain",
449455
"Category": "Categoría",

web/src/locales/fr/data.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,12 @@
444444
"Add Storage Provider": "Ajouter un fournisseur de stockage",
445445
"Auth type": "Type d'authentification",
446446
"Auth type - Tooltip": "Type d'authentification",
447+
"APIKey": "APIKey",
448+
"APIKey - Tooltip": "APIKey",
449+
"APISecret": "APISecret",
450+
"APISecret - Tooltip": "APISecret",
451+
"APPID": "APPID",
452+
"APPID - Tooltip": "APPID - Tooltip",
447453
"Browser URL": "URL du navigateur",
448454
"Browser URL - Tooltip": "URL du navigateur blockchain",
449455
"Category": "Catégorie",

0 commit comments

Comments
 (0)