-
Notifications
You must be signed in to change notification settings - Fork 53
Plugin to summarize event minutes with an LLM #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
zeynepcaysar
wants to merge
82
commits into
indico:master
Choose a base branch
from
zeynepcaysar:summary-rebase
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
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 58454e8
updates
zeynepcaysar d44b547
updates
zeynepcaysar f40c3cb
new file
zeynepcaysar 53e0a18
update the files
zeynepcaysar ccc27cb
Remove __pycache__ from repo
zeynepcaysar 24cb44c
fix
zeynepcaysar 6c75f5c
updates
zeynepcaysar 6211970
update- it doesnt throw an error
zeynepcaysar b0afc65
postman post request works!!
zeynepcaysar 11c6cbe
accessing the event minutes
zeynepcaysar 6fe3c63
updates for button
zeynepcaysar 74b06fb
button things
zeynepcaysar 923594d
added markdown to html function , updates in js file
zeynepcaysar 40dcd8f
updates
zeynepcaysar 6184ca0
updates
zeynepcaysar 88845ef
updates
zeynepcaysar c5e9bcf
updates
zeynepcaysar b440429
index.js
zeynepcaysar 5d18d49
holyshititwashardbutfixed
zeynepcaysar 3caea3b
?
zeynepcaysar 2aedc66
summary modal working
zeynepcaysar 2a31261
updates prototype
zeynepcaysar 56b14dd
updates
zeynepcaysar 318a5df
updates
zeynepcaysar 6a088b1
added save prompt and summary to local storage
zeynepcaysar 83b2c31
minor things
zeynepcaysar 8c901a8
minor updates
zeynepcaysar 08a0e3c
save the summary to event notes - indico axios - hover problem fixed
zeynepcaysar c398e82
minor things
zeynepcaysar 89b2123
last minor updates
zeynepcaysar f0e84f2
removed build and config files from git
zeynepcaysar 5730e45
update requirements.txt
zeynepcaysar f25c09b
minor - update pyproject
zeynepcaysar 967b10e
minor
zeynepcaysar 90e2542
consistent project structure
zeynepcaysar bf424ed
changed the folder structure
zeynepcaysar 43db1ff
deleted plugin files from top level
zeynepcaysar c2fbb0d
added new lines
zeynepcaysar 15e8114
minor things
zeynepcaysar a6179fb
fixed linting issues and changed plugin name
zeynepcaysar b3042e2
fix linting errors
zeynepcaysar 00cc015
changed plugin name
zeynepcaysar a213f5a
updates - still get keyerror:object type for settingsform password field
zeynepcaysar 6335ab6
updates - settings form works
zeynepcaysar 1b9777a
updates - settings form works
zeynepcaysar 3371e75
fix lint issues
zeynepcaysar f4103b7
fix getting the prompt
zeynepcaysar 2a455e2
made the ind summarize button file more modular
zeynepcaysar 8266c93
remove onopen and onClose for modal visibility and update try catch …
zeynepcaysar b553206
minor updates
zeynepcaysar d0cb8d2
moved the dropdown and manage button outside of card
zeynepcaysar 8451a60
update promptselector function
zeynepcaysar f576710
add no summary placeholder
zeynepcaysar fcbcc10
adjust layout
zeynepcaysar ae411b9
fix delete prompt button size
zeynepcaysar 6928e46
updates
zeynepcaysar 6b4eb6e
minior fix
zeynepcaysar d522229
minor updates
zeynepcaysar c61b2fe
changed hook name
zeynepcaysar 0090a6c
Fix package name
tomasr8 e687c52
Better naming
tomasr8 ac0bea0
Manage prompts from plugin settings
tomasr8 5c73a3c
Lint
tomasr8 f31df91
Add empty category side menu
tomasr8 9056e4c
WIP: Add category side menu item
tomasr8 09a0eaa
Fix linter
tomasr8 5e30198
Improve predefined prompts field
tomasr8 8a5dae6
Allow defining prompts at the category level
tomasr8 df99d81
Add generic LLM provider support
GovernmentPlates 08cad74
Implement (basic) streaming + UI changes
GovernmentPlates 11eba55
Clean up llm_interface
GovernmentPlates 0fffdd8
Drop `EventSource` for SSE
GovernmentPlates 8197acb
Start addressing reveiw comments
GovernmentPlates ed0938a
Offload getting prompts as API call
GovernmentPlates b916f01
Support copying summaries in markdown
GovernmentPlates 086393d
Add experimental feature notice
GovernmentPlates 3f196c3
Use generic template hook from core
GovernmentPlates b497bcb
Add custom prompt option
GovernmentPlates c8040d0
Cleanup pyproject.toml
ThiefMaster 1d95f03
Use -dev version
ThiefMaster afac023
Nitpicks
GovernmentPlates File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
54
ai_summary/indico_ai_summary/client/CategoryManagePrompts.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
134
ai_summary/indico_ai_summary/client/components/ActionButtons.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } |
41 changes: 41 additions & 0 deletions
41
ai_summary/indico_ai_summary/client/components/ActionButtons.module.scss
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
124
ai_summary/indico_ai_summary/client/components/PromptManagerField.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}) { | ||
| 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)); | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.