Skip to content

Commit 2f700a3

Browse files
committed
Allow defining prompts at the category level
1 parent 8179e8f commit 2f700a3

File tree

10 files changed

+174
-10
lines changed

10 files changed

+174
-10
lines changed

ai_summary/indico_ai_summary/client/CategoryManagePrompts.jsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,45 @@
55
// them and/or modify them under the terms of the MIT License;
66
// see the LICENSE file for more details.
77

8+
import _ from 'lodash';
89
import React from 'react';
910
import ReactDOM from 'react-dom';
11+
import {Form as FinalForm} from 'react-final-form';
1012
import {Form} from 'semantic-ui-react';
1113

12-
export default function CategoryManagePrompts() {
13-
return <Form>TODO: Manage Prompts</Form>;
14+
import {indicoAxios} from 'indico/utils/axios';
15+
import {FinalSubmitButton, handleSubmitError} from 'indico/react/forms';
16+
17+
import {FinalPromptManagerField} from './components/PromptManagerField';
18+
19+
export default function CategoryManagePrompts({categoryId, prompts: predefinedPrompts}) {
20+
const onSubmit = async ({prompts}, form) => {
21+
// TODO: url import
22+
const url = `/plugin/ai-summary/manage-category-prompts/${categoryId}`;
23+
try {
24+
await indicoAxios.post(url, {prompts});
25+
form.initialize({prompts});
26+
} catch (error) {
27+
handleSubmitError(error);
28+
}
29+
};
30+
31+
const submitBtn = <FinalSubmitButton label="Save Changes" />;
32+
33+
return (
34+
<FinalForm
35+
onSubmit={onSubmit}
36+
initialValues={{prompts: predefinedPrompts}}
37+
initialValuesEqual={_.isEqual}
38+
subscription={{}}
39+
>
40+
{fprops => (
41+
<Form onSubmit={fprops.handleSubmit}>
42+
<FinalPromptManagerField submitBtn={submitBtn} />
43+
</Form>
44+
)}
45+
</FinalForm>
46+
);
1447
}
1548

1649
window.setupCategoryManagePrompts = function setupCategoryManagePrompts() {

ai_summary/indico_ai_summary/client/components/PromptManagerField.jsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,25 @@
55
// them and/or modify them under the terms of the MIT License;
66
// see the LICENSE file for more details.
77

8+
import _ from 'lodash';
89
import React, {useCallback, useMemo, useState} from 'react';
910
import ReactDOM from 'react-dom';
1011
import {Form, TextArea, Button, Input, Card, Icon} from 'semantic-ui-react';
1112

12-
export default function PromptManagerField({value, onChange}) {
13+
import {FinalField} from 'indico/react/forms';
14+
15+
export function FinalPromptManagerField({submitBtn}) {
16+
return (
17+
<FinalField
18+
name="prompts"
19+
component={PromptManagerField}
20+
submitBtn={submitBtn}
21+
isEqual={_.isEqual}
22+
/>
23+
);
24+
}
25+
26+
export default function PromptManagerField({value, onChange, submitBtn}) {
1327
const addPrompt = () => {
1428
onChange([...value, {name: '', text: ''}]);
1529
};
@@ -27,7 +41,7 @@ export default function PromptManagerField({value, onChange}) {
2741
};
2842

2943
return (
30-
<Form>
44+
<>
3145
{value.map((prompt, idx) => (
3246
<PromptField
3347
key={idx}
@@ -43,8 +57,9 @@ export default function PromptManagerField({value, onChange}) {
4357
Add Prompt
4458
<Icon name="plus" />
4559
</Button>
60+
{submitBtn}
4661
</div>
47-
</Form>
62+
</>
4863
);
4964
}
5065

@@ -81,7 +96,7 @@ function PromptField({name, text, onChangeName, onChangeText, onRemove}) {
8196

8297
export function WTFPromptManagerField({fieldId}) {
8398
const field = useMemo(() => document.getElementById(`${fieldId}-data`), [fieldId]);
84-
const [prompts, setPrompts] = useState(JSON.parse(field.value) || [{name: 'Title', text: ''}]);
99+
const [prompts, setPrompts] = useState(JSON.parse(field.value));
85100

86101
const onChange = useCallback(
87102
v => {
@@ -92,7 +107,11 @@ export function WTFPromptManagerField({fieldId}) {
92107
[field]
93108
);
94109

95-
return <PromptManagerField value={prompts} onChange={onChange} />;
110+
return (
111+
<Form>
112+
<PromptManagerField value={prompts} onChange={onChange} />
113+
</Form>
114+
);
96115
}
97116

98117
window.setupPromptManagerFieldWidget = function setupPromptManagerFieldWidget({fieldId}) {

ai_summary/indico_ai_summary/controllers.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
from webargs import fields
1111
from webargs.flaskparser import use_kwargs
1212

13+
from indico.core.db import db
1314
from indico.modules.categories.controllers.base import RHManageCategoryBase
1415
from indico.modules.events.management.controllers.base import RHManageEventBase
1516
from indico.modules.events.notes.util import get_scheduled_notes
1617

18+
from indico_ai_summary.models.prompt import Prompt
19+
from indico_ai_summary.schemas import PromptSchema
1720
from indico_ai_summary.utils import cern_qwen, chunk_text, html_to_markdown, markdown_to_html
1821
from indico_ai_summary.views import WPCategoryManagePrompts
1922

@@ -27,9 +30,19 @@ def _process_args(self):
2730
RHManageCategoryBase._process_args(self)
2831

2932
def _process_GET(self):
33+
prompts = Prompt.query.with_parent(self.category).all()
34+
serialized_prompts = PromptSchema(many=True).dump(prompts)
3035
return WPCategoryManagePrompts.render_template('manage_category_prompts.html', category=self.category,
3136
active_menu_item=CATEGORY_SIDEMENU_ITEM,
32-
stored_prompts=current_plugin.settings.get('prompts'))
37+
stored_prompts=serialized_prompts)
38+
39+
@use_kwargs({'prompts': fields.List(fields.Nested(PromptSchema), required=True)}, location='json')
40+
def _process_POST(self, prompts):
41+
Prompt.query.with_parent(self.category).delete()
42+
for prompt_data in prompts:
43+
prompt = Prompt(category_id=self.category.id, name=prompt_data['name'], text=prompt_data['text'])
44+
db.session.add(prompt)
45+
return '', 204
3346

3447

3548
class SummarizeEvent(RHManageEventBase):

ai_summary/indico_ai_summary/migrations/.no-header

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Add table for predefined prompts for categories
2+
3+
Revision ID: e2bf94dfa64a
4+
Revises:
5+
Create Date: 2025-09-04 12:08:06.061611
6+
"""
7+
8+
import sqlalchemy as sa
9+
from alembic import op
10+
from sqlalchemy.sql.ddl import CreateSchema, DropSchema
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'e2bf94dfa64a'
15+
down_revision = None
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
op.execute(CreateSchema('plugin_ai_summary'))
22+
op.create_table('predefined_prompts',
23+
sa.Column('id', sa.Integer(), nullable=False),
24+
sa.Column('category_id', sa.Integer(), nullable=False, index=True),
25+
sa.Column('name', sa.Text(), nullable=False),
26+
sa.Column('text', sa.Text(), nullable=False),
27+
sa.ForeignKeyConstraint(['category_id'], ['categories.categories.id']),
28+
sa.PrimaryKeyConstraint('id'),
29+
schema='plugin_ai_summary')
30+
31+
32+
def downgrade():
33+
op.drop_table('predefined_prompts', schema='plugin_ai_summary')
34+
op.execute(DropSchema('plugin_ai_summary'))

ai_summary/indico_ai_summary/models/__init__.py

Whitespace-only changes.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# This file is part of the CERN Indico plugins.
2+
# Copyright (C) 2014 - 2025 CERN
3+
#
4+
# The CERN Indico plugins are free software; you can redistribute
5+
# them and/or modify them under the terms of the MIT License; see
6+
# the LICENSE file for more details.
7+
8+
from indico.core.db.sqlalchemy import db
9+
from indico.util.string import format_repr
10+
11+
12+
class Prompt(db.Model):
13+
"""Predefined LLM prompts"""
14+
15+
__tablename__ = 'predefined_prompts'
16+
__table_args__ = {'schema': 'plugin_ai_summary'}
17+
18+
id = db.Column(
19+
db.Integer,
20+
primary_key=True
21+
)
22+
#: The ID of the category this prompt is defined for
23+
category_id = db.Column(
24+
db.Integer,
25+
db.ForeignKey('categories.categories.id'),
26+
index=True,
27+
nullable=False
28+
)
29+
#: Name of the prompt
30+
name = db.Column(
31+
db.String,
32+
nullable=False
33+
)
34+
#: Text of the prompt
35+
text = db.Column(
36+
db.String,
37+
nullable=False
38+
)
39+
40+
#: The Category this prompt is defined for
41+
category = db.relationship(
42+
'Category',
43+
lazy=True,
44+
backref=db.backref(
45+
'predefined_prompts',
46+
cascade='all, delete-orphan',
47+
lazy='dynamic'
48+
)
49+
)
50+
51+
def __repr__(self):
52+
return format_repr(self, 'id', 'category_id', 'name')

ai_summary/indico_ai_summary/plugin.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
from indico_ai_summary.blueprint import blueprint
2323
from indico_ai_summary.controllers import CATEGORY_SIDEMENU_ITEM
24+
from indico_ai_summary.models.prompt import Prompt
25+
from indico_ai_summary.schemas import PromptSchema
2426
from indico_ai_summary.views import WPCategoryManagePrompts
2527

2628

@@ -61,8 +63,11 @@ def get_blueprints(self):
6163
return blueprint
6264

6365
def _render_summarize_button(self, event):
66+
global_prompts = self.settings.get('prompts')
67+
category_prompts = Prompt.query.with_parent(event.category).all()
68+
all_prompts = global_prompts + PromptSchema(many=True).dump(category_prompts)
6469
return render_plugin_template('summarize_button.html', category=event.category,
65-
event=event, stored_prompts=self.settings.get('prompts'))
70+
event=event, stored_prompts=all_prompts)
6671

6772
def _extend_category_menu(self, sender, category, **kwargs):
6873
return SideMenuItem(CATEGORY_SIDEMENU_ITEM, _('Prompts'),
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from webargs import fields
2+
3+
from indico.core.marshmallow import mm
4+
5+
6+
class PromptSchema(mm.Schema):
7+
name = fields.Str(required=True)
8+
text = fields.Str(required=True)

ai_summary/indico_ai_summary/templates/manage_category_prompts.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% extends 'categories/management/base.html' %}
22

33
{% block title %}
4-
{% trans %}Prompts{% endtrans %}
4+
{% trans %}Predefined Prompts{% endtrans %}
55
{% endblock %}
66

77
{% block content %}

0 commit comments

Comments
 (0)