feat: add chats list (#17)

This commit is contained in:
Kelvin Chiu 2023-07-16 16:01:12 +08:00 committed by Yang Luo
parent d74a763224
commit 67dd3dc260
11 changed files with 847 additions and 3 deletions

108
controllers/chat.go Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/casbin/casibase/object"
)
func (c *ApiController) GetGlobalChats() {
chats, err := object.GetGlobalChats()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(chats)
}
func (c *ApiController) GetChats() {
owner := c.Input().Get("owner")
chats, err := object.GetChats(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(chats)
}
func (c *ApiController) GetChat() {
id := c.Input().Get("id")
chat, err := object.GetChat(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(chat)
}
func (c *ApiController) UpdateChat() {
id := c.Input().Get("id")
var chat object.Chat
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
if err != nil {
c.ResponseError(err.Error())
return
}
success, err := object.UpdateChat(id, &chat)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(success)
}
func (c *ApiController) AddChat() {
var chat object.Chat
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
if err != nil {
c.ResponseError(err.Error())
return
}
success, err := object.AddChat(&chat)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(success)
}
func (c *ApiController) DeleteChat() {
var chat object.Chat
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
if err != nil {
c.ResponseError(err.Error())
return
}
success, err := object.DeleteChat(&chat)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(success)
}

View File

@ -127,4 +127,9 @@ func (a *Adapter) createTable() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = a.engine.Sync2(new(Chat))
if err != nil {
panic(err)
}
} }

118
object/chat.go Normal file
View File

