Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
c7b06e5
add the necessary files
zeynepcaysar Jul 14, 2025
58454e8
updates
zeynepcaysar Jul 18, 2025
d44b547
updates
zeynepcaysar Jul 23, 2025
f40c3cb
new file
zeynepcaysar Jul 23, 2025
53e0a18
update the files
zeynepcaysar Jul 29, 2025
ccc27cb
Remove __pycache__ from repo
zeynepcaysar Jul 29, 2025
24cb44c
fix
zeynepcaysar Jul 29, 2025
6c75f5c
updates
zeynepcaysar Jul 30, 2025
6211970
update- it doesnt throw an error
zeynepcaysar Aug 1, 2025
b0afc65
postman post request works!!
zeynepcaysar Aug 1, 2025
11c6cbe
accessing the event minutes
zeynepcaysar Aug 1, 2025
6fe3c63
updates for button
zeynepcaysar Aug 5, 2025
74b06fb
button things
zeynepcaysar Aug 5, 2025
923594d
added markdown to html function , updates in js file
zeynepcaysar Aug 6, 2025
40dcd8f
updates
zeynepcaysar Aug 7, 2025
6184ca0
updates
zeynepcaysar Aug 7, 2025
88845ef
updates
zeynepcaysar Aug 7, 2025
c5e9bcf
updates
zeynepcaysar Aug 7, 2025
b440429
index.js
zeynepcaysar Aug 7, 2025
5d18d49
holyshititwashardbutfixed
zeynepcaysar Aug 7, 2025
3caea3b
?
zeynepcaysar Aug 8, 2025
2aedc66
summary modal working
zeynepcaysar Aug 8, 2025
2a31261
updates prototype
zeynepcaysar Aug 13, 2025
56b14dd
updates
zeynepcaysar Aug 15, 2025
318a5df
updates
zeynepcaysar Aug 15, 2025
6a088b1
added save prompt and summary to local storage
zeynepcaysar Aug 15, 2025
83b2c31
minor things
zeynepcaysar Aug 15, 2025
8c901a8
minor updates
zeynepcaysar Aug 18, 2025
08a0e3c
save the summary to event notes - indico axios - hover problem fixed
zeynepcaysar Aug 18, 2025
c398e82
minor things
zeynepcaysar Aug 19, 2025
89b2123
last minor updates
zeynepcaysar Aug 19, 2025
f0e84f2
removed build and config files from git
zeynepcaysar Aug 19, 2025
5730e45
update requirements.txt
zeynepcaysar Aug 19, 2025
f25c09b
minor - update pyproject
zeynepcaysar Aug 20, 2025
967b10e
minor
zeynepcaysar Aug 20, 2025
90e2542
consistent project structure
zeynepcaysar Aug 20, 2025
bf424ed
changed the folder structure
zeynepcaysar Aug 20, 2025
43db1ff
deleted plugin files from top level
zeynepcaysar Aug 20, 2025
c2fbb0d
added new lines
zeynepcaysar Aug 20, 2025
15e8114
minor things
zeynepcaysar Aug 20, 2025
a6179fb
fixed linting issues and changed plugin name
zeynepcaysar Aug 20, 2025
b3042e2
fix linting errors
zeynepcaysar Aug 20, 2025
00cc015
changed plugin name
zeynepcaysar Aug 21, 2025
a213f5a
updates - still get keyerror:object type for settingsform password field
zeynepcaysar Aug 21, 2025
6335ab6
updates - settings form works
zeynepcaysar Aug 22, 2025
1b9777a
updates - settings form works
zeynepcaysar Aug 22, 2025
3371e75
fix lint issues
zeynepcaysar Aug 22, 2025
f4103b7
fix getting the prompt
zeynepcaysar Aug 22, 2025
2a455e2
made the ind summarize button file more modular
zeynepcaysar Aug 25, 2025
8266c93
remove onopen and onClose for modal visibility and update try catch …
zeynepcaysar Aug 25, 2025
b553206
minor updates
zeynepcaysar Aug 26, 2025
d0cb8d2
moved the dropdown and manage button outside of card
zeynepcaysar Aug 26, 2025
8451a60
update promptselector function
zeynepcaysar Aug 26, 2025
f576710
add no summary placeholder
zeynepcaysar Aug 26, 2025
fcbcc10
adjust layout
zeynepcaysar Aug 26, 2025
ae411b9
fix delete prompt button size
zeynepcaysar Aug 26, 2025
6928e46
updates
zeynepcaysar Aug 27, 2025
6b4eb6e
minior fix
zeynepcaysar Aug 27, 2025
d522229
minor updates
zeynepcaysar Aug 27, 2025
c61b2fe
changed hook name
zeynepcaysar Aug 28, 2025
0090a6c
Fix package name
tomasr8 Sep 2, 2025
e687c52
Better naming
tomasr8 Sep 2, 2025
ac0bea0
Manage prompts from plugin settings
tomasr8 Sep 3, 2025
5c73a3c
Lint
tomasr8 Sep 3, 2025
f31df91
Add empty category side menu
tomasr8 Sep 3, 2025
9056e4c
WIP: Add category side menu item
tomasr8 Sep 3, 2025
09a0eaa
Fix linter
tomasr8 Sep 3, 2025
5e30198
Improve predefined prompts field
tomasr8 Sep 4, 2025
8a5dae6
Allow defining prompts at the category level
tomasr8 Sep 4, 2025
df99d81
Add generic LLM provider support
GovernmentPlates Oct 9, 2025
08cad74
Implement (basic) streaming + UI changes
GovernmentPlates Oct 16, 2025
11eba55
Clean up llm_interface
GovernmentPlates Oct 18, 2025
0fffdd8
Drop `EventSource` for SSE
GovernmentPlates Oct 18, 2025
8197acb
Start addressing reveiw comments
GovernmentPlates Oct 24, 2025
ed0938a
Offload getting prompts as API call
GovernmentPlates Oct 31, 2025
b916f01
Support copying summaries in markdown
GovernmentPlates Nov 7, 2025
086393d
Add experimental feature notice
GovernmentPlates Nov 7, 2025
3f196c3
Use generic template hook from core
GovernmentPlates Nov 10, 2025
b497bcb
Add custom prompt option
GovernmentPlates Nov 10, 2025
c8040d0
Cleanup pyproject.toml
ThiefMaster Nov 12, 2025
1d95f03
Use -dev version
ThiefMaster Nov 12, 2025
afac023
Nitpicks
GovernmentPlates Nov 12, 2025
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
3 changes: 3 additions & 0 deletions ai_summary/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# indico-plugin-ai-summary

