diff --git a/ai.py b/ai.py index 2e33c67..4934625 100644 --- a/ai.py +++ b/ai.py @@ -28,13 +28,12 @@ def get_answer(question: str, personality: str, key: str): response = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ - {'role': 'system', 'content': personality}, - {'role': 'system', 'content': 'Your answers are rather concise.'}, + {'role': 'system', 'content': f'{personality} Your answers are rather concise.'}, {'role': 'user', 'content': question}, ] ) - return response['choices'][0]['text'] + return response['choices'][0]['message']['content'] def classify_answer(question: str, personality: str, answer: str, key: str): @@ -42,8 +41,7 @@ def classify_answer(question: str, personality: str, answer: str, key: str): response = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ - {'role': 'system', 'content': personality}, - {'role': 'system', 'content': 'Your answers are rather concise.'}, + {'role': 'system', 'content': f'{personality} Your answers are rather concise.'}, {'role': 'user', 'content': question}, {'role': 'system', 'content': answer}, {'role': 'user', 'content': 'Summarize you answer with a simple "yes" or "no" (answering with a single word). If (and only if) that\'s not possible, instead of answering with "yes" or "no", list conditions under which the answer would be "yes".'}, @@ -53,9 +51,9 @@ def classify_answer(question: str, personality: str, answer: str, key: str): content = response['choices'][0]['message']['content'] if re.match('^\W*yes\W*$', content, re.IGNORECASE): - return {'classification': True, 'conditions': None} + return {'status': 'yes', 'conditions': None} if re.match('^\W*no\W*$', content, re.IGNORECASE): - return {'classification': False, 'conditions': None} + return {'status': 'no', 'conditions': None} - return {'classification': True, 'conditions': content} + return {'status': 'conditional', 'conditions': content} diff --git a/assets/style.css b/assets/style.css index b8ffec0..f910292 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1,11 +1,21 @@ -body { - background: black; +html { + background: repeating-linear-gradient(45deg, rgb(0 0 0) 0px, rgb(0 0 0) 20px, #211200 20px, #211200 40px); + min-height: 100vh; } -input { +body { + margin: 15px; +} + +.system { background: black; border: 2px solid #ff8d00; - color: #ff8d00; + padding: 15px; + max-width: 1500px; + display: flex; + flex-direction: column; + position: relative; + margin: 0 auto; } .magi { @@ -87,6 +97,7 @@ input { display: flex; background: #ff8d00; padding: 2px; + cursor: pointer; } .magi>.wise-man>.inner { @@ -161,4 +172,58 @@ input { .connection.balthasar-melchior { grid-area: 4 / 5 / 5 / 6; transform: rotate(54deg); +} + +.modal { + border: 2px solid #ff8d00; + color: #ff8d00; + font-family: 'Helvetica'; + font-family: 'Lucida Console'; + position: absolute; + background: black; + max-width: 40vw; + top: 10vh; + right: 10vw; +} + +.modal>.modal-header { + display: flex; + flex-direction: row; + border-bottom: 2px solid #ff8d00; +} + +.modal>.modal-header>.modal-title { + flex: 1; + padding: 5px; + background: repeating-linear-gradient(45deg, rgb(0 0 0) 0px, rgb(0 0 0) 10px, #211200 10px, #211200 20px); +} + +.modal>.modal-header>.close { + padding: 5px; + border-left: 2px solid #ff8d00; + cursor: pointer; +} + +.modal>.modal-body { + display: grid; + grid-template-columns: auto auto; + column-gap: 10px; + padding: 10px; +} + +.input-container { + display: grid; + grid-template-columns: auto 1fr; + color: #ff8d00; + font-family: 'Helvetica'; + font-family: 'Lucida Console'; + column-gap: 10px; + row-gap: 5px; + margin-top: 15px; +} + +.input-container > input { + background: #211200; + border: 2px solid #ff8d00; + color: #ff8d00; } \ No newline at end of file diff --git a/components/modal.js b/components/modal.js new file mode 100644 index 0000000..4cb2c9d --- /dev/null +++ b/components/modal.js @@ -0,0 +1,34 @@ +import React from 'react'; + +export default function Modal({ setProps, name, is_open, question, answer }) { + if (!is_open) return null; + + const questionId = question.id; + const answerId = answer.id; + + if (questionId !== answerId) + answer = { id: questionId, status: 'error', error: 'questionId !== answerId' }; + + const close = () => { + setProps({ is_open: false }); + }; + + return React.createElement('div', { className: 'modal' }, [ + React.createElement('div', { className: 'modal-header' }, [ + React.createElement('div', { className: 'modal-title' }, [name]), + React.createElement('div', { className: 'close', onClick: close }, ['X']), + ]), + React.createElement('div', { className: 'modal-body' }, [ + React.createElement('div', {}, 'question: '), + React.createElement('div', {}, question.query), + React.createElement('div', {}, 'status: '), + React.createElement('div', {}, answer.status), + React.createElement('div', {}, 'error: '), + React.createElement('div', {}, answer.error), + React.createElement('div', {}, 'conditions: '), + React.createElement('div', {}, answer.conditions), + React.createElement('div', {}, 'full response: '), + React.createElement('div', {}, answer.response) + ]), + ]); +} \ No newline at end of file diff --git a/components/wise_man.js b/components/wise_man.js index de58923..ec8f17e 100644 --- a/components/wise_man.js +++ b/components/wise_man.js @@ -11,22 +11,33 @@ function useColor(status) { if (status === 'info') return '#3caee0'; - if (status == 'conditional') + if (status === 'conditional') return 'repeating-linear-gradient(56deg, rgb(82, 230, 145) 0px, rgb(82, 230, 145) 30px, #82cd68 30px, #82cd68 60px)'; + if (status === 'error') + return 'black'; + throw new Error(`Invalid status: ${status}`); } export default function WiseMan(props) { - const { setProps, name, order_number, question_id, answer } = props; + const { setProps, name, order_number, question_id, answer, n_clicks } = props; const fullName = `${name.toUpperCase()} • ${order_number}`; const color = useColor(answer['status']); const processing = question_id !== answer['id']; - return React.createElement('div', { className: `wise-man ${name}` }, + const onClick = () => { + setProps({ n_clicks: n_clicks + 1 }); + }; + + return React.createElement('div', { className: `wise-man ${name}`, onClick: onClick, key: name }, [ React.createElement('div', { className: `inner ${processing ? 'flicker' : ''}`, style: { background: color } }, [ fullName ]) ]) -} \ No newline at end of file +} + +WiseMan.defaultProps = { + n_clicks: 0 +}; \ No newline at end of file diff --git a/main.py b/main.py index 373fd09..45c3be9 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,9 @@ from dataclasses import dataclass import os -from dash_extensions.enrich import Dash, Input, Output, State, Trigger, callback, ALL, MATCH +from dash_extensions.enrich import Dash, Input, Output, State, Trigger, callback, ALL, MATCH, callback_context import dash_core_components as dcc import dash_html_components as html from dash_local_react_components import load_react_component -import random import ai app = Dash(__name__) @@ -12,67 +11,76 @@ app = Dash(__name__) Magi = load_react_component(app, 'components', 'magi.js') WiseMan = load_react_component(app, 'components', 'wise_man.js') Response = load_react_component(app, 'components', 'response.js') +Modal = load_react_component(app, 'components', 'modal.js') -app.layout = html.Div(children=[ - Magi(id='magi', children=[ - html.Div(className='connection casper-balthasar'), - html.Div(className='connection casper-melchior'), - html.Div(className='connection balthasar-melchior'), - html.Div( - className='system-status', - children=[ - html.Div(children='CODE:473'), - html.Div(children='FILE:MAGI_SYS'), - html.Div(id='extention', children='EXTENTION:????'), - html.Div(children='EX_MODE:OFF'), - html.Div(children='PRIORITY:AAA')]), - WiseMan( - id={'type': 'wise-man', 'name': 'melchior'}, - name='melchior', - order_number=1, - personality='You are a scientist. Your goal is to further our our understanding of the universe and advance our technological progress.', - question_id=0, - answer={'id': 0, 'response': 'yes', 'status': 'yes'}), - WiseMan( - id={'type': 'wise-man', 'name': 'balthasar'}, - name='balthasar', - order_number=2, - personality='You are a mother. Your goal is to protect your children and ensure their well-being.', - question_id=0, - answer={'id': 0, 'response': 'yes', 'status': 'yes'}), - WiseMan( - id={'type': 'wise-man', 'name': 'casper'}, - name='casper', - order_number=3, - personality='You are a woman. Your goal is to pursue your dreams and desires.', - question_id=0, - answer={'id': 0, 'response': 'yes', 'status': 'yes'}), - Response(id='response', status='info'), - html.Div(className='title', children='MAGI'), - html.Div(className='header left', children=[ - html.Hr(), - html.Hr(), - html.Span('質 問 '), - html.Hr(), - html.Hr() +app.layout = html.Div( + className='system', + children=[ + Magi(id='magi', children=[ + html.Div(className='connection casper-balthasar'), + html.Div(className='connection casper-melchior'), + html.Div(className='connection balthasar-melchior'), + html.Div(className='header left', children=[ + html.Hr(), + html.Hr(), + html.Span('質 問 '), + html.Hr(), + html.Hr() + ]), + html.Div(className='header right', children=[ + html.Hr(), + html.Hr(), + html.Span('解 決 '), + html.Hr(), + html.Hr() + ]), + html.Div( + className='system-status', + children=[ + html.Div(children='CODE:473'), + html.Div(children='FILE:MAGI_SYS'), + html.Div(id='extention', children='EXTENTION:????'), + html.Div(children='EX_MODE:OFF'), + html.Div(children='PRIORITY:AAA')]), + WiseMan( + id={'type': 'wise-man', 'name': 'melchior'}, + name='melchior', + order_number=1, + personality='You are a scientist. Your goal is to further our understanding of the universe and advance our technological progress.', + question_id=0, + answer={'id': 0, 'response': 'yes', 'status': 'yes'}), + WiseMan( + id={'type': 'wise-man', 'name': 'balthasar'}, + name='balthasar', + order_number=2, + personality='You are a mother. Your goal is to protect your children and ensure their well-being.', + question_id=0, + answer={'id': 0, 'response': 'yes', 'status': 'yes'}), + WiseMan( + id={'type': 'wise-man', 'name': 'casper'}, + name='casper', + order_number=3, + personality='You are a woman. Your goal is to pursue love, dreams and desires.', + question_id=0, + answer={'id': 0, 'response': 'yes', 'status': 'yes'}), + Response(id='response', status='info'), + html.Div(className='title', children='MAGI') ]), - html.Div(className='header right', children=[ - html.Hr(), - html.Hr(), - html.Span('解 決 '), - html.Hr(), - html.Hr() - ]) - ]), - html.Label('MAGI access codes: '), - dcc.Input(id='key', autoComplete='off', type='password', value=os.getenv('OPENAI_API_KEY', '')), - dcc.Input(id='query', type='text', value='', debounce=True, autoComplete='off'), + html.Div(className='input-container', children=[ + html.Label('access code: '), + dcc.Input(id='key', autoComplete='off', type='password', value=os.getenv('OPENAI_API_KEY', '')), + html.Label('question: '), + dcc.Input(id='query', type='text', value='', debounce=True, autoComplete='off'), + ]), + Modal(id={'type': 'modal', 'name': 'melchior'}, name='melchior'), + Modal(id={'type': 'modal', 'name': 'balthasar'}, name='balthasar'), + Modal(id={'type': 'modal', 'name': 'casper'}, name='casper'), - dcc.Store(id='question', data={'id': 0, 'query': ''}), - dcc.Store(id='annotated-question', data={'id': 0, 'query': '', 'is_yes_no_question': False}), - dcc.Store(id='is_yes_no_question', data=False), - dcc.Store(id='question-id', data=0), -]) + dcc.Store(id='question', data={'id': 0, 'query': ''}), + dcc.Store(id='annotated-question', data={'id': 0, 'query': '', 'is_yes_or_no_question': False}), + dcc.Store(id='is_yes_or_no_question', data=False), + dcc.Store(id='question-id', data=0), + ]) @callback( @@ -94,9 +102,25 @@ def question(query: str, question: dict): def annotated_question(question: dict, key: str): print('annotated_question') - is_yes_no_question = ai.is_yes_or_no_question(question['query'], key) + try: + is_yes_or_no_question = ai.is_yes_or_no_question(question['query'], key) - return {'id': question['id'], 'query': question['query'], 'is_yes_no_question': is_yes_no_question} + print(is_yes_or_no_question) + + return { + 'id': question['id'], + 'query': question['query'], + 'is_yes_or_no_question': is_yes_or_no_question, + 'error': None + } + except Exception as e: + print(e) + return { + 'id': question['id'], + 'query': question['query'], + 'is_yes_or_no_question': False, + 'error': str(e) + } @callback( @@ -107,16 +131,38 @@ def extention(question: dict, annotated_question: dict): if question['id'] != annotated_question['id']: return 'EXTENTION:????' - code = '2137' if annotated_question['is_yes_no_question'] else '3023' + code = '7312' if annotated_question['is_yes_or_no_question'] else '3023' return f'EXTENTION:{code}' @callback( Output({'type': 'wise-man', 'name': MATCH}, 'answer'), Input('annotated-question', 'data'), - State({'type': 'wise-man', 'name': MATCH}, 'id')) -def wise_man_answer(question: dict, id: dict): - return {'id': question['id'], 'response': 'Just an example', 'status': 'info' if not question['is_yes_no_question'] else random.choice(['yes', 'no'])} + State({'type': 'wise-man', 'name': MATCH}, 'personality'), + State('key', 'value'), + prevent_initial_call=True) +def wise_man_answer(question: dict, personality: str, key: str): + print('wise_man_answer') + + if question['error']: + return {'id': question['id'], 'response': question['error'], 'status': 'error'} + + try: + answer = ai.get_answer(question['query'], personality, key) + print(answer) + + if question['is_yes_or_no_question']: + classification = ai.classify_answer(question['query'], personality, answer, key) + else: + classification = {'status': 'info', 'conditions': None} + + print(classification) + + return {'id': question['id'], 'response': answer, 'status': classification['status'], 'conditions': classification['conditions'], 'error': None} + + except Exception as e: + print(e) + return {'id': question['id'], 'response': None, 'status': 'error', 'conditions': 'None', 'error': str(e)} @callback( @@ -138,5 +184,22 @@ def response_status(answers: list): return 'info' +@callback( + Output({'type': 'modal', 'name': MATCH}, 'is_open'), + Trigger({'type': 'wise-man', 'name': MATCH}, 'n_clicks'), + prevent_initial_call=True) +def modal(): + return True + + +@callback( + Output({'type': 'modal', 'name': MATCH}, 'question'), + Output({'type': 'modal', 'name': MATCH}, 'answer'), + Input('question', 'data'), + Input({'type': 'wise-man', 'name': MATCH}, 'answer')) +def modal(question: dict, answer: dict): + return question, answer + + if __name__ == '__main__': app.run_server(debug=True)