@@ -0,0 +1,61 @@ | |||
package controllers | |||
import ( | |||
"encoding/json" | |||
"github.com/casbin/casbase/object" | |||
) | |||
func (c *ApiController) GetGlobalVectorsets() { | |||
c.Data["json"] = object.GetGlobalVectorsets() | |||
c.ServeJSON() | |||
} | |||
func (c *ApiController) GetVectorsets() { | |||
owner := c.Input().Get("owner") | |||
c.Data["json"] = object.GetVectorsets(owner) | |||
c.ServeJSON() | |||
} | |||
func (c *ApiController) GetVectorset() { | |||
id := c.Input().Get("id") | |||
c.Data["json"] = object.GetVectorset(id) | |||
c.ServeJSON() | |||
} | |||
func (c *ApiController) UpdateVectorset() { | |||
id := c.Input().Get("id") | |||
var vectorset object.Vectorset | |||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &vectorset) | |||
if err != nil { | |||
panic(err) | |||
} | |||
c.Data["json"] = object.UpdateVectorset(id, &vectorset) | |||
c.ServeJSON() | |||
} | |||
func (c *ApiController) AddVectorset() { | |||
var vectorset object.Vectorset | |||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &vectorset) | |||
if err != nil { | |||
panic(err) | |||
} | |||
c.Data["json"] = object.AddVectorset(&vectorset) | |||
c.ServeJSON() | |||
} | |||
func (c *ApiController) DeleteVectorset() { | |||
var vectorset object.Vectorset | |||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &vectorset) | |||
if err != nil { | |||
panic(err) | |||
} | |||
c.Data["json"] = object.DeleteVectorset(&vectorset) | |||
c.ServeJSON() | |||
} |
@@ -89,4 +89,9 @@ func (a *Adapter) createTable() { | |||
if err != nil { | |||
panic(err) | |||
} | |||
err = a.engine.Sync2(new(Vectorset)) | |||
if err != nil { | |||
panic(err) | |||
} | |||
} |
@@ -0,0 +1,96 @@ | |||
package object | |||
import ( | |||
"fmt" | |||
"github.com/casbin/casbase/util" | |||
"xorm.io/core" | |||
) | |||
type Vectorset struct { | |||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` | |||
Name string `xorm:"varchar(100) notnull pk" json:"name"` | |||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` | |||
DisplayName string `xorm:"varchar(100)" json:"displayName"` | |||
Url string `xorm:"varchar(100)" json:"url"` | |||
Dimension int `json:"dimension"` | |||
Vectors []*Vector `xorm:"mediumtext" json:"vectors"` | |||
} | |||
func GetGlobalVectorsets() []*Vectorset { | |||
vectorsets := []*Vectorset{} | |||
err := adapter.engine.Asc("owner").Desc("created_time").Find(&vectorsets) | |||
if err != nil { | |||
panic(err) | |||
} | |||
return vectorsets | |||
} | |||
func GetVectorsets(owner string) []*Vectorset { | |||
vectorsets := []*Vectorset{} | |||
err := adapter.engine.Desc("created_time").Find(&vectorsets, &Vectorset{Owner: owner}) | |||
if err != nil { | |||
panic(err) | |||
} | |||
return vectorsets | |||
} | |||
func getVectorset(owner string, name string) *Vectorset { | |||
vectorset := Vectorset{Owner: owner, Name: name} | |||
existed, err := adapter.engine.Get(&vectorset) | |||
if err != nil { | |||
panic(err) | |||
} | |||
if existed { | |||
return &vectorset | |||
} else { | |||
return nil | |||
} | |||
} | |||
func GetVectorset(id string) *Vectorset { | |||
owner, name := util.GetOwnerAndNameFromId(id) | |||
return getVectorset(owner, name) | |||
} | |||
func UpdateVectorset(id string, vectorset *Vectorset) bool { | |||
owner, name := util.GetOwnerAndNameFromId(id) | |||
if getVectorset(owner, name) == nil { | |||
return false | |||
} | |||
_, err := adapter.engine.ID(core.PK{owner, name}).AllCols().Update(vectorset) | |||
if err != nil { | |||
panic(err) | |||
} | |||
//return affected != 0 | |||
return true | |||
} | |||
func AddVectorset(vectorset *Vectorset) bool { | |||
affected, err := adapter.engine.Insert(vectorset) | |||
if err != nil { | |||
panic(err) | |||
} | |||
return affected != 0 | |||
} | |||
func DeleteVectorset(vectorset *Vectorset) bool { | |||
affected, err := adapter.engine.ID(core.PK{vectorset.Owner, vectorset.Name}).Delete(&Vectorset{}) | |||
if err != nil { | |||
panic(err) | |||
} | |||
return affected != 0 | |||
} | |||
func (vectorset *Vectorset) GetId() string { | |||
return fmt.Sprintf("%s/%s", vectorset.Owner, vectorset.Name) | |||
} |
@@ -29,4 +29,11 @@ func initAPI() { | |||
beego.Router("/api/update-dataset", &controllers.ApiController{}, "POST:UpdateDataset") | |||
beego.Router("/api/add-dataset", &controllers.ApiController{}, "POST:AddDataset") | |||
beego.Router("/api/delete-dataset", &controllers.ApiController{}, "POST:DeleteDataset") | |||
beego.Router("/api/get-global-vectorsets", &controllers.ApiController{}, "GET:GetGlobalVectorsets") | |||
beego.Router("/api/get-vectorsets", &controllers.ApiController{}, "GET:GetVectorsets") | |||
beego.Router("/api/get-vectorset", &controllers.ApiController{}, "GET:GetVectorset") | |||
beego.Router("/api/update-vectorset", &controllers.ApiController{}, "POST:UpdateVectorset") | |||
beego.Router("/api/add-vectorset", &controllers.ApiController{}, "POST:AddVectorset") | |||
beego.Router("/api/delete-vectorset", &controllers.ApiController{}, "POST:DeleteVectorset") | |||
} |
@@ -10,6 +10,8 @@ import * as Conf from "./Conf"; | |||
import HomePage from "./HomePage"; | |||
import DatasetListPage from "./DatasetListPage"; | |||
import DatasetEditPage from "./DatasetEditPage"; | |||
import VectorsetListPage from "./VectorsetListPage"; | |||
import VectorsetEditPage from "./VectorsetEditPage"; | |||
import SigninPage from "./SigninPage"; | |||
import i18next from "i18next"; | |||
import SelectLanguageBox from "./SelectLanguageBox"; | |||
@@ -53,6 +55,8 @@ class App extends Component { | |||
this.setState({selectedMenuKey: '/'}); | |||
} else if (uri.includes('/datasets')) { | |||
this.setState({ selectedMenuKey: '/datasets' }); | |||
} else if (uri.includes('/vectorsets')) { | |||
this.setState({ selectedMenuKey: '/vectorsets' }); | |||
} else { | |||
this.setState({selectedMenuKey: 'null'}); | |||
} | |||
@@ -219,6 +223,13 @@ class App extends Component { | |||
</Link> | |||
</Menu.Item> | |||
); | |||
res.push( | |||
<Menu.Item key="/vectorsets"> | |||
<Link to="/vectorsets"> | |||
{i18next.t("general:Vectorsets")} | |||
</Link> | |||
</Menu.Item> | |||
); | |||
return res; | |||
} | |||
@@ -271,6 +282,8 @@ class App extends Component { | |||
<Route exact path="/signin" render={(props) => this.renderHomeIfSignedIn(<SigninPage {...props} />)}/> | |||
<Route exact path="/datasets" render={(props) => this.renderSigninIfNotSignedIn(<DatasetListPage account={this.state.account} {...props} />)}/> | |||
<Route exact path="/datasets/:datasetName" render={(props) => this.renderSigninIfNotSignedIn(<DatasetEditPage account={this.state.account} {...props} />)}/> | |||
<Route exact path="/vectorsets" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetListPage account={this.state.account} {...props} />)}/> | |||
<Route exact path="/vectorsets/:vectorsetName" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetEditPage account={this.state.account} {...props} />)}/> | |||
</Switch> | |||
</div> | |||
) | |||
@@ -76,7 +76,7 @@ class DatasetEditPage extends React.Component { | |||
</Row> | |||
<Row style={{marginTop: '20px'}} > | |||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> | |||
{i18next.t("dataset:Distance")} : | |||
{i18next.t("dataset:Distance")}: | |||
</Col> | |||
<Col span={22} > | |||
<InputNumber value={this.state.dataset.distance} onChange={value => { | |||
@@ -98,7 +98,7 @@ class DatasetEditPage extends React.Component { | |||
</Row> | |||
<Row style={{marginTop: '20px'}} > | |||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> | |||
{i18next.t("general:Preview")} : | |||
{i18next.t("general:Preview")}: | |||
</Col> | |||
<Col span={22} > | |||
<Dataset dataset={this.state.dataset} datasetName={this.state.dataset.name} /> | |||
@@ -0,0 +1,167 @@ | |||
import React from "react"; | |||
import {Button, Card, Col, Input, InputNumber, Row} from 'antd'; | |||
import * as VectorsetBackend from "./backend/VectorsetBackend"; | |||
import * as Setting from "./Setting"; | |||
import i18next from "i18next"; | |||
import VectorTable from "./VectorTable"; | |||
import {LinkOutlined} from "@ant-design/icons"; | |||
class VectorsetEditPage extends React.Component { | |||
constructor(props) { | |||
super(props); | |||
this.state = { | |||
classes: props, | |||
vectorsetName: props.match.params.vectorsetName, | |||
vectorset: null, | |||
}; | |||
} | |||
componentWillMount() { | |||
this.getVectorset(); | |||
} | |||
getVectorset() { | |||
VectorsetBackend.getVectorset(this.props.account.name, this.state.vectorsetName) | |||
.then((vectorset) => { | |||
this.setState({ | |||
vectorset: vectorset, | |||
}); | |||
}); | |||
} | |||
parseVectorsetField(key, value) { | |||
if (["score"].includes(key)) { | |||
value = Setting.myParseInt(value); | |||
} | |||
return value; | |||
} | |||
updateVectorsetField(key, value) { | |||
value = this.parseVectorsetField(key, value); | |||
let vectorset = this.state.vectorset; | |||
vectorset[key] = value; | |||
this.setState({ | |||
vectorset: vectorset, | |||
}); | |||
} | |||
renderVectorset() { | |||
return ( | |||
<Card size="small" title={ | |||
<div> | |||
{i18next.t("vectorset:Edit Vectorset")} | |||
<Button type="primary" onClick={this.submitVectorsetEdit.bind(this)}>{i18next.t("general:Save")}</Button> | |||
</div> | |||
} style={{marginLeft: '5px'}} type="inner"> | |||
<Row style={{marginTop: '10px'}} > | |||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> | |||
{i18next.t("general:Name")}: | |||
</Col> | |||
<Col span={22} > | |||
<Input value={this.state.vectorset.name} onChange={e => { | |||
this.updateVectorsetField('name', e.target.value); | |||
}} /> | |||
</Col> | |||
</Row> | |||
<Row style={{marginTop: '20px'}} > | |||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> | |||
{i18next.t("general:Display name")}: | |||
</Col> | |||
<Col span={22} > | |||
<Input value={this.state.vectorset.displayName} onChange={e => { | |||
this.updateVectorsetField('displayName', e.target.value); | |||
}} /> | |||
</Col> | |||
</Row> | |||
<Row style={{marginTop: '20px'}} > | |||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> | |||
{i18next.t("general:URL")}: | |||
</Col> | |||
<Col span={22} > | |||
<Input prefix={<LinkOutlined/>} value={this.state.vectorset.url} onChange={e => { | |||
this.updateVectorsetField('url', e.target.value); | |||
}} /> | |||
</Col> | |||
</Row> | |||
<Row style={{marginTop: '20px'}} > | |||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> | |||
{i18next.t("vectorset:Dimension")}: | |||
</Col> | |||
<Col span={22} > | |||
<InputNumber value={this.state.vectorset.dimension} onChange={value => { | |||
this.updateVectorsetField('dimension', value); | |||
}} /> | |||
</Col> | |||
</Row> | |||
<Row style={{marginTop: '20px'}} > | |||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> | |||
{i18next.t("vectorset:Count")}: | |||
</Col> | |||
<Col span={22} > | |||
<InputNumber disabled={true} value={this.state.vectorset.vectors.length} /> | |||
</Col> | |||
</Row> | |||
<Row style={{marginTop: '20px'}} > | |||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> | |||
{i18next.t("vectorset:Vectors")}: | |||
</Col> | |||
<Col span={22} > | |||
<VectorTable | |||
title={i18next.t("vectorset:Vectors")} | |||
table={this.state.vectorset.vectors} | |||
onUpdateTable={(value) => { this.updateVectorsetField('vectors', value)}} | |||
/> | |||
</Col> | |||
</Row> | |||
</Card> | |||
) | |||
} | |||
submitVectorsetEdit() { | |||
let vectorset = Setting.deepCopy(this.state.vectorset); | |||
VectorsetBackend.updateVectorset(this.state.vectorset.owner, this.state.vectorsetName, vectorset) | |||
.then((res) => { | |||
if (res) { | |||
Setting.showMessage("success", `Successfully saved`); | |||
this.setState({ | |||
vectorsetName: this.state.vectorset.name, | |||
}); | |||
this.props.history.push(`/vectorsets/${this.state.vectorset.name}`); | |||
} else { | |||
Setting.showMessage("error", `failed to save: server side failure`); | |||
this.updateVectorsetField('name', this.state.vectorsetName); | |||
} | |||
}) | |||
.catch(error => { | |||
Setting.showMessage("error", `failed to save: ${error}`); | |||
}); | |||
} | |||
render() { | |||
return ( | |||
<div> | |||
<Row style={{width: "100%"}}> | |||
<Col span={1}> | |||
</Col> | |||
<Col span={22}> | |||
{ | |||
this.state.vectorset !== null ? this.renderVectorset() : null | |||
} | |||
</Col> | |||
<Col span={1}> | |||
</Col> | |||
</Row> | |||
<Row style={{margin: 10}}> | |||
<Col span={2}> | |||
</Col> | |||
<Col span={18}> | |||
<Button type="primary" size="large" onClick={this.submitVectorsetEdit.bind(this)}>{i18next.t("general:Save")}</Button> | |||
</Col> | |||
</Row> | |||
</div> | |||
); | |||
} | |||
} | |||
export default VectorsetEditPage; |
@@ -0,0 +1,195 @@ | |||
import React from "react"; | |||
import {Link} from "react-router-dom"; | |||
import {Button, Col, Popconfirm, Row, Table} from 'antd'; | |||
import moment from "moment"; | |||
import * as Setting from "./Setting"; | |||
import * as VectorsetBackend from "./backend/VectorsetBackend"; | |||
import i18next from "i18next"; | |||
class VectorsetListPage extends React.Component { | |||
constructor(props) { | |||
super(props); | |||
this.state = { | |||
classes: props, | |||
vectorsets: null, | |||
}; | |||
} | |||
componentWillMount() { | |||
this.getVectorsets(); | |||
} | |||
getVectorsets() { | |||
VectorsetBackend.getVectorsets(this.props.account.name) | |||
.then((res) => { | |||
this.setState({ | |||
vectorsets: res, | |||
}); | |||
}); | |||
} | |||
newVectorset() { | |||
return { | |||
owner: this.props.account.name, | |||
name: `vectorset_${this.state.vectorsets.length}`, | |||
createdTime: moment().format(), | |||
displayName: `Vectorset ${this.state.vectorsets.length}`, | |||
url: "https://github.com/Embedding/Chinese-Word-Vectors", | |||
dimension: 128, | |||
vectors: [], | |||
} | |||
} | |||
addVectorset() { | |||
const newVectorset = this.newVectorset(); | |||
VectorsetBackend.addVectorset(newVectorset) | |||
.then((res) => { | |||
Setting.showMessage("success", `Vectorset added successfully`); | |||
this.setState({ | |||
vectorsets: Setting.prependRow(this.state.vectorsets, newVectorset), | |||
}); | |||
} | |||
) | |||
.catch(error => { | |||
Setting.showMessage("error", `Vectorset failed to add: ${error}`); | |||
}); | |||
} | |||
deleteVectorset(i) { | |||
VectorsetBackend.deleteVectorset(this.state.vectorsets[i]) | |||
.then((res) => { | |||
Setting.showMessage("success", `Vectorset deleted successfully`); | |||
this.setState({ | |||
vectorsets: Setting.deleteRow(this.state.vectorsets, i), | |||
}); | |||
} | |||
) | |||
.catch(error => { | |||
Setting.showMessage("error", `Vectorset failed to delete: ${error}`); | |||
}); | |||
} | |||
renderTable(vectorsets) { | |||
const columns = [ | |||
{ | |||
title: i18next.t("general:Name"), | |||
dataIndex: 'name', | |||
key: 'name', | |||
width: '120px', | |||
sorter: (a, b) => a.name.localeCompare(b.name), | |||
render: (text, record, index) => { | |||
return ( | |||
<Link to={`/vectorsets/${text}`}> | |||
{text} | |||
</Link> | |||
) | |||
} | |||
}, | |||
{ | |||
title: i18next.t("general:Display name"), | |||
dataIndex: 'displayName', | |||
key: 'displayName', | |||
width: '200px', | |||
sorter: (a, b) => a.displayName.localeCompare(b.displayName), | |||
}, | |||
{ | |||
title: i18next.t("general:URL"), | |||
dataIndex: 'url', | |||
key: 'url', | |||
width: '300px', | |||
sorter: (a, b) => a.url.localeCompare(b.url), | |||
render: (text, record, index) => { | |||
return ( | |||
<a target="_blank" rel="noreferrer" href={text}> | |||
{ | |||
Setting.getShortText(text) | |||
} | |||
</a> | |||
) | |||
} | |||
}, | |||
{ | |||
title: i18next.t("vectorset:Dimension"), | |||
dataIndex: 'dimension', | |||
key: 'dimension', | |||
width: '130px', | |||
sorter: (a, b) => a.dimension - b.dimension, | |||
}, | |||
{ | |||
title: i18next.t("vectorset:Vectors"), | |||
dataIndex: 'vectors', | |||
key: 'vectors', | |||
// width: '120px', | |||
sorter: (a, b) => a.vectors.localeCompare(b.vectors), | |||
render: (text, record, index) => { | |||
return Setting.getTags(text); | |||
} | |||
}, | |||
{ | |||
title: i18next.t("vectorset:Count"), | |||
dataIndex: 'count', | |||
key: 'count', | |||
width: '120px', | |||
sorter: (a, b) => a.count - b.count, | |||
render: (text, record, index) => { | |||
return record.vectors.length; | |||
} | |||
}, | |||
{ | |||
title: i18next.t("general:Action"), | |||
dataIndex: 'action', | |||
key: 'action', | |||
width: '160px', | |||
render: (text, record, index) => { | |||
return ( | |||
<div> | |||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/vectorsets/${record.name}`)}>{i18next.t("general:Edit")}</Button> | |||
<Popconfirm | |||
title={`Sure to delete vectorset: ${record.name} ?`} | |||
onConfirm={() => this.deleteVectorset(index)} | |||
okText="OK" | |||
cancelText="Cancel" | |||
> | |||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> | |||
</Popconfirm> | |||
</div> | |||
) | |||
} | |||
}, | |||
]; | |||
return ( | |||
<div> | |||
<Table columns={columns} dataSource={vectorsets} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} | |||
title={() => ( | |||
<div> | |||
{i18next.t("general:Vectorsets")} | |||
<Button type="primary" size="small" onClick={this.addVectorset.bind(this)}>{i18next.t("general:Add")}</Button> | |||
</div> | |||
)} | |||
loading={vectorsets === null} | |||
/> | |||
</div> | |||
); | |||
} | |||
render() { | |||
return ( | |||
<div> | |||
<Row style={{width: "100%"}}> | |||
<Col span={1}> | |||
</Col> | |||
<Col span={22}> | |||
{ | |||
this.renderTable(this.state.vectorsets) | |||
} | |||
</Col> | |||
<Col span={1}> | |||
</Col> | |||
</Row> | |||
</div> | |||
); | |||
} | |||
} | |||
export default VectorsetListPage; |
@@ -0,0 +1,56 @@ | |||
import * as Setting from "../Setting"; | |||
export function getGlobalVectorsets() { | |||
return fetch(`${Setting.ServerUrl}/api/get-global-vectorsets`, { | |||
method: "GET", | |||
credentials: "include" | |||
}).then(res => res.json()); | |||
} | |||
export function getVectorsets(owner) { | |||
return fetch(`${Setting.ServerUrl}/api/get-vectorsets?owner=${owner}`, { | |||
method: "GET", | |||
credentials: "include" | |||
}).then(res => res.json()); | |||
} | |||
export function getVectorset(owner, name) { | |||
return fetch(`${Setting.ServerUrl}/api/get-vectorset?id=${owner}/${encodeURIComponent(name)}`, { | |||
method: "GET", | |||
credentials: "include" | |||
}).then(res => res.json()); | |||
} | |||
export function getVectorsetGraph(owner, name, clusterNumber, distanceLimit) { | |||
return fetch(`${Setting.ServerUrl}/api/get-vectorset-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, { | |||
method: "GET", | |||
credentials: "include" | |||
}).then(res => res.json()); | |||
} | |||
export function updateVectorset(owner, name, vectorset) { | |||
let newVectorset = Setting.deepCopy(vectorset); | |||
return fetch(`${Setting.ServerUrl}/api/update-vectorset?id=${owner}/${encodeURIComponent(name)}`, { | |||
method: 'POST', | |||
credentials: 'include', | |||
body: JSON.stringify(newVectorset), | |||
}).then(res => res.json()); | |||
} | |||
export function addVectorset(vectorset) { | |||
let newVectorset = Setting.deepCopy(vectorset); | |||
return fetch(`${Setting.ServerUrl}/api/add-vectorset`, { | |||
method: 'POST', | |||
credentials: 'include', | |||
body: JSON.stringify(newVectorset), | |||
}).then(res => res.json()); | |||
} | |||
export function deleteVectorset(vectorset) { | |||
let newVectorset = Setting.deepCopy(vectorset); | |||
return fetch(`${Setting.ServerUrl}/api/delete-vectorset`, { | |||
method: 'POST', | |||
credentials: 'include', | |||
body: JSON.stringify(newVectorset), | |||
}).then(res => res.json()); | |||
} |
@@ -23,6 +23,14 @@ | |||
"Loading...": "Loading...", | |||
"Name": "Name", | |||
"Preview": "Preview", | |||
"Save": "Save" | |||
"Save": "Save", | |||
"URL": "URL", | |||
"Vectorsets": "Vectorsets" | |||
}, | |||
"vectorset": { | |||
"Count": "Count", | |||
"Dimension": "Dimension", | |||
"Edit Vectorset": "Edit Vectorset", | |||
"Vectors": "Vectors" | |||
} | |||
} |
@@ -23,6 +23,14 @@ | |||
"Loading...": "加载中...", | |||
"Name": "名称", | |||
"Preview": "预览", | |||
"Save": "保存" | |||
"Save": "保存", | |||
"URL": "URL", | |||
"Vectorsets": "向量集" | |||
}, | |||
"vectorset": { | |||
"Count": "个数", | |||
"Dimension": "维度", | |||
"Edit Vectorset": "编辑向量集", | |||
"Vectors": "向量" | |||
} | |||
} |