Skip to content

Commit 6e327b8

Browse files
Merge pull request #227 from OpenTOPAS/bot-helper
Bot helper
2 parents c365718 + a220b69 commit 6e327b8

8 files changed

Lines changed: 215 additions & 17 deletions

File tree

.github/DISCUSSION_TEMPLATE/feature-request.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ body:
3030
default: 0
3131
validations:
3232
required: true
33+
- type: checkboxes
34+
id: TOPAS-assistant-bot
35+
attributes:
36+
label: TOPAS-assistant-bot
37+
description: Please mark this checkbox if you are willing to receive a single response from the TOPAS-assistant-bot (beta version).
38+
options:
39+
- label: I would like to receive an immediate response from the TOPAS-assistant-bot.
40+
required: false
3341
- type: checkboxes
3442
id: terms
3543
attributes:
@@ -46,4 +54,3 @@ body:
4654
options:
4755
- label: I testify that I have fully read the bug reporting guideline beforehand.
4856
required: true
49-

.github/DISCUSSION_TEMPLATE/run-problem.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ body:
4949
- 3.8
5050
- 3.9
5151
- 4.0
52-
default: 0
52+
- 4.1
53+
- 4.2
54+
default: 5
5355
validations:
5456
required: true
5557
- type: dropdown
@@ -67,20 +69,27 @@ body:
6769
label: Relevant log output
6870
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
6971
render: shell
72+
- type: checkboxes
73+
id: TOPAS-assistant-bot
74+
attributes:
75+
label: TOPAS-assistant-bot
76+
description: Please mark this checkbox if you are willing to receive a single response from the TOPAS-assistant-bot (beta version).
77+
options:
78+
- label: I would like to receive an immediate response from the TOPAS-assistant-bot.
79+
required: false
7080
- type: checkboxes
7181
id: terms
7282
attributes:
7383
label: Code of Conduct
74-
description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com)
84+
description: By submitting this discussion, you agree to follow our [Code of Conduct](https://example.com)
7585
options:
7686
- label: I agree to follow this project's Code of Conduct
7787
required: true
7888
- type: checkboxes
7989
id: attestation
8090
attributes:
8191
label: Attestation
82-
description: By submitting this issue, you testify of having read the code of conduct.
92+
description: By submitting this discussion, you testify of having read the code of conduct.
8393
options:
8494
- label: I testify that I have fully read the bug reporting guideline beforehand.
8595
required: true
86-

.github/DISCUSSION_TEMPLATE/validation.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,32 @@ body:
3434
- 3.8
3535
- 3.9
3636
- 4.0
37-
default: 0
37+
- 4.1
38+
- 4.2
39+
default: 5
3840
validations:
3941
required: true
42+
- type: checkboxes
43+
id: TOPAS-assistant-bot
44+
attributes:
45+
label: TOPAS-assistant-bot
46+
description: Please mark this checkbox if you are willing to receive a single response from the TOPAS-assistant-bot (beta version).
47+
options:
48+
- label: I would like to receive an immediate response from the TOPAS-assistant-bot.
49+
required: false
4050
- type: checkboxes
4151
id: terms
4252
attributes:
4353
label: Code of Conduct
44-
description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com)
54+
description: By submitting this discussion, you agree to follow our [Code of Conduct](https://example.com)
4555
options:
4656
- label: I agree to follow this project's Code of Conduct
4757
required: true
4858
- type: checkboxes
4959
id: attestation
5060
attributes:
5161
label: Attestation
52-
description: By submitting this issue, you testify of having read the code of conduct.
62+
description: By submitting this discussion, you testify of having read the code of conduct.
5363
options:
5464
- label: I testify that I have fully read the bug reporting guideline beforehand.
5565
required: true
56-
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
name: Discussion Bot
2+
3+
on:
4+
discussion:
5+
types: [created]
6+
7+
env:
8+
# Must match the model used when building the index.json embeddings.
9+
OPENAI_EMBEDDING_MODEL: "text-embedding-3-small"
10+
BOT_REPO: OpenTOPAS/TOPAS-assistant-bot
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
gpt-response:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Check user opted in to bot response
21+
id: optin
22+
env:
23+
BODY: ${{ github.event.discussion.body }}
24+
run: |
25+
set -euo pipefail
26+
# Match the exact checked checkbox line produced by the discussion form.
27+
# Use "--" so grep does not treat leading "-" in the pattern as an option.
28+
opt_in="- [x] I would like to receive an immediate response from the TOPAS-BOT."
29+
if printf "%s" "$BODY" | tr -d '\r' | grep -Fqi -- "$opt_in"; then
30+
echo "opted_in=true" >> "$GITHUB_OUTPUT"
31+
echo "Opt-in checkbox checked."
32+
else
33+
echo "opted_in=false" >> "$GITHUB_OUTPUT"
34+
echo "Opt-in checkbox not checked; skipping bot response."
35+
fi
36+
37+
- name: Checkout OpenTOPAS
38+
if: steps.optin.outputs.opted_in == 'true'
39+
uses: actions/checkout@v4
40+
41+
- name: Validate required secrets
42+
if: steps.optin.outputs.opted_in == 'true'
43+
env:
44+
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
45+
BOT_APP_PRIVATE_KEY: ${{ secrets.BOT_APP_PRIVATE_KEY }}
46+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
47+
run: |
48+
set -euo pipefail
49+
if [ -z "${BOT_APP_ID:-}" ]; then
50+
echo "Missing required secret: BOT_APP_ID (must be the GitHub App ID)."
51+
exit 1
52+
fi
53+
if [ -z "${BOT_APP_PRIVATE_KEY:-}" ]; then
54+
echo "Missing required secret: BOT_APP_PRIVATE_KEY (paste the full .pem contents)."
55+
exit 1
56+
fi
57+
if [ -z "${OPENAI_API_KEY:-}" ]; then
58+
echo "Missing required secret: OPENAI_API_KEY."
59+
exit 1
60+
fi
61+
62+
# Solution using the GitHub App avoids problems with private/public bot/source code repos. It works for both.
63+
- name: Create GitHub App token
64+
if: steps.optin.outputs.opted_in == 'true'
65+
id: app-token
66+
uses: actions/create-github-app-token@v1
67+
with:
68+
app-id: ${{ secrets.BOT_APP_ID }}
69+
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
70+
owner: OpenTOPAS
71+
# Explicitly scope the token to the repos we need to access.
72+
# This avoids cases where the token only has access to the current repo.
73+
repositories: OpenTOPAS,TOPAS-assistant-bot
74+
75+
- name: Debug App token repository access
76+
if: steps.optin.outputs.opted_in == 'true'
77+
env:
78+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
79+
run: |
80+
set -euo pipefail
81+
echo "Token can access these repos:"
82+
gh api installation/repositories -q '.repositories[].full_name'
83+
84+
- name: Verify App token can access bot repo
85+
if: steps.optin.outputs.opted_in == 'true'
86+
env:
87+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
88+
run: |
89+
set -euo pipefail
90+
# For private repos, GitHub returns 404 when the token lacks access.
91+
if ! gh api "repos/${BOT_REPO}" >/dev/null; then
92+
echo "GitHub App token cannot access ${BOT_REPO}."
93+
echo "Fix: ensure the GitHub App installation includes ${BOT_REPO} and has Contents: read, then re-run."
94+
exit 1
95+
fi
96+
97+
- name: Verify App token has Contents read on bot repo
98+
if: steps.optin.outputs.opted_in == 'true'
99+
env:
100+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
101+
run: |
102+
set -euo pipefail
103+
# Cloning requires Contents: read. Metadata-only access can still list repos but cannot read files.
104+
# This call should succeed if Contents permission has been granted AND re-approved on the installation.
105+
if ! gh api "repos/${BOT_REPO}/contents/" >/dev/null; then
106+
echo "GitHub App token can see ${BOT_REPO} but cannot read repository contents."
107+
echo "Fix: in the GitHub App settings, set Repository permissions -> Contents: Read-only,"
108+
echo "then re-install (or re-configure) the app installation on ${BOT_REPO} to approve new permissions."
109+
exit 1
110+
fi
111+
112+
- name: Checkout bot repo
113+
if: steps.optin.outputs.opted_in == 'true'
114+
id: bot-repo
115+
uses: actions/checkout@v4
116+
with:
117+
repository: ${{ env.BOT_REPO }}
118+
token: ${{ steps.app-token.outputs.token }}
119+
path: topas-bot
120+
121+
- name: Set up Python
122+
uses: actions/setup-python@v5
123+
with:
124+
python-version: "3.11"
125+
126+
- name: Install bot dependencies
127+
if: steps.optin.outputs.opted_in == 'true'
128+
run: pip install -r topas-bot/requirements.txt
129+
130+
- name: Download latest index.json from bot repo releases
131+
if: steps.optin.outputs.opted_in == 'true'
132+
env:
133+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
134+
run: |
135+
mkdir -p topas-bot/.bot
136+
137+
# Since index.json is a release asset we need to first find the API URL.
138+
# gh api returns an array of releases where [0] is the latest.
139+
# .assets loops over assets of that release then we select index.json
140+
asset_api_url="$(gh api repos/${{ env.BOT_REPO }}/releases -q '.[0].assets[] | select(.name=="index.json") | .url')"
141+
if [ -z "${asset_api_url}" ]; then
142+
echo "Could not find index.json asset on latest release in ${{ env.BOT_REPO }}"
143+
exit 1
144+
fi
145+
146+
curl -fsSL \
147+
-H "Authorization: Bearer ${GH_TOKEN}" \
148+
-H "Accept: application/octet-stream" \
149+
"${asset_api_url}" \
150+
-o topas-bot/.bot/index.json
151+
152+
test -s topas-bot/.bot/index.json
153+
154+
- name: Run GPT Response Script
155+
if: steps.optin.outputs.opted_in == 'true'
156+
working-directory: topas-bot
157+
env:
158+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
159+
OPENAI_EMBEDDING_MODEL: ${{ env.OPENAI_EMBEDDING_MODEL }}
160+
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
161+
run: python .github/actions/get_gpt_response.py

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ enable_testing()
1010

1111
set (TOPAS_VERSION_MAJOR 4)
1212
set (TOPAS_VERSION_MINOR 2)
13-
set (TOPAS_VERSION_PATCH 0)
13+
set (TOPAS_VERSION_PATCH 1)
1414

1515
# Print compiler information
1616
message (STATUS "CMAKE_CXX_COMPILER_ID: " ${CMAKE_CXX_COMPILER_ID})

geometry/TsVGeometryComponent.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1492,7 +1492,11 @@ G4bool TsVGeometryComponent::IsOnBoundary(G4ThreeVector, G4VSolid*, SurfaceType)
14921492
}
14931493