@ -0,0 +1,118 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casbin/casibase/util"
"xorm.io/core"
)
type Chat 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"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
//Organization string `xorm:"varchar(100)" json:"organization"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"`
User1 string `xorm:"varchar(100)" json:"user1"`
User2 string `xorm:"varchar(100)" json:"user2"`
Users []string `xorm:"varchar(100)" json:"users"`
MessageCount int `json:"messageCount"`
}
func GetGlobalChats() ([]*Chat, error) {
chats := []*Chat{}
err := adapter.engine.Asc("owner").Desc("created_time").Find(&chats)
if err != nil {
return chats, err
}
return chats, nil
}
func GetChats(owner string) ([]*Chat, error) {
chats := []*Chat{}
err := adapter.engine.Desc("created_time").Find(&chats, &Chat{Owner: owner})
if err != nil {
return chats, err
}
return chats, nil
}
func getChat(owner, name string) (*Chat, error) {
chat := Chat{Owner: owner, Name: name}
existed, err := adapter.engine.Get(&chat)
if err != nil {
return nil, err
}
if existed {
return &chat, nil
} else {
return nil, nil
}
}
func GetChat(id string) (*Chat, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getChat(owner, name)
}
func UpdateChat(id string, chat *Chat) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
_, err := getChat(owner, name)
if err != nil {
return false, err
}
if chat == nil {
return false, nil
}
_, err = adapter.engine.ID(core.PK{owner, name}).AllCols().Update(chat)
if err != nil {
return false, err
}
//return affected != 0
return true, nil
}
func AddChat(chat *Chat) (bool, error) {
affected, err := adapter.engine.Insert(chat)
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteChat(chat *Chat) (bool, error) {
affected, err := adapter.engine.ID(core.PK{chat.Owner, chat.Name}).Delete(&Chat{})
if err != nil {
return false, err
}
return affected != 0, nil
}
func (chat *Chat) GetId() string {
return fmt.Sprintf("%s/%s", chat.Owner, chat.Name)
}

View File

@ -74,6 +74,13 @@ func initAPI() {
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider") beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider") beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")
beego.Router("/api/get-global-chats", &controllers.ApiController{}, "GET:GetGlobalChats")
beego.Router("/api/get-chats", &controllers.ApiController{}, "GET:GetChats")
beego.Router("/api/get-chat", &controllers.ApiController{}, "GET:GetChat")
beego.Router("/api/update-chat", &controllers.ApiController{}, "POST:UpdateChat")
beego.Router("/api/add-chat", &controllers.ApiController{}, "POST:AddChat")
beego.Router("/api/delete-chat", &controllers.ApiController{}, "POST:DeleteChat")
beego.Router("/api/update-file", &controllers.ApiController{}, "POST:UpdateFile") beego.Router("/api/update-file", &controllers.ApiController{}, "POST:UpdateFile")
beego.Router("/api/add-file", &controllers.ApiController{}, "POST:AddFile") beego.Router("/api/add-file", &controllers.ApiController{}, "POST:AddFile")
beego.Router("/api/delete-file", &controllers.ApiController{}, "POST:DeleteFile") beego.Router("/api/delete-file", &controllers.ApiController{}, "POST:DeleteFile")

View File

@ -38,6 +38,8 @@ import ProviderEditPage from "./ProviderEditPage";
import SigninPage from "./SigninPage"; import SigninPage from "./SigninPage";
import i18next from "i18next"; import i18next from "i18next";
import LanguageSelect from "./LanguageSelect"; import LanguageSelect from "./LanguageSelect";
import ChatEditPage from "./ChatEditPage";
import ChatListPage from "./ChatListPage";
const {Header, Footer} = Layout; const {Header, Footer} = Layout;
@ -88,6 +90,8 @@ class App extends Component {
this.setState({selectedMenuKey: "/videos"}); this.setState({selectedMenuKey: "/videos"});
} else if (uri.includes("/providers")) { } else if (uri.includes("/providers")) {
this.setState({selectedMenuKey: "/providers"}); this.setState({selectedMenuKey: "/providers"});
} else if (uri.includes("/chats")) {
this.setState({selectedMenuKey: "/chats"});
} else { } else {
this.setState({selectedMenuKey: "null"}); this.setState({selectedMenuKey: "null"});
} }
@ -314,6 +318,13 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push(
<Menu.Item key="/chats">
<Link to="/chats">
{i18next.t("general:Chats")}
</Link>
</Menu.Item>
);
if (Setting.isLocalAdminUser(this.state.account)) { if (Setting.isLocalAdminUser(this.state.account)) {
res.push( res.push(
@ -390,6 +401,8 @@ class App extends Component {
<Route exact path="/videos/:videoName" render={(props) => this.renderSigninIfNotSignedIn(<VideoEditPage account={this.state.account} {...props} />)} /> <Route exact path="/videos/:videoName" render={(props) => this.renderSigninIfNotSignedIn(<VideoEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderSigninIfNotSignedIn(<ProviderListPage account={this.state.account} {...props} />)} /> <Route exact path="/providers" render={(props) => this.renderSigninIfNotSignedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:providerName" render={(props) => this.renderSigninIfNotSignedIn(<ProviderEditPage account={this.state.account} {...props} />)} /> <Route exact path="/providers/:providerName" render={(props) => this.renderSigninIfNotSignedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/chats" render={(props) => this.renderSigninIfNotSignedIn(<ChatListPage account={this.state.account} {...props} />)} />
<Route exact path="/chats/:chatName" render={(props) => this.renderSigninIfNotSignedIn(<ChatEditPage account={this.state.account} {...props} />)} />
</Switch> </Switch>
</div> </div>
); );

218
web/src/ChatEditPage.js Normal file
View File

@ -0,0 +1,218 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, Row, Select} from "antd";
import * as ChatBackend from "./backend/ChatBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
const {Option} = Select;
class ChatEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
chatName: props.match.params.chatName,
chat: null,
};
}
UNSAFE_componentWillMount() {
this.getChat();
}
getChat() {
ChatBackend.getChat(this.props.account.name, this.state.chatName)
.then((chat) => {
if (chat.status === "ok") {
this.setState({
chat: chat.data,
});
} else {
Setting.showMessage("error", `Failed to get chat: ${chat.msg}`);
}
});
}
parseChatField(key, value) {
if ([""].includes(key)) {
value = Setting.myParseInt(value);
}
return value;
}
updateChatField(key, value) {
value = this.parseChatField(key, value);
const chat = this.state.chat;
chat[key] = value;
this.setState({
chat: chat,
});
}
renderChat() {
return (
<Card size="small" title={
<div>
{i18next.t("chat:Edit Chat")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitChatEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
{/* <Row style={{marginTop: "10px"}} >*/}
{/* <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>*/}
{/* {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :*/}
{/* </Col>*/}
{/* <Col span={22} >*/}
{/* <Select virtual={false} disabled={!Setting.isAdminUser(this.props.account)} style={{width: "100%"}} value={this.state.chat.organization} onChange={(value => {this.updateChatField("organization", value);})}*/}
{/* options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))*/}
{/* } />*/}
{/* </Col>*/}
{/* </Row>*/}
<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.chat.name} onChange={e => {
this.updateChatField("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.chat.displayName} onChange={e => {
this.updateChatField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("chat:Type")} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.type} onChange={(value => {
this.updateChatField("type", value);
})}>
{
[
{id: "Single", name: i18next.t("chat:Single")},
{id: "Group", name: i18next.t("chat:Group")},
{id: "AI", name: i18next.t("chat:AI")},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("chat:Category")} :
</Col>
<Col span={22} >
<Input value={this.state.chat.category} onChange={e => {
this.updateChatField("category", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("chat:User1")} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user1} onChange={(value => {this.updateChatField("user1", value);})}
options={this.state.chat.users.map((user) => Setting.getOption(`${user}`, `${user}`))
} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("chat:User2")} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user2} onChange={(value => {this.updateChatField("user2", value);})}
options={[{label: "None", value: ""}, ...this.state.chat.users.map((user) => Setting.getOption(`${user}`, `${user}`))]
} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("chat:Users")} :
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.chat.users}
onChange={(value => {this.updateChatField("users", value);})}
options={this.state.chat.users.map((user) => Setting.getOption(`${user}`, `${user}`))}
/>
</Col>
</Row>
</Card>
);
}
submitChatEdit() {
const chat = Setting.deepCopy(this.state.chat);
ChatBackend.updateChat(this.state.chat.owner, this.state.chatName, chat)
.then((res) => {
if (res.status === "ok") {
if (res.data) {
Setting.showMessage("success", "Successfully saved");
this.setState({
chatName: this.state.chat.name,
});
this.props.history.push(`/chats/${this.state.chat.name}`);
} else {
Setting.showMessage("error", "failed to save: server side failure");
this.updateChatField("name", this.state.chatName);
}
} else {
Setting.showMessage("error", `failed to save: ${res.msg}`);
}
})
.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.chat !== null ? this.renderChat() : null
}
</Col>
<Col span={1}>
</Col>
</Row>
<Row style={{margin: 10}}>
<Col span={2}>
</Col>
<Col span={18}>
<Button type="primary" onClick={this.submitChatEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</Col>
</Row>
</div>
);
}
}
export default ChatEditPage;