This plugin automatically generates summaries of meeting minutes stored in Indico utilizing an open-source Large Language Model (LLM). It enables users to select the minutes they wish to summarize, to choose pre-written prompts or write custom ones to generate the summaries. summary-rebase
1 change: 1 addition & 0 deletions ai_summary/indico_ai_summary/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

18 changes: 18 additions & 0 deletions ai_summary/indico_ai_summary/blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2025 CERN
#
# The Indico plugins are free software; you can redistribute
# them and/or modify them under the terms of the MIT License;
# see the LICENSE file for more details.

from indico.core.plugins import IndicoPluginBlueprint

from indico_ai_summary.controllers import RHLLMPrompts, RHManageCategoryPrompts, RHSummarizeEvent


blueprint = IndicoPluginBlueprint('ai_summary', __name__, url_prefix='/plugin/ai-summary')

blueprint.add_url_rule('!/category/<int:category_id>/manage/prompts', 'manage_category_prompts',
RHManageCategoryPrompts, methods=('GET', 'POST'))
blueprint.add_url_rule('/llm-prompts/<int:event_id>', 'llm_prompts', RHLLMPrompts, methods=('GET',))
blueprint.add_url_rule('/summarize-event/<int:event_id>', 'summarize_event', RHSummarizeEvent, methods=('POST',))
54 changes: 54 additions & 0 deletions ai_summary/indico_ai_summary/client/CategoryManagePrompts.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This file is part of the Indico plugins.
// Copyright (C) 2002 - 2025 CERN
//
// The Indico plugins are free software; you can redistribute
// them and/or modify them under the terms of the MIT License;
// see the LICENSE file for more details.

import manageCategoryPrompts from 'indico-url:plugin_ai_summary.manage_category_prompts';

import _ from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
import {Form as FinalForm} from 'react-final-form';
import {Form} from 'semantic-ui-react';

import {indicoAxios} from 'indico/utils/axios';
import {FinalSubmitButton, handleSubmitError} from 'indico/react/forms';

import {FinalPromptManagerField} from './components/PromptManagerField';

