Skip to content

Commit a7cae35

Browse files
✨ AI answers user questions in support center (🗃️⚠️🚨) (#8525)
1 parent 66aebbf commit a7cae35

29 files changed

+571
-92
lines changed

packages/models-library/src/models_library/conversations.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ class ConversationMessageType(StrAutoEnum):
3838
#
3939

4040

41-
IsSupportUser: TypeAlias = bool
41+
class ConversationUserType(StrAutoEnum):
42+
SUPPORT_USER = auto()
43+
CHATBOT_USER = auto()
44+
REGULAR_USER = auto()
4245

4346

4447
class ConversationGetDB(BaseModel):
@@ -58,7 +61,29 @@ class ConversationGetDB(BaseModel):
5861
modified: datetime
5962
last_message_created_at: datetime
6063

61-
model_config = ConfigDict(from_attributes=True)
64+
model_config = ConfigDict(
65+
from_attributes=True,
66+
json_schema_extra={
67+
"examples": [
68+
# Support message
69+
{
70+
"conversation_id": "42838344-03de-4ce2-8d93-589a5dcdfd05",
71+
"product_name": "osparc",
72+
"name": "test_conversation",
73+
"project_uuid": "42838344-03de-4ce2-8d93-589a5dcdfd05",
74+
"user_group_id": "789",
75+
"type": ConversationType.SUPPORT,
76+
"extra_context": {},
77+
"fogbugz_case_id": None,
78+
"is_read_by_user": False,
79+
"is_read_by_support": False,
80+
"created": "2024-01-01T12:00:00",
81+
"modified": "2024-01-01T12:00:00",
82+
"last_message_created_at": "2024-01-01T12:00:00",
83+
}
84+
]
85+
},
86+
)
6287

6388

6489
class ConversationMessageGetDB(BaseModel):

packages/models-library/src/models_library/rabbitmq_messages.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from common_library.basic_types import DEFAULT_FACTORY
1010
from pydantic import BaseModel, Field
1111

12+
from .conversations import ConversationGetDB, ConversationMessageID
1213
from .products import ProductName
1314
from .progress_bar import ProgressReport
1415
from .projects import ProjectID
@@ -93,6 +94,17 @@ def routing_key(self) -> str | None:
9394
return None
9495

9596

97+
class WebserverChatbotRabbitMessage(RabbitMessageBase):
98+
channel_name: Literal["simcore.services.webserver-chatbot"] = (
99+
"simcore.services.webserver-chatbot"
100+
)
101+
conversation: ConversationGetDB
102+
last_message_id: ConversationMessageID
103+
104+
def routing_key(self) -> str | None:
105+
return None
106+
107+
96108
class ProgressType(StrAutoEnum):
97109
COMPUTATION_RUNNING = auto() # NOTE: this is the original only progress report
98110

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""add chatbot user id to products table
2+
3+
Revision ID: e1c7e416461c
4+
Revises: f641b3eacafd
5+
Create Date: 2025-10-16 07:51:44.033767+00:00
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "e1c7e416461c"
14+
down_revision = "f641b3eacafd"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column(
22+
"products", sa.Column("support_chatbot_user_id", sa.BigInteger(), nullable=True)
23+
)
24+
op.create_foreign_key(
25+
"fk_products_support_chatbot_user_id",
26+
"products",
27+
"users",
28+
["support_chatbot_user_id"],
29+
["id"],
30+
onupdate="CASCADE",
31+
ondelete="SET NULL",
32+
)
33+
# ### end Alembic commands ###
34+
35+
36+
def downgrade():
37+
# ### commands auto generated by Alembic - please adjust! ###
38+
op.drop_constraint(
39+
"fk_products_support_chatbot_user_id", "products", type_="foreignkey"
40+
)
41+
op.drop_column("products", "support_chatbot_user_id")
42+
# ### end Alembic commands ###
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Add base_url to product table
2+
3+
Revision ID: ff13501db935
4+
Revises: e1c7e416461c
5+
Create Date: 2025-10-17 14:48:02.509847+00:00
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "ff13501db935"
14+
down_revision = "e1c7e416461c"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column("products", sa.Column("base_url", sa.String(), nullable=True))
22+
# ### end Alembic commands ###
23+
24+
op.execute("UPDATE products SET base_url = 'http://CHANGE_ME.localhost'")
25+
26+
op.alter_column("products", "base_url", existing_type=sa.String(), nullable=True)
27+
28+
29+
def downgrade():
30+
# ### commands auto generated by Alembic - please adjust! ###
31+
op.drop_column("products", "base_url")
32+
# ### end Alembic commands ###

packages/postgres-database/src/simcore_postgres_database/models/products.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .base import metadata
2020
from .groups import groups
2121
from .jinja2_templates import jinja2_templates
22+
from .users import users
2223

2324
# NOTE: a default entry is created in the table Product
2425
# see packages/postgres-database/src/simcore_postgres_database/migration/versions/350103a7efbd_modified_products_table.py
@@ -153,6 +154,12 @@ class ProductLoginSettingsDict(TypedDict, total=False):
153154
nullable=False,
154155
doc="Regular expression that matches product hostname from an url string",
155156
),
157+
sa.Column(
158+
"base_url",
159+
sa.String,
160+
nullable=False,
161+
doc="Product base URL (scheme + host), ex. https://osparc.io",
162+
),
156163
# EMAILS --------------------
157164
sa.Column(
158165
"support_email",
@@ -281,6 +288,19 @@ class ProductLoginSettingsDict(TypedDict, total=False):
281288
nullable=True,
282289
doc="Group associated to this product support",
283290
),
291+
sa.Column(
292+
"support_chatbot_user_id",
293+
sa.BigInteger,
294+
sa.ForeignKey(
295+
users.c.id,
296+
name="fk_products_support_chatbot_user_id",
297+
ondelete=RefActions.SET_NULL,
298+
onupdate=RefActions.CASCADE,
299+
),
300+
unique=False,
301+
nullable=True,
302+
doc="User associated to this product chatbot user",
303+
),
284304
sa.Column(
285305
"support_assigned_fogbugz_person_id",
286306
sa.BigInteger,

packages/postgres-database/tests/conftest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,9 @@ async def _creator(product_name: str) -> Row:
354354
async with asyncpg_engine.begin() as connection:
355355
result = await connection.execute(
356356
sa.insert(products)
357-
.values(name=product_name, host_regex=".*")
357+
.values(
358+
name=product_name, host_regex=".*", base_url="https://example.com"
359+
)
358360
.returning(sa.literal_column("*"))
359361
)
360362
assert result

packages/postgres-database/tests/products/test_models_products.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,21 @@ async def test_jinja2_templates_table(
6767
{
6868
"name": "osparc",
6969
"host_regex": r"^osparc.",
70+
"base_url": "https://osparc.io",
7071
"registration_email_template": registration_email_template,
7172
},
7273
{
7374
"name": "s4l",
7475
"host_regex": r"(^s4l[\.-])|(^sim4life\.)",
76+
"base_url": "https://sim4life.info",
7577
"short_name": "s4l web",
7678
"registration_email_template": registration_email_template,
7779
},
7880
{
7981
"name": "tis",
8082
"short_name": "TIP",
8183
"host_regex": r"(^ti.[\.-])|(^ti-solution\.)",
84+
"base_url": "https://tis.com",
8285
},
8386
]:
8487
# aiopg doesn't support executemany!!
@@ -133,6 +136,7 @@ async def test_insert_select_product(
133136
"display_name": "o²S²PARC",
134137
"short_name": "osparc",
135138
"host_regex": r"([\.-]{0,1}osparc[\.-])",
139+
"base_url": "https://osparc.io",
136140
"support_email": "foo@osparc.io",
137141
"twilio_messaging_sid": None,
138142
"vendor": Vendor(

packages/postgres-database/tests/test_utils_services.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ def services_fixture(faker: Faker, pg_sa_engine: sa.engine.Engine) -> ServicesFi
151151
"display_name": "Product Osparc",
152152
"short_name": "osparc",
153153
"host_regex": r"^osparc.",
154+
"base_url": "https://osparc.io",
154155
"priority": 0,
155156
}
156157
product_name = conn.execute(

packages/postgres-database/tests/test_utils_services_environments.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ class ExpectedSecrets(NamedTuple):
3434
async def product_name(connection: SAConnection) -> str:
3535
a_product_name = "a_prod"
3636
await connection.execute(
37-
products.insert().values(name=a_product_name, host_regex="")
37+
products.insert().values(
38+
name=a_product_name, host_regex="", base_url="http://example.com"
39+
)
3840
)
3941
yield a_product_name
4042
await connection.execute(products.delete())

packages/pytest-simcore/src/pytest_simcore/helpers/faker_factories.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ def random_product(
306306
"display_name": suffix.capitalize().replace("_", " "),
307307
"short_name": suffix[:4],
308308
"host_regex": r"[a-zA-Z0-9]+\.com",
309+
"base_url": f"https://{suffix}.com",
309310
"support_email": f"support@{suffix}.io",
310311
"product_owners_email": fake.random_element(
311312
elements=[f"product-owners@{suffix}.io", None]

0 commit comments

Comments
 (0)