14941494

1495-
G4double TsVGeometryComponent::GetAreaOfSelectedSurface(G4VSolid*, SurfaceType, G4int, G4int, G4int) {
1495+
G4double TsVGeometryComponent::GetAreaOfSelectedSurface(G4VSolid* solid, SurfaceType surfaceID, G4int, G4int, G4int) {
1496+
if (surfaceID == AnySurface) {
1497+
return solid->GetSurfaceArea();
1498+
}
1499+
14961500
G4cerr << "Topas is exiting due to a serious error." << G4endl;
14971501
G4cerr << "TsVGeometryComponent::GetAreaOfSelectedSurface called for component that does not handle surface scoring: " << fName << G4endl;
14981502
fPm->AbortSession(1);

scoring/TsVBinnedScorer.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2032,6 +2032,9 @@ void TsVBinnedScorer::Output()
20322032
if (!GetUnit().empty())
20332033
seriesDescription = seriesDescription + " [" + GetUnit() + "]";
20342034
}
2035+
if (!GetUnit().empty() && GetUnit()=="Gy") {
2036+
anon.Replace(gdcm::Tag(0x3004, 0x0002), "GY");
2037+
}
20352038
anon.Replace(gdcm::Tag(0x0008,0x103e), seriesDescription); // Series Description
20362039
anon.Replace(gdcm::Tag(0x0008,0x0070), "TOPAS"); // Manufacturer
20372040
G4String topasVersionString = "TOPAS " + fPm->GetTOPASVersion();

scoring/TsVScorer.cc

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,17 @@ void TsVScorer::PostConstructor()
148148
}
149149

