Skip to content

Commit b57cc61

Browse files
authored
Initial AI Assistant implementation (fixes #8) (#13)
1 parent 9e1e695 commit b57cc61

11 files changed

Lines changed: 724 additions & 42 deletions

File tree

.github/workflows/debricked.yml

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -40,41 +40,18 @@ jobs:
4040
runs-on: ubuntu-latest
4141
if: ${{ (github.event_name == 'push') || (github.event_name == 'pull_request') || (github.event.inputs.runDebrickedScan == 'true') }}
4242
steps:
43-
- name: Checkout
44-
uses: actions/checkout@v4
45-
with:
46-
# Fetch at least the immediate parents so that if this is a pull request then we can checkout the head.
47-
fetch-depth: 2
48-
# Java is required to run the various Fortify utilities.
49-
# Setup JDK 11 on host
50-
- uses: actions/setup-java@v4
51-
with:
52-
distribution: 'temurin'
53-
java-version: '11'
54-
# Install Fortify (if required)
55-
- name: Setup Fortify tools
56-
uses: fortify/github-action/setup@v1.2.2
57-
with:
58-
export-path: true
59-
fcli: latest
60-
# Run debricked scan
61-
- name: Download Debricked CLI
62-
run: curl -L https://github.com/debricked/cli/releases/latest/download/cli_linux_x86_64.tar.gz | tar -xz debricked
63-
- name: Run Debricked scan
64-
run: ./debricked scan -r "${APP_NAME}" --access-token="${DEBRICKED_TOKEN}" -e "*/**.lock" -e "**/build/classes/test/**" -e "**/target/classes/test-classes/**" .
43+
# Docker example - uncomment to use Docker instead of GitHub Action
44+
- uses: actions/checkout@v4
45+
- uses: debricked/fingerprint@v4
46+
- uses: debricked/resolve@v4
47+
- uses: debricked/actions@v4
6548
env:
66-
APP_NAME: ${{ env.DEFAULT_APP_NAME }}
6749
DEBRICKED_TOKEN: ${{ secrets.DEBRICKED_TOKEN }}
68-
69-
scan_frontend:
70-
stage: scan
71-
image: debricked/cli:2-resolution-debian
72-
script:
73-
- debricked scan "$COMPONENT_DIR" -r ${DEBRICKED_REPO} -b ${DEBRICKED_BRANCH} -t ${DEBRICKED_TOKEN} --sbom CycloneDX --sbom-output gl-sbom-cdx.json
74-
dependencies:
75-
- build_frontend
76-
allow_failure: true
77-
artifacts:
78-
reports:
79-
cyclonedx:
80-
- gl-sbom-cdx.json
50+
# CLI example - uncomment to use CLI instead of GitHub Action
51+
#- name: Download Debricked CLI
52+
# run: curl -L https://github.com/debricked/cli/releases/latest/download/cli_linux_x86_64.tar.gz | tar -xz debricked
53+
#- name: Run Debricked scan
54+
# run: ./debricked scan -r "${APP_NAME}" --access-token="${DEBRICKED_TOKEN}" -e "*/**.lock" -e "**/build/classes/test/**" -e "**/target/classes/test-classes/**" .
55+
# env:
56+
# APP_NAME: ${{ env.DEFAULT_APP_NAME }}
57+
# DEBRICKED_TOKEN: ${{ secrets.DEBRICKED_TOKEN }}

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ Scan Application (with OpenText Application Security)
8585

8686
To carry out an OpenText Static Code Analyzer local scan, run the following:
8787

88+
```
89+
sourceanalyzer -b iwa -clean
90+
sourceanalyzer -b iwa -python-path ".venv\\Lib\\site-packages" iwa
91+
sourceanalyzer -b iwa -scan
92+
```
93+
94+
or you can use the Makefile target:
95+
8896
```
8997
make sast-scan
9098
```
@@ -93,7 +101,7 @@ To carry out a OpenText ScanCentral SAST scan, run the following:
93101

94102
```
95103
fcli ssc session login
96-
scancentral package -o package.zip -bt none --python-virtual-env .venv -oss
104+
scancentral package -o package.zip -bt none --python-virtual-env .venv
97105
fcli sast-scan start --release "_YOURAPP_:_YOURREL_" -f package.zip --store curScan
98106
fcli sast-scan wait-for ::curScan::
99107
fcli ssc action run appversion-summary --av "_YOURAPP_:_YOURREL_" -fs "Security Auditor View" -f summary.md

etc/InsecureWebApp-Prod-Login.webmacro

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

iwa/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def create_app(test_config=None):
6767
# enabled CORS on api routes
6868
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
6969

70-
# OpenAI configuration for Agent
70+
# OpenAI configuration for assistant
7171
#app.config['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY')
7272
#openai_extension.init_app(app)
7373

@@ -107,8 +107,8 @@ def internal_error(error):
107107
app.register_blueprint(products_bp, url_prefix='/products')
108108
from iwa.blueprints.cart.cart_routes import cart_bp
109109
app.register_blueprint(cart_bp, url_prefix='/cart')
110-
#from iwa.agent.routes import agent_bp
111-
#app.register_blueprint(agent_bp)
110+
from iwa.blueprints.assistant.assistant_routes import assistant_bp
111+
app.register_blueprint(assistant_bp, url_prefix='/assistant')
112112
from iwa.blueprints.insecure.insecure_routes import insecure_bp
113113
app.register_blueprint(insecure_bp, url_prefix='/insecure')
114114

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
InsecureWebApp - an insecure Python/Flask Web application
3+
4+
Copyright (C) 2024-2025 Kevin A. Lee (kadraman)
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
"""
19+
20+
from flask import Blueprint
21+
22+
assistant_bp = Blueprint("assistant", __name__, template_folder='templates', static_folder='static')
23+
24+
from iwa.blueprints.assistant import assistant_routes
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"""
2+
InsecureWebApp - an insecure Python/Flask Web application
3+
4+
Copyright (C) 2024-2025 Kevin A. Lee (kadraman)
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
"""
19+
import logging
20+
import os
21+
import time
22+
23+
from flask import render_template, request, jsonify, session
24+
from openai import OpenAI
25+
26+
from iwa.blueprints.assistant import assistant_bp
27+
28+
29+
logger = logging.getLogger(__name__)
30+
31+
# Initialize the OpenAI client
32+
api_key = os.getenv("OPENAI_API_KEY")
33+
if api_key:
34+
logger.debug("OPENAI_API_KEY: %s", api_key)
35+
logger.debug("AI Assistant functionality is enabled.")
36+
client = OpenAI(api_key=api_key)
37+
else:
38+
logger.debug("OpenAI API key not found. Assistant functionality is disabled.")
39+
40+
# Initialize the assistant and thread globally
41+
assistant_id = ""
42+
thread_id = ""
43+
44+
# Define a global chat history
45+
chat_history = [
46+
{"role": "system", "content": "You are a helpful assistant."},
47+
]
48+
49+
50+
def create_assistant():
51+
global assistant_id
52+
if assistant_id == "":
53+
my_assistant = client.beta.assistants.create(
54+
instructions="""You are a helpful medical assistant. You can help with medical questions and provide
55+
information on various health topics. If you are unsure about a question, please advise the user to
56+
consult a healthcare professional. You can also help with scheduling appointments, providing information on medications,
57+
and answering general health inquiries. Always prioritize user safety and confidentiality.
58+
""",
59+
name="MyMedicalAssistant",
60+
model="gpt-3.5-turbo",
61+
tools=[{"type": "file_search"}],
62+
)
63+
assistant_id = my_assistant.id
64+
else:
65+
my_assistant = client.beta.assistants.retrieve(assistant_id)
66+
assistant_id = my_assistant.id
67+
68+
return my_assistant
69+
70+
71+
def create_thread():
72+
global thread_id
73+
if thread_id == "":
74+
thread = client.beta.threads.create()
75+
thread_id = thread.id
76+
else:
77+
thread = client.beta.threads.retrieve(thread_id)
78+
thread_id = thread.id
79+
80+
return thread
81+
82+
83+
@assistant_bp.route('/', methods=['GET', 'POST'])
84+
def index():
85+
"""assistant page."""
86+
if not api_key:
87+
session["assistant_enabled"] = False
88+
message = "The AI Assistant is not currently available. Please try again later!"
89+
else:
90+
session["assistant_enabled"] = True
91+
message = "The AI Assistant is ready to help you!"
92+
return render_template("assistant/index.html", message=message)
93+
94+
@assistant_bp.route('/chat', methods=['POST'])
95+
def chat():
96+
content = request.json["message"]
97+
chat_history.append({"role": "user", "content": content})
98+
99+
# Send the message to the assistant
100+
message_params = {"thread_id": thread_id, "role": "user", "content": content}
101+
102+
thread_message = client.beta.threads.messages.create(**message_params)
103+
104+
# Run the assistant
105+
run = client.beta.threads.runs.create(
106+
thread_id=thread_id, assistant_id=assistant_id
107+
)
108+
# Wait for the run to complete and get the response
109+
while run.status != "completed":
110+
time.sleep(0.5)
111+
run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
112+
113+
response = client.beta.threads.messages.list(thread_id).data[0]
114+
115+
text_content = None
116+
117+
# Iterate through the content objects to find the first text content
118+
for content in response.content:
119+
if content.type == "text":
120+
text_content = content.text.value
121+
break # Exit the loop once the first text content is found
122+
123+
# Check if text content was found
124+
if text_content:
125+
chat_history.append({"role": "assistant", "content": text_content})
126+
return jsonify(success=True, message=text_content)
127+
else:
128+
# Handle the case where no text content is found
129+
return jsonify(success=False, message="No text content found")
130+
131+
132+
@assistant_bp.route('/reset', methods=['POST'])
133+
def reset_chat():
134+
global chat_history
135+
chat_history = [{"role": "system", "content": "You are a helpful assistant."}]
136+
137+
global thread_id
138+
thread_id = ""
139+
create_thread()
140+
return jsonify(success=True)
141+
142+
@assistant_bp.route("/get_ids", methods=["GET"])
143+
def get_ids():
144+
return jsonify(assistant_id=assistant_id, thread_id=thread_id)
145+
146+
@assistant_bp.route("/get_messages", methods=["GET"])
147+
def get_messages():
148+
if thread_id != "":
149+
thread_messages = client.beta.threads.messages.list(thread_id, order="asc")
150+
messages = [
151+
{
152+
"role": msg.role,
153+
"content": msg.content[0].text.value,
154+
}
155+
for msg in thread_messages.data
156+
]
157+
return jsonify(success=True, messages=messages)
158+
else:
159+
return jsonify(success=False, message="No thread ID")
160+
161+
@assistant_bp.before_request
162+
def before_request():
163+
if api_key:
164+
create_assistant()
165+
create_thread()
166+
167+
@assistant_bp.after_request
168+
def after_request(response):
169+
return response

0 commit comments

Comments
 (0)