Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions castle/cms/browser/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,12 @@
layer="..interfaces.ICastleLayer"
/>

<browser:page
name="openai-request"
for="*"
class=".openai.OpenAI"
permission="zope2.View"
layer="castle.cms.interfaces.ICastleLayer"
/>

</configure>
84 changes: 46 additions & 38 deletions castle/cms/browser/controlpanel/configure.zcml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:plone="http://namespaces.plone.org/plone"
xmlns:five="http://namespaces.zope.org/five">

<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:plone="http://namespaces.plone.org/plone"
xmlns:five="http://namespaces.zope.org/five"
>
<browser:page
for="Products.CMFPlone.interfaces.siteroot.IPloneSiteRoot"
name="castle-settings"
Expand Down Expand Up @@ -44,55 +45,55 @@
name="twitter-auth"
permission="zope2.View"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
for="Products.CMFPlone.interfaces.siteroot.IPloneSiteRoot"
class=".social.AuthorizeGoogle"
name="google-auth"
permission="zope2.View"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="social-controlpanel"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".social.SocialControlPanel"
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="site-controlpanel"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".site.SiteControlPanel"
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="security-controlpanel"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".security.SecurityControlPanel"
permission="plone.app.controlpanel.Security"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="business-controlpanel"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".business.BusinessControlPanel"
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="announcements-controlpanel"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".announcements.AnnouncementsControlPanel"
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="manage-subscribers"
Expand All @@ -101,7 +102,7 @@
class=".announcements.ManageSubscribers"
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="audit-controlpanel"
Expand All @@ -110,7 +111,7 @@
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
template="templates/audit.pt"
/>
/>

<browser:page
name="sessions-controlpanel"
Expand All @@ -119,7 +120,7 @@
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
template="templates/sessions.pt"
/>
/>

<browser:page
name="tags-controlpanel"
Expand All @@ -128,16 +129,15 @@
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
template="templates/tags.pt"
/>
/>

<browser:page
name="crawler-controlpanel"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".crawler.CrawlerControlPanel"
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
/>

/>

<browser:page
name="search-exclusion-controlpanel"
Expand All @@ -146,7 +146,7 @@
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
template="templates/search-exclusion.pt"
/>
/>

<browser:page
name="published-with-private-parents"
Expand All @@ -155,15 +155,15 @@
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
template="templates/published-with-private-parents.pt"
/>
/>

<browser:page
name="survey-controlpanel"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".survey.SurveyControlPanel"
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="links-controlpanel"
Expand All @@ -172,7 +172,7 @@
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
template="templates/links.pt"
/>
/>

<!-- overridding so we can customize styles, remove buttons -->
<browser:page
Expand All @@ -182,7 +182,7 @@
template="templates/mapper.pt"
permission="plone.app.controlpanel.Themes"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<!-- override for widgets -->
<browser:page
Expand All @@ -191,7 +191,7 @@
class=".dateandtime.DateAndTimeControlPanel"
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<!-- work with castle theming -->
<browser:page
Expand All @@ -201,7 +201,7 @@
attribute="getFrame"
permission="plone.app.controlpanel.Themes"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<!-- user/groups overrides -->
<browser:page
Expand All @@ -211,7 +211,7 @@
permission="plone.app.controlpanel.UsersAndGroups"
template="templates/usergroups_usersoverview.pt"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="usergroup-groupmembership"
Expand All @@ -220,16 +220,16 @@
permission="plone.app.controlpanel.UsersAndGroups"
template="templates/usergroups_groupmembership.pt"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="usergroup-groupdetails"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class="Products.CMFPlone.controlpanel.browser.usergroups_groupdetails.GroupDetailsControlPanel"
permission="plone.app.controlpanel.UsersAndGroups"
template="templates/usergroups_groupdetails.pt"
layer="castle.cms.interfaces.ICastleLayer"
/>
<browser:page
name="usergroup-groupdetails"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class="Products.CMFPlone.controlpanel.browser.usergroups_groupdetails.GroupDetailsControlPanel"
permission="plone.app.controlpanel.UsersAndGroups"
template="templates/usergroups_groupdetails.pt"
layer="castle.cms.interfaces.ICastleLayer"
/>