150150
// If scorer needs surface area calculation, check that component provides this feature
151-
if (fNeedsSurfaceAreaCalculation &! fComponent->CanCalculateSurfaceArea()) {
152-
G4cerr << "Topas is exiting due to a serious error in scoring." << G4endl;
153-
G4cerr << "Scorer name: " << GetName() << " requires surface area calculation." << G4endl;
154-
G4cerr << "But the component: " << fComponent->GetName() << " is of a type" << G4endl;
155-
G4cerr << "that does not provide this calculation." << G4endl;
156-
fPm->AbortSession(1);
151+
// or that AnySurface is requested (can use the solid's total surface area).
152+
if (fNeedsSurfaceAreaCalculation && !fComponent->CanCalculateSurfaceArea()) {
153+
G4String surfaceNameLower = fSurfaceName;
154+
G4StrUtil::to_lower(surfaceNameLower);
155+
if (surfaceNameLower != "anysurface") {
156+
G4cerr << "Topas is exiting due to a serious error in scoring." << G4endl;
157+
G4cerr << "Scorer name: " << GetName() << " requires surface area calculation." << G4endl;
158+
G4cerr << "But the component: " << fComponent->GetName() << " is of a type" << G4endl;
159+
G4cerr << "that does not provide this calculation." << G4endl;
160+
fPm->AbortSession(1);
161+
}
157162
}
158163

159164
// Ask the component to convert the string surface name to an integer surface id.

0 commit comments

Comments
 (0)