export default function CategoryManagePrompts({categoryId, prompts: predefinedPrompts}) {
const onSubmit = async ({prompts}, form) => {
try {
await indicoAxios.post(manageCategoryPrompts({category_id: categoryId}), {prompts});
form.initialize({prompts});
} catch (error) {
handleSubmitError(error);
}
};

const submitBtn = <FinalSubmitButton label="Save Changes" />;

return (
<FinalForm
onSubmit={onSubmit}
initialValues={{prompts: predefinedPrompts}}
initialValuesEqual={_.isEqual}
subscription={{}}
>
{fprops => (
<Form onSubmit={fprops.handleSubmit}>
<FinalPromptManagerField submitBtn={submitBtn} />
</Form>
)}
</FinalForm>
);
}

window.setupCategoryManagePrompts = function setupCategoryManagePrompts() {
const container = document.getElementById('plugin-ai-summary-prompts');
const categoryId = parseInt(container.dataset.categoryId, 10);
const prompts = JSON.parse(container.dataset.prompts);
ReactDOM.render(<CategoryManagePrompts categoryId={categoryId} prompts={prompts} />, container);
};
134 changes: 134 additions & 0 deletions ai_summary/indico_ai_summary/client/components/ActionButtons.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// This file is part of the Indico plugins.
// Copyright (C) 2002 - 2025 CERN
//
// The Indico plugins are free software; you can redistribute
// them and/or modify them under the terms of the MIT License;
// see the LICENSE file for more details.

import React, {useState, useEffect} from 'react';
import {Button, ButtonGroup, Icon, Popup, Dropdown, DropdownMenu, DropdownItem} from 'semantic-ui-react';
import {Translate} from '../i18n';

import './ActionButtons.module.scss';

function CopyToClipboardButton({summaryHtml, summaryMarkdown}) {
const [isCopied, setIsCopied] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

useEffect(() => {
if (isCopied) {
const timer = setTimeout(() => setIsCopied(false), 2000);
return () => clearTimeout(timer);
}
}, [isCopied]);

function handleCopy(text) {
navigator.clipboard.writeText(text);
setIsCopied(true);
}

const button = (
<Popup
trigger={
<Button icon styleName="copy-button">
<Icon name={isCopied ? 'check' : 'copy'} color={isCopied ? 'green' : undefined} />
<Icon name="dropdown" />
</Button>
}
content={isCopied ? Translate.string('Copied!') : Translate.string('Copy summary')}
position="top center"
disabled={isDropdownOpen}
/>
);

return (
<Dropdown
trigger={button}
onOpen={() => setIsDropdownOpen(true)}
onClose={() => setIsDropdownOpen(false)}
>
<DropdownMenu>
<DropdownItem
onClick={() => {
handleCopy(summaryHtml);
}}
icon="code"
text={Translate.string('Copy HTML')}
/>
<DropdownItem
onClick={() => {
handleCopy(summaryMarkdown);
}}
icon="file alternate outline"
text={Translate.string('Copy Markdown')}
/>
</DropdownMenu>
</Dropdown>
)
}

function SaveSummaryButton({onSave, saving}) {
const [showSavedIcon, setShowSavedIcon] = useState(false);
const [prevSaving, setPrevSaving] = useState(false);

useEffect(() => {
if (prevSaving && !saving) {
setShowSavedIcon(true);
const timer = setTimeout(() => setShowSavedIcon(false), 2000);
return () => clearTimeout(timer);
}
setPrevSaving(saving);
}, [saving, prevSaving]);

return (
<Popup
trigger={
<Button primary={!showSavedIcon} basic={showSavedIcon} icon onClick={onSave} disabled={saving} styleName="save-button">
{saving ? (
<Icon name="spinner" loading />
) : showSavedIcon ? (
<Icon name="check" color="green" />
) : (
<Icon name="save" />
)}
</Button>
}
content={
saving
? Translate.string('Saving summary...')
: showSavedIcon
? Translate.string('Saved!')
: Translate.string('Save the generated summary to the meeting minutes')
}
position="top center"
/>
);
}

