@@ -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) | |||
} |
@@ -127,4 +127,9 @@ func (a *Adapter) createTable() { | |||
if err != nil { | |||
panic(err) | |||
} | |||
err = a.engine.Sync2(new(Chat)) | |||
if err != nil { | |||
panic(err) | |||
} | |||
} |
@@ -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) | |||
} |
@@ -74,6 +74,13 @@ func initAPI() { | |||
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider") | |||
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/add-file", &controllers.ApiController{}, "POST:AddFile") | |||
beego.Router("/api/delete-file", &controllers.ApiController{}, "POST:DeleteFile") | |||
@@ -38,6 +38,8 @@ import ProviderEditPage from "./ProviderEditPage"; | |||
import SigninPage from "./SigninPage"; | |||
import i18next from "i18next"; | |||
import LanguageSelect from "./LanguageSelect"; | |||
import ChatEditPage from "./ChatEditPage"; | |||
import ChatListPage from "./ChatListPage"; | |||
const {Header, Footer} = Layout; | |||
@@ -88,6 +90,8 @@ class App extends Component { | |||
this.setState({selectedMenuKey: "/videos"}); | |||
} else if (uri.includes("/providers")) { | |||
this.setState({selectedMenuKey: "/providers"}); | |||
} else if (uri.includes("/chats")) { | |||
this.setState({selectedMenuKey: "/chats"}); | |||
} else { | |||
this.setState({selectedMenuKey: "null"}); | |||
} | |||
@@ -314,6 +318,13 @@ class App extends Component { | |||
</Link> | |||
</Menu.Item> | |||
); | |||
res.push( | |||
<Menu.Item key="/chats"> | |||
<Link to="/chats"> | |||
{i18next.t("general:Chats")} | |||
</Link> | |||
</Menu.Item> | |||
); | |||
if (Setting.isLocalAdminUser(this.state.account)) { | |||
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="/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="/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> | |||
</div> | |||
); | |||
@@ -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")} | |||
<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; |
@@ -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")} | |||
<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; |
@@ -289,11 +289,19 @@ export function getTag(text, type, state) { | |||
} | |||
} | |||
export function getTags(vectors) { | |||
export function getTags(vectors, type) { | |||
if (!vectors) { | |||
return []; | |||
} | |||
if (type === "vectors") { | |||
return getVectorTag(vectors); | |||
} else if (type === "users") { | |||
return getUserTag(vectors); | |||
} | |||
} | |||
function getVectorTag(vectors) { | |||
const res = []; | |||
vectors.forEach((vector, i) => { | |||
if (vector.data.length !== 0) { | |||
@@ -315,6 +323,28 @@ export function getTags(vectors) { | |||
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) { | |||
if (!labels) { | |||
return []; | |||
@@ -593,3 +623,10 @@ export function getItem(label, key, icon, children, type) { | |||
type, | |||
}; | |||
} | |||
export function getOption(label, value) { | |||
return { | |||
label, | |||
value, | |||
}; | |||
} |
@@ -164,7 +164,7 @@ class VectorsetListPage extends React.Component { | |||
// width: '120px', | |||
sorter: (a, b) => a.vectors.localeCompare(b.vectors), | |||
render: (text, record, index) => { | |||
return Setting.getTags(text); | |||
return Setting.getTags(text, "vectors"); | |||
}, | |||
}, | |||
{ | |||
@@ -124,7 +124,7 @@ class WordsetListPage extends React.Component { | |||
// width: '120px', | |||
sorter: (a, b) => a.vectors.localeCompare(b.vectors), | |||
render: (text, record, index) => { | |||
return Setting.getTags(text); | |||
return Setting.getTags(text, "vectors"); | |||
}, | |||
}, | |||
// { | |||
@@ -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()); | |||
} |