Skip to content

Commit 36f7975

Browse files
authored
chore(AI Assistant): make the assistant available for free (baserow#4222)
1 parent 7f30d19 commit 36f7975

File tree

16 files changed

+29
-212
lines changed

16 files changed

+29
-212
lines changed

backend/src/baserow/test_utils/pytest_conftest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ def fake():
6060
# This solution is taken from: https://bit.ly/3UJ90co
6161
@pytest.fixture(scope="session")
6262
def async_event_loop():
63-
loop = asyncio.get_event_loop()
63+
try:
64+
loop = asyncio.get_running_loop()
65+
except RuntimeError:
66+
loop = asyncio.new_event_loop()
6467
yield loop
6568
loop.close()
6669

enterprise/backend/src/baserow_enterprise/api/assistant/views.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from django.http import StreamingHttpResponse
66

7-
from baserow_premium.license.handler import LicenseHandler
87
from drf_spectacular.openapi import OpenApiParameter, OpenApiTypes
98
from drf_spectacular.utils import OpenApiResponse, extend_schema
109
from loguru import logger
@@ -39,7 +38,6 @@
3938
HumanMessage,
4039
UIContext,
4140
)
42-
from baserow_enterprise.features import ASSISTANT
4341

4442
from .errors import (
4543
ERROR_ASSISTANT_CHAT_DOES_NOT_EXIST,
@@ -62,7 +60,6 @@ class AssistantChatsView(APIView):
6260
operation_id="list_assistant_chats",
6361
description=(
6462
"List all AI assistant chats for the current user in the specified workspace."
65-
"\n\nThis is a **advanced/enterprise** feature."
6663
),
6764
parameters=[
6865
OpenApiParameter(
@@ -104,10 +101,6 @@ def get(self, request: Request, query_params) -> Response:
104101
workspace_id = query_params["workspace_id"]
105102
workspace = CoreHandler().get_workspace(workspace_id)
106103

107-
LicenseHandler.raise_if_user_doesnt_have_feature(
108-
ASSISTANT, request.user, workspace
109-
)
110-
111104
CoreHandler().check_permissions(
112105
request.user,
113106
ChatAssistantChatOperationType.type,
@@ -132,7 +125,6 @@ class AssistantChatView(APIView):
132125
operation_id="send_message_to_assistant_chat",
133126
description=(
134127
"Send a message to the specified AI assistant chat and stream back the response.\n\n"
135-
"This is an **advanced/enterprise** feature."
136128
),
137129
request=AssistantMessageRequestSerializer,
138130
responses={
@@ -157,9 +149,6 @@ def post(self, request: Request, chat_uuid: str, data) -> StreamingHttpResponse:
157149
ui_context = UIContext.from_validate_request(request, data["ui_context"])
158150
workspace_id = ui_context.workspace.id
159151
workspace = CoreHandler().get_workspace(workspace_id)
160-
LicenseHandler.raise_if_user_doesnt_have_feature(
161-
ASSISTANT, request.user, workspace
162-
)
163152
CoreHandler().check_permissions(
164153
request.user,
165154
ChatAssistantChatOperationType.type,
@@ -216,10 +205,7 @@ def _stream_assistant_message(self, message: AssistantMessageUnion) -> str:
216205
@extend_schema(
217206
tags=["AI Assistant"],
218207
operation_id="list_assistant_chat_messages",
219-
description=(
220-
"List all messages in the specified AI assistant chat.\n\n"
221-
"This is an **advanced/enterprise** feature."
222-
),
208+
description=("List all messages in the specified AI assistant chat.\n\n"),
223209
responses={
224210
200: AssistantChatMessagesSerializer,
225211
400: get_error_schema(["ERROR_USER_NOT_IN_GROUP"]),
@@ -239,9 +225,6 @@ def get(self, request: Request, chat_uuid: str) -> Response:
239225
chat = handler.get_chat(request.user, chat_uuid)
240226

241227
workspace = chat.workspace
242-
LicenseHandler.raise_if_user_doesnt_have_feature(
243-
ASSISTANT, request.user, workspace
244-
)
245228
CoreHandler().check_permissions(
246229
request.user,
247230
ChatAssistantChatOperationType.type,
@@ -263,7 +246,6 @@ class AssistantChatMessageFeedbackView(APIView):
263246
operation_id="submit_assistant_message_feedback",
264247
description=(
265248
"Provide sentiment and feedback for the given AI assistant chat message.\n\n"
266-
"This is an **advanced/enterprise** feature."
267249
),
268250
responses={
269251
200: None,
@@ -286,10 +268,6 @@ def put(self, request: Request, message_id: int, data) -> Response:
286268

287269
handler = AssistantHandler()
288270
message = handler.get_chat_message_by_id(request.user, message_id)
289-
LicenseHandler.raise_if_user_doesnt_have_feature(
290-
ASSISTANT, request.user, message.chat.workspace
291-
)
292-
293271
try:
294272
prediction: AssistantChatPrediction = message.prediction
295273
except AttributeError:

enterprise/backend/src/baserow_enterprise/assistant/tools/database/tools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,7 @@ def create_views(
591591
table_id=table.id,
592592
view_id=created_views[0]["id"],
593593
view_name=created_views[0]["name"],
594+
view_type=created_views[0]["type"],
594595
)
595596
)
596597

enterprise/backend/src/baserow_enterprise/assistant/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ class ViewNavigationType(BaseModel):
197197
table_id: int
198198
view_id: int
199199
view_name: str
200+
view_type: str
200201

201202
def to_localized_string(self):
202203
return _("view %(view_name)s") % {"view_name": self.view_name}

enterprise/backend/src/baserow_enterprise/features.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
DATA_SYNC = "data_sync"
1010
ADVANCED_WEBHOOKS = "advanced_webhooks"
1111
FIELD_LEVEL_PERMISSIONS = "field_level_permissions"
12-
ASSISTANT = "assistant"
1312

1413
BUILDER_SSO = "application_user_sso"
1514
BUILDER_NO_BRANDING = "application_no_branding"

enterprise/backend/src/baserow_enterprise/license_types.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from baserow.core.models import Workspace
88
from baserow_enterprise.features import (
99
ADVANCED_WEBHOOKS,
10-
ASSISTANT,
1110
AUDIT_LOG,
1211
BUILDER_CUSTOM_CODE,
1312
BUILDER_FILE_INPUT,
@@ -33,7 +32,6 @@
3332
RBAC,
3433
TEAMS,
3534
AUDIT_LOG,
36-
ASSISTANT,
3735
# database
3836
DATA_SYNC,
3937
ADVANCED_WEBHOOKS,

enterprise/backend/tests/baserow_enterprise_tests/api/assistant/test_assistant_views.py

Lines changed: 0 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,6 @@ def test_cannot_list_assistant_chats_without_valid_workspace(
6262
assert rsp.json()["error"] == "ERROR_USER_NOT_IN_GROUP"
6363

6464

65-
@pytest.mark.django_db
66-
@override_settings(DEBUG=True)
67-
def test_cannot_list_assistant_chats_without_license(
68-
api_client, enterprise_data_fixture
69-
):
70-
user, token = enterprise_data_fixture.create_user_and_token()
71-
workspace = enterprise_data_fixture.create_workspace(user=user)
72-
73-
rsp = api_client.get(
74-
reverse("assistant:list") + f"?workspace_id={workspace.id}",
75-
format="json",
76-
HTTP_AUTHORIZATION=f"JWT {token}",
77-
)
78-
assert rsp.status_code == 402
79-
assert rsp.json()["error"] == "ERROR_FEATURE_NOT_AVAILABLE"
80-
81-
8265
@pytest.mark.django_db
8366
@override_settings(DEBUG=True)
8467
def test_list_assistant_chats(api_client, enterprise_data_fixture):
@@ -181,28 +164,6 @@ def test_cannot_send_message_without_valid_workspace(
181164
assert rsp.json()["error"] == "ERROR_USER_NOT_IN_GROUP"
182165

183166

184-
@pytest.mark.django_db
185-
@override_settings(DEBUG=True)
186-
def test_cannot_send_message_without_license(api_client, enterprise_data_fixture):
187-
"""Test that sending messages requires an enterprise license"""
188-
189-
user, token = enterprise_data_fixture.create_user_and_token()
190-
workspace = enterprise_data_fixture.create_workspace(user=user)
191-
chat_uuid = str(uuid4())
192-
193-
rsp = api_client.post(
194-
reverse("assistant:chat_messages", kwargs={"chat_uuid": chat_uuid}),
195-
data={
196-
"content": "Hello AI",
197-
"ui_context": {"workspace": {"id": workspace.id, "name": workspace.name}},
198-
},
199-
format="json",
200-
HTTP_AUTHORIZATION=f"JWT {token}",
201-
)
202-
assert rsp.status_code == 402
203-
assert rsp.json()["error"] == "ERROR_FEATURE_NOT_AVAILABLE"
204-
205-
206167
@pytest.mark.django_db()
207168
@override_settings(DEBUG=True)
208169
@patch("baserow_enterprise.assistant.handler.Assistant")
@@ -403,30 +364,6 @@ def test_cannot_get_messages_without_valid_chat(api_client, enterprise_data_fixt
403364
assert rsp.json()["error"] == "ERROR_ASSISTANT_CHAT_DOES_NOT_EXIST"
404365

405366

406-
@pytest.mark.django_db
407-
@override_settings(DEBUG=True)
408-
def test_cannot_get_messages_without_license(api_client, enterprise_data_fixture):
409-
"""Test that getting messages requires an enterprise license"""
410-
411-
user, token = enterprise_data_fixture.create_user_and_token()
412-
workspace = enterprise_data_fixture.create_workspace(user=user)
413-
414-
# Create a chat
415-
chat = AssistantChat.objects.create(
416-
user=user, workspace=workspace, title="Test Chat"
417-
)
418-
419-
rsp = api_client.get(
420-
reverse(
421-
"assistant:chat_messages",
422-
kwargs={"chat_uuid": str(chat.uuid)},
423-
),
424-
HTTP_AUTHORIZATION=f"JWT {token}",
425-
)
426-
assert rsp.status_code == 402
427-
assert rsp.json()["error"] == "ERROR_FEATURE_NOT_AVAILABLE"
428-
429-
430367
@pytest.mark.django_db
431368
@override_settings(DEBUG=True)
432369
def test_cannot_get_messages_from_another_users_chat(
@@ -1930,43 +1867,6 @@ def test_cannot_submit_feedback_for_another_users_message(
19301867
assert rsp.json()["error"] == "ERROR_ASSISTANT_CHAT_DOES_NOT_EXIST"
19311868

19321869

1933-
@pytest.mark.django_db
1934-
@override_settings(DEBUG=True)
1935-
def test_cannot_submit_feedback_without_license(api_client, enterprise_data_fixture):
1936-
"""Test that submitting feedback requires an enterprise license"""
1937-
1938-
user, token = enterprise_data_fixture.create_user_and_token()
1939-
workspace = enterprise_data_fixture.create_workspace(user=user)
1940-
# Note: NOT enabling enterprise license
1941-
1942-
# Create chat and messages
1943-
chat = AssistantChat.objects.create(
1944-
user=user, workspace=workspace, title="Test Chat"
1945-
)
1946-
human_message = AssistantChatMessage.objects.create(
1947-
chat=chat, role=AssistantChatMessage.Role.HUMAN, content="Question"
1948-
)
1949-
ai_message = AssistantChatMessage.objects.create(
1950-
chat=chat, role=AssistantChatMessage.Role.AI, content="Answer"
1951-
)
1952-
AssistantChatPrediction.objects.create(
1953-
human_message=human_message,
1954-
ai_response=ai_message,
1955-
prediction={"reasoning": "test"},
1956-
)
1957-
1958-
# Try to submit feedback without license
1959-
rsp = api_client.put(
1960-
reverse("assistant:message_feedback", kwargs={"message_id": ai_message.id}),
1961-
data={"sentiment": "LIKE"},
1962-
format="json",
1963-
HTTP_AUTHORIZATION=f"JWT {token}",
1964-
)
1965-
1966-
assert rsp.status_code == 402
1967-
assert rsp.json()["error"] == "ERROR_FEATURE_NOT_AVAILABLE"
1968-
1969-
19701870
@pytest.mark.django_db
19711871
@override_settings(DEBUG=True)
19721872
def test_submit_feedback_validates_sentiment_choice(

enterprise/web-frontend/modules/baserow_enterprise/components/EnterpriseFeatures.vue

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,6 @@
5151
<i class="iconoir-check premium-features__feature-icon"></i>
5252
{{ $t('enterpriseFeatures.fieldLevelPermissions') }}
5353
</li>
54-
<li
55-
v-if="!hiddenFeatures.includes(enterpriseFeatures.ASSISTANT)"
56-
class="premium-features__feature"
57-
>
58-
<i class="iconoir-sparks premium-features__feature-icon"></i>
59-
{{ $t('enterpriseFeatures.assistant') }}
60-
</li>
6154
<li
6255
v-if="!hiddenFeatures.includes(enterpriseFeatures.SUPPORT)"
6356
class="premium-features__feature"

enterprise/web-frontend/modules/baserow_enterprise/components/assistant/AssistantPanel.vue

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ export default {
130130
handler(newLocation) {
131131
if (!newLocation) return
132132
133+
if (newLocation.type === 'database-view') {
134+
// Don't navigate to deactivated views
135+
const viewType = this.$registry.get('view', newLocation.view_type)
136+
if (!viewType || viewType.isDeactivated(this.workspace.id)) {
137+
return
138+
}
139+
}
140+
133141
const router = this.$router
134142
const store = this.$store
135143
if (
@@ -141,13 +149,18 @@ export default {
141149
newLocation.database_id
142150
)
143151
144-
return (
152+
const isCurrentlyOnTable =
153+
this.$route.name === 'database-table' &&
154+
parseInt(this.$route.params.tableId) ===
155+
parseInt(newLocation.table_id)
156+
157+
const tableLoaded =
145158
database &&
146-
database.tables.find(
147-
(table) => table.id === newLocation.table_id
148-
) &&
149-
(!newLocation.view_id ||
150-
store.getters['view/get'](newLocation.view_id) !== undefined)
159+
database.tables.find((table) => table.id === newLocation.table_id)
160+
const viewLoaded = store.getters['view/get'](newLocation.view_id)
161+
return (
162+
tableLoaded &&
163+
(!isCurrentlyOnTable || !newLocation.view_id || viewLoaded)
151164
)
152165
}).then(() => {
153166
router.push({

enterprise/web-frontend/modules/baserow_enterprise/components/assistant/AssistantSidebarItem.vue

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,8 @@
11
<template>
22
<div v-if="hasPermission">
3-
<li
4-
class="tree__item"
5-
:class="{ 'tree__action--deactivated': deactivated }"
6-
>
3+
<li class="tree__item">
74
<div class="tree__action">
8-
<a
9-
v-if="deactivated"
10-
href="#"
11-
class="tree__link"
12-
@click.prevent="$refs.paidFeaturesModal.show()"
13-
>
14-
<i class="tree__icon iconoir-lock"></i>
15-
<span class="tree__link-text">{{
16-
$t('assistantSidebarItem.title')
17-
}}</span>
18-
</a>
19-
<a
20-
v-else
21-
href="#"
22-
class="tree__link"
23-
@click.prevent="toggleRightSidebar"
24-
>
5+
<a href="#" class="tree__link" @click.prevent="toggleRightSidebar">
256
<i class="tree__icon iconoir-sparks"></i>
267
<span class="tree__link-text">{{
278
$t('assistantSidebarItem.title')
@@ -32,22 +13,13 @@
3213
></i>
3314
</a>
3415
</div>
35-
<PaidFeaturesModal
36-
ref="paidFeaturesModal"
37-
:workspace="workspace"
38-
initial-selected-type="assistant"
39-
></PaidFeaturesModal>
4016
</li>
4117
</div>
4218
</template>
4319

4420
<script>
45-
import EnterpriseFeatures from '@baserow_enterprise/features'
46-
import PaidFeaturesModal from '@baserow_premium/components/PaidFeaturesModal'
47-
4821
export default {
4922
name: 'AssistantSidebarItem',
50-
components: { PaidFeaturesModal },
5123
props: {
5224
workspace: {
5325
type: Object,
@@ -60,9 +32,6 @@ export default {
6032
},
6133
},
6234
computed: {
63-
deactivated() {
64-
return !this.$hasFeature(EnterpriseFeatures.ASSISTANT, this.workspace.id)
65-
},
6635
hasPermission() {
6736
return this.$hasPermission(
6837
'assistant.chat',
@@ -74,7 +43,6 @@ export default {
7443
mounted() {
7544
if (
7645
this.hasPermission &&
77-
!this.deactivated &&
7846
localStorage.getItem('baserow.rightSidebarOpen') !== 'false'
7947
) {
8048
// open the right sidebar if the feature is available

0 commit comments

Comments
 (0)