export default function ActionButtons({loading, error, summaryHtml, summaryMarkdown, saving, onSave, onRetry}) {
if (!loading) {
return (
<div styleName="action-buttons-container">
<ButtonGroup basic>
{summaryHtml && !error && (
<CopyToClipboardButton summaryHtml={summaryHtml} summaryMarkdown={summaryMarkdown} />
)}
<Popup trigger={
<Button icon onClick={onRetry}>
<Icon name="undo" />
</Button>
}
content={Translate.string('Retry generating summary')}
position="top center"
/>
</ButtonGroup>

{summaryHtml && !error && (
<SaveSummaryButton onSave={onSave} saving={saving} />
)}
</div>
);
}

return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// This file is part of the Indico plugins.
// Copyright (C) 2002 - 2025 CERN
//
// The Indico plugins are free software; you can redistribute
// them and/or modify them under the terms of the MIT License;
// see the LICENSE file for more details.

@use 'base/palette' as *;

.action-buttons {
display: flex;
justify-content: space-between;
align-items: baseline;

.llm-info {
color: $dark-gray;
}
}

.action-buttons-container {
display: flex;
align-items: baseline;
gap: 0;

.copy-button {
&:hover {
box-shadow: none !important;
}

&:focus {
box-shadow: none !important;
}

display: flex;
gap: 5px;
}

.save-button {
margin-left: 10px;
}
}
124 changes: 124 additions & 0 deletions ai_summary/indico_ai_summary/client/components/PromptManagerField.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// This file is part of the Indico plugins.
// Copyright (C) 2002 - 2025 CERN
//
// The Indico plugins are free software; you can redistribute
// them and/or modify them under the terms of the MIT License;
// see the LICENSE file for more details.

import _ from 'lodash';
import React, {useCallback, useMemo, useState} from 'react';
import ReactDOM from 'react-dom';
import {Form, TextArea, Button, Input, Card, Icon} from 'semantic-ui-react';
import {Translate} from '../i18n';

import {FinalField} from 'indico/react/forms';

import './PromptManagerField.module.scss';

export function FinalPromptManagerField({submitBtn}) {
Comment thread
GovernmentPlates marked this conversation as resolved.
return (
<FinalField
name="prompts"
component={PromptManagerField}
submitBtn={submitBtn}
isEqual={_.isEqual}
/>
);
}

export default function PromptManagerField({value, onChange, submitBtn}) {
const addPrompt = () => {
onChange([...value, {name: '', text: ''}]);
};

const removePrompt = idx => {
onChange(value.filter((_, i) => i !== idx));
};

const updatePrompt = (idx, text) => {
onChange(value.map((p, i) => (i === idx ? {name: p.name, text} : p)));
};

const updateName = (idx, name) => {
onChange(value.map((p, i) => (i === idx ? {name, text: p.text} : p)));
};

return (
<>
{value.map((prompt, idx) => (
<PromptField
key={idx}
name={prompt.name}
text={prompt.text}
onRemove={() => removePrompt(idx)}
onChangeText={text => updatePrompt(idx, text)}
onChangeName={name => updateName(idx, name)}
/>
))}
<div styleName="add-prompt-buttons">
<Button icon type="button" labelPosition="right" onClick={addPrompt}>
<Translate>Add Prompt</Translate>
<Icon name="plus" />
</Button>
{submitBtn}
</div>
</>
);
}

function PromptField({name, text, onChangeName, onChangeText, onRemove}) {
return (
<Card fluid>
<Card.Content>
<div styleName="delete-prompt-button">
<Button icon type="button" compact onClick={onRemove}>
<Icon name="trash alternate outline" />
</Button>
</div>
<Form.Field>
<Translate as="label">Prompt Name</Translate>
<Input
value={name}
placeholder={Translate.string('e.g. Summarizer...')}
onChange={(_, {value: v}) => onChangeName(v)}
/>
</Form.Field>
<Form.Field>
<Translate as="label">Prompt Text</Translate>
<TextArea
value={text}
placeholder={Translate.string('Enter your prompt here...')}
onChange={(_, {value: v}) => onChangeText(v)}
rows={10}
/>
</Form.Field>
</Card.Content>
</Card>
);
}

export function WTFPromptManagerField({fieldId}) {
const field = useMemo(() => document.getElementById(`${fieldId}-data`), [fieldId]);
const [prompts, setPrompts] = useState(JSON.parse(field.value));

const onChange = useCallback(
v => {
field.value = JSON.stringify(v);
setPrompts(v);
field.dispatchEvent(new Event('change', {bubbles: true}));
},
[field]
);

return (
<div className="ui form">
<Form>
<PromptManagerField value={prompts} onChange={onChange} />
</Form>
</div>
);
}

window.setupPromptManagerFieldWidget = function setupPromptManagerFieldWidget({fieldId}) {
ReactDOM.render(<WTFPromptManagerField fieldId={fieldId} />, document.getElementById(fieldId));
};
Loading
Loading