diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..68dc84378
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,162 @@
+# Include any files or directories that you don't want to be copied to your
+# container here (e.g., local build artifacts, temporary files, etc.).
+#
+# For more help, visit the .dockerignore file reference guide at
+# https://docs.docker.com/engine/reference/builder/#dockerignore-file
+
+**/.DS_Store
+**/__pycache__
+**/.venv
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/bin
+**/charts
+**/docker-compose*
+**/compose*
+**/Dockerfile*
+**/*.Dockerfile
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.log
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# VS Code
+.vscode/
+
+# Ignore other unnecessary files
+*.bak
+*.swp
+.DS_Store
+*.pdb
+*.sqlite3
diff --git a/code/backend/pages/04_Configuration.py b/code/backend/pages/04_Configuration.py
index 74294fd63..2ed84daa5 100644
--- a/code/backend/pages/04_Configuration.py
+++ b/code/backend/pages/04_Configuration.py
@@ -491,37 +491,43 @@ def validate_documents():
"Configuration saved successfully! Please restart the chat service for these changes to take effect."
)
- with st.popover(":red[Reset configuration to defaults]"):
-
- # Close button with a custom class
- if st.button("X", key="close_popup", help="Close popup"):
- st.session_state["popup_open"] = False
- st.rerun()
-
- st.write(
- "**Resetting the configuration cannot be reversed, proceed with caution!**"
- )
+ @st.dialog("Reset Configuration", width="small")
+ def reset_config_dialog():
+ st.write("**Resetting the configuration cannot be reversed. Proceed with caution!**")
st.text_input('Enter "reset" to proceed', key="reset_configuration")
if st.button(
- ":red[Reset]", disabled=st.session_state["reset_configuration"] != "reset"
+ ":red[Reset]",
+ disabled=st.session_state.get("reset_configuration", "") != "reset",
+ key="confirm_reset"
):
- try:
- ConfigHelper.delete_config()
- except ResourceNotFoundError:
- pass
-
- for key in st.session_state:
- del st.session_state[key]
-
+ with st.spinner("Resetting Configuration to Default values..."):
+ try:
+ ConfigHelper.delete_config()
+ except ResourceNotFoundError:
+ pass
+
+ ConfigHelper.clear_config()
+ st.session_state.clear()
st.session_state["reset"] = True
st.session_state["reset_configuration"] = ""
+ st.session_state["show_reset_dialog"] = False
st.rerun()
- if st.session_state.get("reset") is True:
- st.success("Configuration reset successfully!")
- del st.session_state["reset"]
- del st.session_state["reset_configuration"]
+ # Reset configuration button
+ if st.button(":red[Reset configuration to defaults]"):
+ st.session_state["show_reset_dialog"] = True
+
+ # Open the dialog if needed
+ if st.session_state.get("show_reset_dialog"):
+ reset_config_dialog()
+ st.session_state["show_reset_dialog"] = False
+
+ # After reset success
+ if st.session_state.get("reset"):
+ st.success("Configuration reset successfully!")
+ del st.session_state["reset"]
+ del st.session_state["reset_configuration"]
except Exception as e:
logger.error(f"Error occurred: {e}")
diff --git a/code/frontend/src/components/ChatHistoryListItemCell/ChatHistoryListItemCell.test.tsx b/code/frontend/src/components/ChatHistoryListItemCell/ChatHistoryListItemCell.test.tsx
index 94ae37505..19d730d06 100644
--- a/code/frontend/src/components/ChatHistoryListItemCell/ChatHistoryListItemCell.test.tsx
+++ b/code/frontend/src/components/ChatHistoryListItemCell/ChatHistoryListItemCell.test.tsx
@@ -11,6 +11,17 @@ jest.mock('../../api/api', () => ({
historyDelete: jest.fn()
}))
+// Mocking TooltipHost from @fluentui/react
+jest.mock('@fluentui/react', () => {
+ const actual = jest.requireActual('@fluentui/react');
+ return {
+ ...actual,
+ TooltipHost: ({ children }: { children: React.ReactNode }) => (
+
{children}
+ ),
+ };
+});
+
const conversation: Conversation = {
id: '1',
title: 'Test Chat',
@@ -47,7 +58,7 @@ describe('ChatHistoryListItemCell', () => {
})
test('renders the chat history item', () => {
-render();
+ render();
const titleElement = screen.getByText(/Test Chat/i)
expect(titleElement).toBeInTheDocument()
})
@@ -71,10 +82,10 @@ render();
// Check if the truncated title is in the document
const truncatedTitle = screen.getByText(/A very long title that shoul .../i)
expect(truncatedTitle).toBeInTheDocument()
- })
+ })
test('calls onSelect when clicked', () => {
- render();
+ render();
const item = screen.getByLabelText('chat history item')
fireEvent.click(item)
expect(mockOnSelect).toHaveBeenCalledWith(conversation)
@@ -93,7 +104,7 @@ render();
// Expect that no content related to the title is rendered
const titleElement = screen.queryByText(/Test Chat/i);
expect(titleElement).not.toBeInTheDocument();
-})
+ })
test('displays delete and edit buttons on hover', async () => {
const mockAppStateUpdated = {
@@ -132,7 +143,7 @@ render();
})
test('shows confirmation dialog and deletes item', async () => {
- ;(historyDelete as jest.Mock).mockResolvedValueOnce({
+ ; (historyDelete as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({})
})
@@ -156,7 +167,7 @@ render();
})
test('when delete API fails or return false', async () => {
- ;(historyDelete as jest.Mock).mockResolvedValueOnce({
+ ; (historyDelete as jest.Mock).mockResolvedValueOnce({
ok: false,
json: async () => ({})
})
@@ -204,10 +215,10 @@ render();
const appStateWithRequestInitiated = {
...componentProps,
isGenerating: true,
- selectedConvId:'1'
+ selectedConvId: '1'
}
- render();
+ render();
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
const deleteButton = screen.getByTitle(/Delete/i)
@@ -218,10 +229,10 @@ render();
})
test('does not disable buttons when request is not initiated', () => {
- render();
- const item = screen.getByLabelText('chat history item')
- fireEvent.mouseEnter(item)
- const deleteButton = screen.getByTitle(/Delete/i)
+ render();
+ const item = screen.getByLabelText('chat history item')
+ fireEvent.mouseEnter(item)
+ const deleteButton = screen.getByTitle(/Delete/i)
const editButton = screen.getByTitle(/Edit/i)
expect(deleteButton).not.toBeDisabled()
@@ -245,12 +256,12 @@ render();
})
test('handles input onChange and onKeyDown ENTER events correctly', async () => {
- ;(historyRename as jest.Mock).mockResolvedValueOnce({
+ ; (historyRename as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({})
})
-render();
+ render();
// Simulate hover to reveal Edit button
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
@@ -316,7 +327,7 @@ render();
test('Should hide the rename from when cancel it.', async () => {
userEvent.setup()
- render()
+ render()
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
// Wait for the Edit button to appear and click it
@@ -336,10 +347,10 @@ render();
test.skip('handles rename save API failed', async () => {
userEvent.setup()
- ;(historyRename as jest.Mock).mockRejectedValue({
- ok: false,
- json: async () => ({})
- })
+ ; (historyRename as jest.Mock).mockRejectedValue({
+ ok: false,
+ json: async () => ({})
+ })
render();
// Simulate hover to reveal Edit button
@@ -380,12 +391,12 @@ render();
date: new Date().toISOString()
}
- ;(historyRename as jest.Mock).mockResolvedValueOnce({
- ok: false,
- json: async () => ({ message: 'Title already exists' })
- })
+ ; (historyRename as jest.Mock).mockResolvedValueOnce({
+ ok: false,
+ json: async () => ({ message: 'Title already exists' })
+ })
-render();
+ render();
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
@@ -406,7 +417,7 @@ render();
test('triggers edit functionality when Enter key is pressed', async () => {
- ;(historyRename as jest.Mock).mockResolvedValueOnce({
+ ; (historyRename as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({ message: 'Title changed' })
})
@@ -438,12 +449,12 @@ render();
})
test('successfully saves edited title', async () => {
- ;(historyRename as jest.Mock).mockResolvedValueOnce({
+ ; (historyRename as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => ({})
})
-render();
+ render();
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
@@ -500,7 +511,7 @@ render();
///////
test('opens delete confirmation dialog when Enter key is pressed on the Delete button', async () => {
-render();
+ render();
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
@@ -511,7 +522,7 @@ render();
})
test('opens delete confirmation dialog when Space key is pressed on the Delete button', async () => {
-render();
+ render();
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
@@ -522,7 +533,7 @@ render();
})
test('opens edit input when Space key is pressed on the Edit button', async () => {
- render();
+ render();
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
@@ -534,7 +545,7 @@ render();
})
test('opens edit input when Enter key is pressed on the Edit button', async () => {
- render();
+ render();
const item = screen.getByLabelText('chat history item')
fireEvent.mouseEnter(item)
@@ -547,11 +558,11 @@ render();
test('handles rename save when the updated text is equal to initial text', async () => {
userEvent.setup()
- ;(historyRename as jest.Mock).mockRejectedValue({
- ok: false,
- json: async () => ({ message: 'Title not changed' })
- })
- render()
+ ; (historyRename as jest.Mock).mockRejectedValue({
+ ok: false,
+ json: async () => ({ message: 'Title not changed' })
+ })
+ render()
// Simulate hover to reveal Edit button
const item = screen.getByLabelText('chat history item')
@@ -573,26 +584,26 @@ render();
//fireEvent.change(inputItem, { target: { value: 'Test Chat' } });
})
expect(historyRename).not.toHaveBeenCalled()
-})
-test('Should hide the rename from on Enter or space .', async () => {
- userEvent.setup()
-
- render()
- const item = screen.getByLabelText('chat history item')
- fireEvent.mouseEnter(item)
- // Wait for the Edit button to appear and click it
- await waitFor(() => {
- const editButton = screen.getByTitle(/Edit/i)
- fireEvent.click(editButton)
})
+ test('Should hide the rename from on Enter or space .', async () => {
+ userEvent.setup()
- const editButton =screen.getByRole('button', { name: 'cancel edit title' })
- fireEvent.keyDown(editButton, { key: 'Enter', code: 'Enter', charCode: 13 })
+ render()
+ const item = screen.getByLabelText('chat history item')
+ fireEvent.mouseEnter(item)
+ // Wait for the Edit button to appear and click it
+ await waitFor(() => {
+ const editButton = screen.getByTitle(/Edit/i)
+ fireEvent.click(editButton)
+ })
- // Wait for the error to be hidden after 5 seconds
- await waitFor(() => {
- const input = screen.queryByLabelText('confirm new title')
- expect(input).not.toBeInTheDocument()
+ const editButton = screen.getByRole('button', { name: 'cancel edit title' })
+ fireEvent.keyDown(editButton, { key: 'Enter', code: 'Enter', charCode: 13 })
+
+ // Wait for the error to be hidden after 5 seconds
+ await waitFor(() => {
+ const input = screen.queryByLabelText('confirm new title')
+ expect(input).not.toBeInTheDocument()
+ })
})
})
-})
diff --git a/code/frontend/src/components/ChatHistoryListItemCell/ChatHistoryListItemCell.tsx b/code/frontend/src/components/ChatHistoryListItemCell/ChatHistoryListItemCell.tsx
index ab8541e67..fc47ef2c1 100644
--- a/code/frontend/src/components/ChatHistoryListItemCell/ChatHistoryListItemCell.tsx
+++ b/code/frontend/src/components/ChatHistoryListItemCell/ChatHistoryListItemCell.tsx
@@ -7,10 +7,12 @@ import {
DialogType,
IconButton,
ITextField,
+ ITooltipHostStyles,
PrimaryButton,
Stack,
Text,
TextField,
+ TooltipHost,
} from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks";
@@ -30,6 +32,11 @@ interface ChatHistoryListItemCellProps {
toggleToggleSpinner: (toggler: boolean) => void;
}
+
+const calloutProps = { gapSpace: 0 };
+const hostStyles: Partial = { root: { display: 'inline-block' } };
+
+
export const ChatHistoryListItemCell: React.FC<
ChatHistoryListItemCellProps
> = ({
@@ -51,6 +58,7 @@ export const ChatHistoryListItemCell: React.FC<
const [textFieldFocused, setTextFieldFocused] = useState(false);
const textFieldRef = useRef(null);
const isSelected = item?.id === selectedConvId;
+ const tooltipId = 'tooltip'+ item?.id;
const dialogContentProps = {
type: DialogType.close,
title: "Are you sure you want to delete this item?",
@@ -257,7 +265,14 @@ export const ChatHistoryListItemCell: React.FC<
) : (
<>
- {truncatedTitle}
+
+ {truncatedTitle}
{(isSelected || isHovered) && (