275
web/src/ChatListPage.js Normal file
View File

@ -0,0 +1,275 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Link} from "react-router-dom";
import {Button, Col, Popconfirm, Row, Table} from "antd";
import * as Setting from "./Setting";
import * as ChatBackend from "./backend/ChatBackend";
import moment from "moment";
import i18next from "i18next";
class ChatListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
chats: null,
};
}
UNSAFE_componentWillMount() {
this.getChats();
}
getChats() {
ChatBackend.getChats(this.props.account.name)
.then((res) => {
if (res.status === "ok") {
this.setState({
chats: res.data,
});
} else {
Setting.showMessage("error", `Failed to get chats: ${res.msg}`);
}
});
}
newChat() {
const randomName = Setting.getRandomName();
return {
owner: this.props.account.name,
name: `chat_${randomName}`,
createdTime: moment().format(),
updatedTime: moment().format(),
displayName: `New Chat - ${randomName}`,
category: "Chat Category - 1",
type: "Single",
user1: `${this.props.account.owner}/${this.props.account.name}`,
user2: "",
users: [`${this.props.account.owner}/${this.props.account.name}`],
messageCount: 0,
};
}
addChat() {
const newChat = this.newChat();
ChatBackend.addChat(newChat)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", "Chat added successfully");
this.setState({
chats: Setting.prependRow(this.state.chats, newChat),
});
} else {
Setting.showMessage("error", `Failed to add Chat: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Chat failed to add: ${error}`);
});
}
deleteChat(i) {
ChatBackend.deleteChat(this.state.chats[i])
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", "Chat deleted successfully");
this.setState({
chats: Setting.deleteRow(this.state.chats, i),
});
} else {
Setting.showMessage("error", `Failed to delete Chat: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Chat failed to delete: ${error}`);
});
}
renderTable(chats) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "140px",
sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, record, index) => {
return (
<Link to={`chats/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
key: "createdTime",
width: "150px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("general:Updated time"),
dataIndex: "updatedTime",
key: "updatedTime",
width: "15 0px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("general:Display name"),
dataIndex: "displayName",
key: "displayName",
// width: '100px',
sorter: true,
// ...this.getColumnSearchProps("displayName"),
},
{
title: i18next.t("chat:Type"),
dataIndex: "type",
key: "type",
width: "110px",
sorter: true,
filterMultiple: false,
filters: [
{text: "Single", value: "Single"},
{text: "Group", value: "Group"},
{text: "AI", value: "AI"},
],
render: (text, record, index) => {
return i18next.t(`chat:${text}`);
},
},
{
title: i18next.t("chat:Category"),
dataIndex: "category",
key: "category",
// width: '100px',
sorter: true,
// ...this.getColumnSearchProps("category"),
},
{
title: i18next.t("chat:User1"),
dataIndex: "user1",
key: "user1",
width: "120px",
sorter: true,
// ...this.getColumnSearchProps("user1"),
render: (text, record, index) => {
return (
<Link to={`/users/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("chat:User2"),
dataIndex: "user2",
key: "user2",
width: "120px",
sorter: true,
// ...this.getColumnSearchProps("user2"),
render: (text, record, index) => {
return (
<Link to={`/users/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Users"),
dataIndex: "users",
key: "users",
// width: '100px',
sorter: true,
// ...this.getColumnSearchProps("users"),
render: (text, record, index) => {
return Setting.getTags(text, "users");
},
},
{
title: i18next.t("chat:Message count"),
dataIndex: "messageCount",
key: "messageCount",
// width: '100px',
sorter: true,
// ...this.getColumnSearchProps("messageCount"),
},
{
title: i18next.t("general:Action"),
dataIndex: "action",
key: "action",
width: "180px",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/chats/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete chat: ${record.name} ?`}
onConfirm={() => this.deleteChat(index)}
okText="OK"
cancelText="Cancel"
>
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);
},
},
];
return (
<div>
<Table columns={columns} dataSource={chats} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => (
<div>
{i18next.t("chat:Chats")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addChat.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
loading={chats === null}
/>
</div>
);
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.renderTable(this.state.chats)
}
</Col>
<Col span={1}>
</Col>
</Row>
</div>
);
}
}
export default ChatListPage;