<browser:page
name="status-controlpanel"
Expand All @@ -238,7 +238,7 @@
permission="plone.app.controlpanel.Site"
layer="castle.cms.interfaces.ICastleLayer"
template="templates/status.pt"
/>
/>

<configure package="Products.CMFPlone.controlpanel.browser">
<browser:page
Expand All @@ -248,14 +248,22 @@
permission="cmf.ManagePortal"
template="quickinstaller.pt"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>
</configure>

<browser:page
name="upgrade_products"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".quickinstaller.UpgradeProductsView"
permission="cmf.ManagePortal"
layer="castle.cms.interfaces.ICastleLayer"
/>
/>

<browser:page
name="manage-openai-settings"
for="Products.CMFPlone.interfaces.siteroot.IPloneSiteRoot"
class="castle.cms.browser.controlpanel.openai.OpenAISettingsControlPanel"
permission="cmf.ManagePortal"
layer="castle.cms.interfaces.ICastleLayer"
/>
</configure>
25 changes: 25 additions & 0 deletions castle/cms/browser/controlpanel/openai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from plone.app.registry.browser.controlpanel import (
RegistryEditForm,
ControlPanelFormWrapper,
)
from plone.supermodel import model

import zope.schema as schema

class IOpenAISettings(model.Schema):
openai_api_key = schema.TextLine(
title=u'OpenAI API Key',
default=None,
required=False,
)

class OpenAISettingsControlPanelForm(RegistryEditForm):
schema_prefix = 'castle'
schema = IOpenAISettings
id = 'OpenAISettingsControlPanel'
label = u'OpenAI Settings'
description = 'Settings to communicate with OpenAI API'


class OpenAISettingsControlPanel(ControlPanelFormWrapper):
form = OpenAISettingsControlPanelForm
91 changes: 91 additions & 0 deletions castle/cms/browser/openai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from Products.Five import BrowserView
from plone.protect import (
PostOnly,
protect,
)
from plone import api
import requests
import random
import json

class OpenAI(BrowserView):

@protect(PostOnly)
def __call__(self, REQUEST=None):
data = self.request.form.get("data", {})
return json.dumps(self.openai_api_request(data))

def openai_api_request(self, data):
response = requests.post(
url="https://api.openai.com/v1/chat/completions",
data=json.dumps({
"model": "gpt-3.5-turbo",
"messages": [{
"role": "user",
"content": data,
}],
"temperature": 0.2,
"max_tokens": 500,
}),
headers={
"Content-Type": "application/json",
"Authorization": "Bearer {}".format(self.api_key)
},
)
return self.handle_response(response)

def handle_response(self, response, num_retries=1, delay=1, max_retries=10):
if not response.ok:
try:
response_json = response.json()
except: # noqa
return self.error_response()
error = response_json.get("error", {})
code = error.get("code", {})
if response.status_code == 429 and code != "insufficient_quota": # nosec
if num_retries <= max_retries:
self.exponential_backoff(data=self.request.data, num_retries=num_retries, delay=delay)
try:
received_data = response.json()
except: # noqa
return self.error_response()

status_code = response.status_code
success = 200 <= status_code < 300

if success:
status = "success"
message = received_data.get('choices')[0].get('message').get('content')
else: # openai api error
status = response_json.get("error", {}).get("code", {})
message = received_data.get("error", {}).get("message", {})

return_data = {
"status": status,
"message": message,
}

response = self.request.response
response.setStatus(status_code)
response.setHeader('Content-Type', 'application/json')

return return_data

def error_response(self): # unforseen error
response = self.request.response
response.setStatus(response.status_code)
response.setHeader('Content-Type', 'application/json')
return {
"status": "error",
"message": "unforseen error",
}

@property
def api_key(self):
key = api.portal.get_registry_record("castle.openai_api_key", default="default_value")
return key

def exponential_backoff(self, data, num_retries, delay, exponential_base=2):
num_retries = num_retries + 1
delay *= exponential_base * (2 * random.random()) # nosec
self.openai_api_request(data=data, num_retries=num_retries, delay=delay)
Loading