| @@ -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()); | |||
| } | |||