View File

@ -289,11 +289,19 @@ export function getTag(text, type, state) {
} }
} }
export function getTags(vectors) { export function getTags(vectors, type) {
if (!vectors) { if (!vectors) {
return []; return [];
} }
if (type === "vectors") {
return getVectorTag(vectors);
} else if (type === "users") {
return getUserTag(vectors);
}
}
function getVectorTag(vectors) {
const res = []; const res = [];
vectors.forEach((vector, i) => { vectors.forEach((vector, i) => {
if (vector.data.length !== 0) { if (vector.data.length !== 0) {
@ -315,6 +323,28 @@ export function getTags(vectors) {
return res; return res;
} }
function getUserTag(users) {
const res = [];
users.forEach((user, i) => {
if (user.length !== 0) {
res.push(
<Tooltip placement="top" title={getShortText(JSON.stringify(user), 500)}>
<Tag color={"success"}>
{user}
</Tag>
</Tooltip>
);
} else {
res.push(
<Tag color={"warning"}>
{user}
</Tag>
);
}
});
return res;
}
export function getLabelTags(labels) { export function getLabelTags(labels) {
if (!labels) { if (!labels) {
return []; return [];
@ -593,3 +623,10 @@ export function getItem(label, key, icon, children, type) {
type, type,
}; };
} }
export function getOption(label, value) {
return {
label,
value,
};
}

View File

@ -164,7 +164,7 @@ class VectorsetListPage extends React.Component {
// width: '120px', // width: '120px',
sorter: (a, b) => a.vectors.localeCompare(b.vectors), sorter: (a, b) => a.vectors.localeCompare(b.vectors),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text, "vectors");
}, },
}, },
{ {

View File

@ -124,7 +124,7 @@ class WordsetListPage extends React.Component {
// width: '120px', // width: '120px',
sorter: (a, b) => a.vectors.localeCompare(b.vectors), sorter: (a, b) => a.vectors.localeCompare(b.vectors),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text, "vectors");
}, },
}, },
// { // {

View File

@ -0,0 +1,63 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as Setting from "../Setting";
export function getGlobalChats() {
return fetch(`${Setting.ServerUrl}/api/get-global-chats`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function getChats(owner) {
return fetch(`${Setting.ServerUrl}/api/get-chats?owner=${owner}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function getChat(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-chat?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function updateChat(owner, name, chat) {
const newChat = Setting.deepCopy(chat);
return fetch(`${Setting.ServerUrl}/api/update-chat?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newChat),
}).then(res => res.json());
}
export function addChat(chat) {
const newChat = Setting.deepCopy(chat);
return fetch(`${Setting.ServerUrl}/api/add-chat`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newChat),
}).then(res => res.json());
}
export function deleteChat(chat) {
const newChat = Setting.deepCopy(chat);
return fetch(`${Setting.ServerUrl}/api/delete-chat`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newChat),
}).then(res => res.json());
}