Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9203641e6a | |||
| 14eaf57335 | |||
| 9ae5840e8b | |||
| 659b0937d3 | |||
| 7bc8a129c6 | |||
| a12c11c058 | |||
| 5771e52c49 | |||
| d038f1ac24 | |||
| 86374c91fe | |||
| 0adbb9c222 | |||
| c3d7f3ba4f | |||
| d864d7529a | |||
| 0b971dffc4 | |||
| ecf45bd2e7 | |||
| b88e573761 | |||
| ecf2ddf46e | |||
| b4a35bafef | |||
| de3e59afea | |||
| 9f5bdddb8f | |||
| 854ebbdd94 | |||
| 7d954e7380 | |||
| d843a2b74c | |||
| ab94c06e75 | |||
| 1757542077 | |||
| 941b426574 | |||
| 534874e810 | |||
| 0cccd02050 | |||
| e69a131875 | |||
| d797440288 | |||
| 1b5624018c | |||
| 807a766a63 | |||
| 5245e53954 | |||
| d1b0554748 | |||
| f8c3bcd156 | |||
| 25e5c6acea | |||
| 20fa94f533 | |||
| ccc8e73f48 | |||
| f179e8e19b | |||
| bfe8479016 | |||
| b261437952 | |||
| dac233ba9c | |||
| 7c9d20896e | |||
| ce5532e9a6 | |||
| feb7e61fd8 | |||
| 9a9b1388ce | |||
| 61974859bd | |||
| bf165d8181 | |||
| fbcac82bbf | |||
| 60ad3d109b | |||
| 8ddb920e99 | |||
| adbf14c43c | |||
| a2144e0480 | |||
| d0935f771a | |||
| 9bb9b76227 | |||
| 3b2577084e | |||
| eaba29fc6f | |||
| a5d54c2cb6 | |||
| b3136a9bcd | |||
| 81f2352291 | |||
| b102309d7b | |||
| 753353445c | |||
| 03c08d4024 | |||
| 38c78934a5 | |||
| 66620cc6ca | |||
| f5244ee88b | |||
| a18bfca9f8 | |||
| a90b3e5f2d | |||
| c4e4d8ded0 | |||
| fd31d583af | |||
| 89b1d16bfd | |||
| cfb6b6a9bd | |||
| 18dbee6cbb | |||
| f1fe8f0bfc | |||
| 2aa9aae282 | |||
| a3d9d07ead | |||
| 305b8485e3 | |||
| fcf6db2686 | |||
| c82f603d51 | |||
| 4f4d9af67d | |||
| 10469df5be | |||
| 8a9e57be76 | |||
| cecc2f7520 | |||
| 1b7142f157 | |||
| 3fef179723 | |||
| 99e3737718 | |||
| c39b831a59 | |||
| 3b70ab6eb9 | |||
| 4743fc26ad | |||
| 3ad564a75d | |||
| 9ea88d693d | |||
| 1837c1282a | |||
| 8d9369c502 | |||
| 957b8f46c8 | |||
| c37b7d46b4 |
@@ -160,3 +160,5 @@ cython_debug/
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Exclude venv from smartassist
|
||||
smartassist/smartassist_dev_venv
|
||||
@@ -0,0 +1,21 @@
|
||||
# This is "explain.prompt", a slash command to explain code
|
||||
# It is used to define and reuse prompts within Continue
|
||||
# Continue will automatically create a slash command for each prompt in the .prompts folder
|
||||
# To learn more, see the full .prompt file reference: https://docs.continue.dev/walkthroughs/prompt-files
|
||||
temperature: 0.3
|
||||
---
|
||||
<system>
|
||||
You are an expert programmer
|
||||
</system>
|
||||
|
||||
{{{ input }}}
|
||||
|
||||
Please analyze the source code snippet above and provide a detailed explanation of its functionality.
|
||||
|
||||
Input: Clearly identify where the code takes input from (e.g., user input, file, database, API call). Specify the format of this input (e.g., text string, numerical values, JSON object).
|
||||
Processing: Describe step-by-step how the code processes the input. Explain the purpose of each key function, loop, or conditional statement. Use clear and concise language, avoiding jargon where possible.
|
||||
Output: Specify what the code produces as output (e.g., printed text, modified file, database update, API response). Describe the format and content of this output.
|
||||
|
||||
Invocation Context: Who would typically use this code snippet? What is its intended purpose or application? Provide examples of real-world scenarios where this code might be employed.
|
||||
|
||||
If you need to provide more context, please include it in your explanation.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Frontend Configuration
|
||||
frontend:
|
||||
url: "http://localhost:5004"
|
||||
|
||||
# Backend Configuration
|
||||
backend:
|
||||
url: "http://localhost:5004"
|
||||
api: "/api/chat"
|
||||
|
||||
# Ollama Server Configuration
|
||||
ollama:
|
||||
url: "http://localhost:11434"
|
||||
api_key: "${OLLAMA_API_KEY}" # Refer to environment variable
|
||||
model: "phi3:mini" # Select a model supported by the Ollama server
|
||||
# model: "llama3:70b" # Select a model supported by the Ollama server
|
||||
# model: "llama3:latest" # Select a model supported by the Ollama server
|
||||
# model: "mannix/llama3-8b-ablitered-v3:latest" # Select a model supported by the Ollama server
|
||||
# model: "mistral-nemo:latest" # Select a model supported by the Ollama server
|
||||
# model: "gemma2:27b"
|
||||
|
||||
# Logging – comment out the whole section for default level which is INFO
|
||||
logging:
|
||||
level: DEBUG # Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
# level: INFO # Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
|
||||
# Additional Configuration Options (Optional – ignored for now)
|
||||
|
||||
# Cache Settings (Optional)
|
||||
cache:
|
||||
enabled: true
|
||||
timeout: 60 # Seconds
|
||||
|
||||
test:
|
||||
test_key_1: "ett"
|
||||
test_key_2: "ett"
|
||||
test_key_3: "${THIS_IS_A_DUMMY_VAR}"
|
||||
|
||||
another_test: "Syns detta?"
|
||||
@@ -1,6 +1,10 @@
|
||||
# A requirements file for the project. This file specifies
|
||||
# the development environment:
|
||||
# - Python 3.8+
|
||||
# - pipenv
|
||||
# A requirements file for the project.
|
||||
#
|
||||
# Run 'pip install -r smartassist/requirements_dev.txt' to install all required packages.
|
||||
|
||||
|
||||
ollama
|
||||
Flask
|
||||
Flask
|
||||
flask_cors
|
||||
requests
|
||||
PyYAML
|
||||
@@ -1,247 +0,0 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||
@@ -1,70 +0,0 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# You cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# Call hash to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
hash -r 2> /dev/null
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
unset VIRTUAL_ENV_PROMPT
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
# on Windows, a path can contain colons and backslashes and has to be converted:
|
||||
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
|
||||
# transform D:\path\to\venv to /d/path/to/venv on MSYS
|
||||
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||
export VIRTUAL_ENV=$(cygpath "/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env")
|
||||
else
|
||||
# use the path as-is
|
||||
export VIRTUAL_ENV="/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env"
|
||||
fi
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1="(smartassist_dev_env) ${PS1:-}"
|
||||
export PS1
|
||||
VIRTUAL_ENV_PROMPT="(smartassist_dev_env) "
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
fi
|
||||
|
||||
# Call hash to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
hash -r 2> /dev/null
|
||||
@@ -1,27 +0,0 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = "(smartassist_dev_env) $prompt"
|
||||
setenv VIRTUAL_ENV_PROMPT "(smartassist_dev_env) "
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
@@ -1,69 +0,0 @@
|
||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/). You cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
# prevents error when using nested fish instances (Issue #93858)
|
||||
if functions -q _old_fish_prompt
|
||||
functions -e fish_prompt
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
set -e VIRTUAL_ENV_PROMPT
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV "/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s%s%s" (set_color 4B8BBE) "(smartassist_dev_env) " (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
set -gx VIRTUAL_ENV_PROMPT "(smartassist_dev_env) "
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env/bin/python3.12
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from flask.cli import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env/bin/python3.12
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from httpx import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env/bin/python3.12
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env/bin/python3.12
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env/bin/python3.12
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1 +0,0 @@
|
||||
python3.12
|
||||
@@ -1 +0,0 @@
|
||||
python3.12
|
||||
@@ -1 +0,0 @@
|
||||
/opt/homebrew/opt/python@3.12/bin/python3.12
|
||||
@@ -1,5 +0,0 @@
|
||||
home = /opt/homebrew/opt/python@3.12/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.12.4
|
||||
executable = /opt/homebrew/Cellar/python@3.12/3.12.4/Frameworks/Python.framework/Versions/3.12/bin/python3.12
|
||||
command = /opt/homebrew/opt/python@3.12/bin/python3.12 -m venv /Users/ECSJPER/src/src_local/python_test/smartassist/smartassist_dev_env
|
||||
@@ -0,0 +1,179 @@
|
||||
|
||||
# Import the necessary functions from ollama, Flask, requests, threading
|
||||
from ollama import Client
|
||||
from flask import Flask, request, jsonify, send_from_directory, render_template, session, make_response
|
||||
from flask_cors import CORS, cross_origin # CORS stands for Cross-Origin Resource Sharing. This is necessary to allow the frontend to make requests to our backend.
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import utils
|
||||
from utils import GlobalState
|
||||
|
||||
# Create a logger for this module
|
||||
global_state = GlobalState() # Import the singleton that holds global states (e.g., logger)
|
||||
logger = global_state.getLogger(__name__) # Logger for this module, inherit properties of the root logger
|
||||
|
||||
|
||||
# Find out the path to current directory according to the Python interpreter (venv)
|
||||
logger.debug("Current working directory: %s", os.getcwd())
|
||||
|
||||
# Initialize a Flask application
|
||||
app = Flask(__name__)
|
||||
app.config['STATIC_FOLDER'] = 'static' # Adjust if needed
|
||||
|
||||
# Set the secret key for session management
|
||||
secret_key = os.urandom(24)
|
||||
app.config['SECRET_KEY'] = secret_key # When do I need this. How is it retained between sessions?
|
||||
|
||||
# Optionally set other configuration options
|
||||
app.config['SESSION_PERMANENT'] = False # Session will expire after each request
|
||||
app.config['SESSION_TYPE'] = 'filesystem' # Store sessions on the filesystem
|
||||
|
||||
|
||||
logger.debug("flask app template folder: %s", app.template_folder)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""
|
||||
This route serves index.html to connecting clients
|
||||
"""
|
||||
|
||||
session['chat_history'] = [] # The session object (actually, a dictonary) holds the chat session
|
||||
logger.debug("Entering route '/'")
|
||||
# api_endpoint = os.environ['BE_API_ENDPOINT'] # Retrieve the environment variable
|
||||
api_endpoint = global_state.get_backend_api_ep() # Retrieve the environment variable
|
||||
logger.debug("Backend API endpoint: %s", api_endpoint)
|
||||
use_model = global_state.get_llm()
|
||||
with open('smartassist/src/html/client.html', 'r') as f:
|
||||
client_html = f.read()
|
||||
logger.debug("Client HTML (first few characters): %s", client_html[:50]) # Print to see if it's loading
|
||||
# logger.debug("Client HTML (all characters): %s", client_html) # Print to see if it's loading
|
||||
|
||||
return render_template('index.html', api_endpoint=api_endpoint, use_model = use_model, client_content=client_html)
|
||||
|
||||
@app.route('/set_session')
|
||||
def set_session():
|
||||
resp = make_response()
|
||||
resp.set_cookie('session', 'some-value', samesite='None', secure=True) # Add SameSite attribute here
|
||||
return resp
|
||||
|
||||
@app.route('/profile')
|
||||
def profile():
|
||||
# Retrieve data from the session
|
||||
user_id = session.get('user_id')
|
||||
|
||||
if user_id:
|
||||
return f'User ID: {user_id}'
|
||||
else:
|
||||
return 'No user ID found'
|
||||
|
||||
|
||||
@app.route('/<path:filename>')
|
||||
def serve_static(filename):
|
||||
return send_from_directory(app.config['STATIC_FOLDER'], filename)
|
||||
|
||||
|
||||
# CORS(app, resources={
|
||||
# r"/api/chat": {
|
||||
# "origins": "*",
|
||||
# "headers": ["Origin", "Content-Type", "Authorization"],
|
||||
# }
|
||||
# })
|
||||
|
||||
CORS(app, resources={
|
||||
r"/api/chat": {
|
||||
"origins": "*"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/chat', methods=['POST'])
|
||||
def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"):
|
||||
"""
|
||||
This function handles the chat. The frontend client (web browser) calls the
|
||||
backend server through this endpoint (/api/chat) that manage queries
|
||||
to the LLM (Large Language Model) server and it also manages the response
|
||||
from the LLM server.
|
||||
"""
|
||||
# Get the message from the JSON in the request body
|
||||
data = request.get_json()
|
||||
message = data.get('query')
|
||||
url_server = data.get('url_server', url_server) # Use provided URL or default
|
||||
model = data.get('model', model) # Use provided model or default
|
||||
|
||||
# Get chat history from session storage (e.g., a dictionary)
|
||||
chat_history = session.get('chat_history', [])
|
||||
|
||||
# Add the new message to the chat history
|
||||
chat_history.append({'role': 'user', 'message': message})
|
||||
|
||||
# Update the session with the new chat history
|
||||
session['chat_history'] = chat_history
|
||||
|
||||
# Create the data dictionary with chat history
|
||||
data_to_send = {
|
||||
"model": model,
|
||||
'prompt': '\n'.join([f"{item['role']}: {item['message']}" for item in chat_history]),
|
||||
"stream": False
|
||||
}
|
||||
|
||||
try:
|
||||
url = url_server
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
response = requests.post(url,
|
||||
headers=headers,
|
||||
data=json.dumps(data_to_send))
|
||||
response.raise_for_status() # Raise an exception for bad status codes
|
||||
llm_response = response.json()['response'] # Assuming the LLM's response is under 'response' key
|
||||
chat_history.append({'role': 'assistant', 'message': llm_response}) # Add assistant response to chat history
|
||||
logger.debug(f"Chat History: {chat_history}")
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error("Request Exception: %s", str(e))
|
||||
return jsonify({'error': 'Failed to process request'}), 500
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("JSON Decode Error: %s", str(e)) # Corresponds to print(f"JSON Decode Error: {e}")
|
||||
return jsonify({'error': 'Invalid JSON response from server'}), 500
|
||||
|
||||
|
||||
|
||||
@app.route('/smartassist', methods=["POST"])
|
||||
def smartassist():
|
||||
# Extract the query from the incoming JSON data
|
||||
data = request.json
|
||||
user_query = data['query']
|
||||
|
||||
# Get the response from the OLLAMA API based on the user's query
|
||||
# NOTE: Should we append message history here? Maybe interact with SQLlite?
|
||||
response = get_response(user_query)
|
||||
|
||||
# Return the response as a JSON object in the HTTP response
|
||||
return jsonify({"response": response})
|
||||
|
||||
def get_response(user_query):
|
||||
client = Client() # Create a client object for interacting with OLLAMA API
|
||||
response = client.generate_response(user_query) # Generate and retrieve the response based on user's query
|
||||
return response
|
||||
|
||||
|
||||
def run_flask(fport=5005):
|
||||
"""
|
||||
Starts the Flask server
|
||||
"""
|
||||
# Flask endpoint for user interaction
|
||||
logger.debug("Entering run_flask()")
|
||||
# app.run(port = str(str(fport)), debug=False)
|
||||
app.run(port = str(str(fport)), debug=True)
|
||||
# app.run(port=5000, debug=True, use_reloader=False)
|
||||
logger.debug("Exiting run_flask()")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run the Flask application
|
||||
run_flask()
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ollama Chat</title>
|
||||
<link rel="stylesheet" href="/css/clientstyle.css">
|
||||
<!-- <link rel="stylesheet" href="python_test/smartassist/src/css/clientstyle.css"> -->
|
||||
</head>
|
||||
<body>
|
||||
<h1>Ollama Chat</h1>
|
||||
<div id="chatbox">
|
||||
<!-- messages will be rendered here -->
|
||||
</div>
|
||||
<textarea id="userInput" placeholder="Type your message..." rows="5"></textarea>
|
||||
<button id="sendButton" onclick="sendMessage()">Send</button>
|
||||
|
||||
<!-- Get the apiEndpoint and the useModel -->
|
||||
<!-- <script>
|
||||
const apiEndpoint = window.apiEndpoint;
|
||||
const useModel = window.useModel;
|
||||
console.log("client.html - API Endpoint: ", apiEndpoint);
|
||||
console.log("client.html - use model: ", useModel);
|
||||
</script> -->
|
||||
|
||||
<!-- <script>
|
||||
let apiEndpoint; // Make variable available outside of the scope of the event listener
|
||||
let useModel; // Make variable available outside of the scope of the event listener
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.origin === 'http://localhost:5004') { // Make sure this matches your origin
|
||||
const { apiEndpoint, useModel } = event.data;
|
||||
console.log("client.html - API Endpoint: ", apiEndpoint);
|
||||
console.log("client.html - use model: ", useModel);
|
||||
window.apiEndpoint = apiEndpoint;
|
||||
window.useModel = useModel;
|
||||
}
|
||||
});
|
||||
</script> -->
|
||||
|
||||
<!-- Marked-it for markdown rendering -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it@14/dist/markdown-it.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<!-- Include MathJax library to render mathematical notation -->
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<script>
|
||||
window.MathJax = {
|
||||
loader: { load: ['input/tex', 'output/chtml'] },
|
||||
tex: {
|
||||
packages: ['base', 'ams'],
|
||||
inlineMath: [['$', '$']]
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
const chatContainer = document.getElementById('chatbox');
|
||||
|
||||
// Handle resize events
|
||||
window.addEventListener('resize',
|
||||
function() {
|
||||
chatContainer.style.height = 'auto';
|
||||
});
|
||||
|
||||
const userInputElement = document.getElementById('userInput');
|
||||
|
||||
userInputElement.addEventListener('keydown', function(event) {
|
||||
if (event.shiftKey && event.key === 'Enter') { // Shift+Enter for newline
|
||||
event.preventDefault();
|
||||
userInputElement.value += '\n';
|
||||
} else if (event.key === 'Enter') { // Enter to send message
|
||||
window.frontendApi.sendMessage();
|
||||
userInputElement.value = ''; // Clear the input field after sending
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Get the javascript handling communication with the backend -->
|
||||
<script src="/js/frontend.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Example function to interact with OLLAMA
|
||||
from ollama import Client
|
||||
|
||||
def get_response(user_query):
|
||||
client = Client()
|
||||
response = client.generate_response(user_query)
|
||||
return response
|
||||
|
||||
# Flask endpoint for user interaction
|
||||
from flask import Flask, request, jsonify
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/smartassist', methods=['POST'])
|
||||
def smartassist():
|
||||
data = request.json
|
||||
user_query = data['query']
|
||||
response = get_response(user_query)
|
||||
return jsonify({"response": response})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
@@ -0,0 +1,121 @@
|
||||
# Start all services
|
||||
import subprocess
|
||||
import os
|
||||
import yaml
|
||||
import json
|
||||
import socket
|
||||
import urllib.parse
|
||||
from backend import run_flask
|
||||
import logging
|
||||
import utils
|
||||
from utils import GlobalState
|
||||
|
||||
global_state = GlobalState() # Configure root logger. The level will be adjusted later based on config file
|
||||
logger = global_state.getLogger(__name__) # Logger for this module, inherit properties of the root logger
|
||||
|
||||
def configure():
|
||||
"""
|
||||
Reads YAML configruation file into dictionary, parse it and fill all referenceed
|
||||
environment variables with their values.
|
||||
"""
|
||||
####################################
|
||||
# Read YAML config
|
||||
####################################
|
||||
# Load configuration file that defines parameters for services
|
||||
with open('./smartassist/config/smartassist.yaml') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
def resolve_env_var(value):
|
||||
if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
|
||||
env_var_name = value[2:-1] # Extract name between ${}
|
||||
return os.getenv(env_var_name, None)
|
||||
return value
|
||||
|
||||
def update_dict_with_env_vars(d):
|
||||
for key in d:
|
||||
if isinstance(d[key], dict):
|
||||
update_dict_with_env_vars(d[key]) # Recursively check nested dictionaries
|
||||
elif isinstance(d[key], str):
|
||||
d[key] = resolve_env_var(d[key])
|
||||
return d
|
||||
|
||||
# Update the config dictionary with resolved environment variables
|
||||
updated_config = update_dict_with_env_vars(config)
|
||||
|
||||
####################################
|
||||
# Extract global logging level
|
||||
####################################
|
||||
if isinstance(updated_config.get('logging'), dict): # Look for 'logging' key in config file
|
||||
logging_config = updated_config['logging']
|
||||
if isinstance(logging_config.get('level'), str): # Set to value of the yaml file if specified
|
||||
global_state.set_log_level(logging_config['level'])
|
||||
logger.debug("configure(): This logger now has effective log level %s", logger.getEffectiveLevel())
|
||||
|
||||
####################################
|
||||
# Extract and export backend API
|
||||
# endpoint as global state variable
|
||||
####################################
|
||||
if isinstance(updated_config.get('backend'), dict): # Look for 'backend' key
|
||||
if isinstance(updated_config['backend'].get('url'), str): # Look for 'url' key
|
||||
url = updated_config['backend'].get('url')
|
||||
if isinstance(updated_config['backend'].get('api'), str): # Look for 'api' key
|
||||
api = updated_config['backend'].get('api')
|
||||
# backend_api_ep = url+api # Extract API endpoint if defined
|
||||
logger.debug(f"Constructing endpoint address as url+api: {url+api}")
|
||||
global_state.set_backend_api_ep(url+api) # Extract API endpoint if defined and set in global_state
|
||||
logger.debug(f"Backend API endpoint is set to {global_state.get_backend_api_ep()}")
|
||||
# os.environ['BE_API_ENDPOINT'] = backend_api_ep # Look into alternative way to share this with backend.py
|
||||
|
||||
####################################
|
||||
# Extract Ollama parameters (url, api_key, model)
|
||||
####################################
|
||||
if isinstance(updated_config.get('ollama'), dict): # Look for 'ollama' key
|
||||
if isinstance(updated_config['ollama'].get('model'), str): # Look for 'model' key
|
||||
model_to_use = updated_config['ollama'].get('model')
|
||||
global_state.set_llm(model_to_use)
|
||||
logger.debug("configure(): LLM is set to: %s",global_state.get_llm())
|
||||
|
||||
return updated_config
|
||||
|
||||
|
||||
# def start_frontend(config):
|
||||
# parsed_url = urllib.parse.urlparse(config['frontend']['url'])
|
||||
# hostname = parsed_url.netloc.split(':')[0] # Split by ':' and take the first part, i.e., 'localhost', IP, or domain name
|
||||
# port = parsed_url.port # This is the server port
|
||||
|
||||
# # Use the socket module in Python to check whether a port is in use,
|
||||
# # which would indicate that a server is already running on that port.
|
||||
# with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# try:
|
||||
# s.bind((hostname, port))
|
||||
# logger.debug("No server is running on %s -— starting one.", parsed_url.netloc)
|
||||
# # Start frontend (web server) as a separate process
|
||||
# subprocess.Popen(["python", "-m", "http.server", str(port)])
|
||||
# except socket.error as e:
|
||||
# if e.errno == 48:
|
||||
# logger.debug("A server is already running on %s -— will use this.", parsed_url.netloc)
|
||||
# else:
|
||||
# raise # Unexpected error, re-raise it so we can see the traceback
|
||||
# except Exception as e:
|
||||
# logger.error("Failed to start frontend: %s", str(e)) # Corresponds to print(f"Failed to start frontend: {e}")
|
||||
|
||||
|
||||
|
||||
def start_backend(config):
|
||||
parsed_url = urllib.parse.urlparse(config['backend']['url'])
|
||||
# hostname = parsed_url.netloc.split(':')[0] # Split by ':' and take the first part, i.e., 'localhost', IP, or domain name
|
||||
port = parsed_url.port # This is the server port
|
||||
logger.debug('Backend parsed url set to {}'.format(parsed_url))
|
||||
logger.debug('Backend port set to {}'.format(port))
|
||||
|
||||
try:
|
||||
run_flask(fport = port)
|
||||
except Exception as e:
|
||||
logger.error("Failed to start backend: %s", str(e)) # Corresponds to print(f"Failed to start backend: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
conf = configure() # Read config from file and set up config dict
|
||||
logger.debug('conf dictionary set to {}'.format(json.dumps(conf, indent=4)))
|
||||
# start_frontend(config=conf) # Not needed as we are using Flask for backend now
|
||||
start_backend(config=conf)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#chatbox {
|
||||
width: calc(50% - 60px); /* Adjust width for input and button */
|
||||
/* max-width: 500px; */
|
||||
height: 600px;
|
||||
/* background-color: #fff8bc; */
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
overflow: auto; /* Allow horizontal and vertical scrolling of the chatbox */
|
||||
resize: both; /* Allow resizing vertically */
|
||||
border: 1px solid #ccc; /* Add a thin grey border around chatbox */
|
||||
margin-bottom: 20px; /* Add some space between chatbox and userInput */
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background-color: #9cc1ecbb;
|
||||
padding: 10px 15px;
|
||||
border-radius: 10px;
|
||||
text-align: left; /* Align user messages to the left */
|
||||
}
|
||||
|
||||
.ai-response {
|
||||
/* background-color: #f0f8ff; */
|
||||
background-color: #f5ecd0;
|
||||
padding: 10px 15px;
|
||||
border-radius: 10px;
|
||||
text-align: left; /* Align AI responses to the left */
|
||||
}
|
||||
|
||||
#userInput {
|
||||
width: calc(50% - 60px); /* Adjust width for input and button */
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-family: 'Courier New', Courier, monospace; /* Fixed width typeface */
|
||||
}
|
||||
|
||||
#userInput:focus {
|
||||
outline: none;
|
||||
border-color: #66afe9; /* Blue outline on focus */
|
||||
}
|
||||
|
||||
button[onclick="sendMessage()"] {
|
||||
background-color: #4CAF50; /* Green */
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s; /* Smooth transition effect */
|
||||
}
|
||||
|
||||
button[onclick="sendMessage()"]:hover {
|
||||
background-color: #b2b2b2; /* Light Grey on hover */
|
||||
}
|
||||
|
||||
button[onclick="sendMessage()"]:active {
|
||||
background-color: #6f6f6f; /* Dark Grey when clicked */
|
||||
}
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
|
||||
// // Get the user input element from the DOM
|
||||
// const chatbox = document.getElementById('chatbox');
|
||||
// const userInput = document.getElementById('userInput');
|
||||
|
||||
// const parser = window.markdownit({
|
||||
// linkify: true,
|
||||
// strikethrough: true,
|
||||
// });
|
||||
|
||||
// parser.enable(['table']);
|
||||
|
||||
// // const apiEndpoint = window.apiEndpoint; // Get the API endpoint
|
||||
// // const useModel = window.useModel; // Get whether to use a model or not
|
||||
// // console.log("frontend.js - API Endpoint: ", window.apiEndpoint);
|
||||
// // console.log("frontend.js - Use model: ", window.useModel);
|
||||
|
||||
// let apiEndpoint; // Make variable available outside of the scope of the event listener
|
||||
// let useModel; // Make variable available outside of the scope of the event listener
|
||||
// window.addEventListener('message', function(event) {
|
||||
// if (event.origin === 'http://localhost:5004') { // Make sure this matches your origin
|
||||
// const { apiEndpoint, useModel } = event.data;
|
||||
// console.log("client.html - API Endpoint: ", apiEndpoint);
|
||||
// console.log("client.html - use model: ", useModel);
|
||||
// window.apiEndpoint = apiEndpoint;
|
||||
// window.useModel = useModel;
|
||||
// }
|
||||
// });
|
||||
|
||||
// console.log("frontend.js - API Endpoint: ", window.apiEndpoint);
|
||||
// console.log("frontend.js - Use model: ", window.useModel);
|
||||
|
||||
// // Define a function to send the user's message to the AI
|
||||
// function sendMessage() {
|
||||
// // Get the user's input message and trim any whitespace
|
||||
// const query = userInput.value.trim();
|
||||
|
||||
// // Check if the message is not empty
|
||||
// if (query !== '') {
|
||||
|
||||
// // fetch(`${apiEndpoint}`, {
|
||||
// fetch(apiEndpoint, {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ query, model: useModel }), // Add these parameters here
|
||||
// // body: JSON.stringify({ query, url_server: "http://your-custom-url", model: "phi3:mini" }), // Add these parameters here
|
||||
// })
|
||||
// .then(response => response.json())
|
||||
// .then(data => {
|
||||
// // Get the AI's response from the API data
|
||||
// const aiResponse = data.response;
|
||||
|
||||
// // Render the user's original message in the chatbox
|
||||
// renderMessage(query, 'user-message');
|
||||
|
||||
// // Render the AI's response in the chatbox
|
||||
// renderMessage(aiResponse, 'ai-response');
|
||||
|
||||
// // Clear the user input field for the next message
|
||||
// userInput.value = '';
|
||||
// })
|
||||
// .catch(error => console.error('Error sending message:', error));
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// // Define a function to render a message in the chatbox with a specific class name
|
||||
// function renderMessage(text, className) {
|
||||
// // Create a new div element to hold the message
|
||||
// const messageElement = document.createElement('div');
|
||||
|
||||
// // Add the specified class name to the element
|
||||
// messageElement.className = className;
|
||||
|
||||
// // // Set the text content of the element to the message text
|
||||
// // messageElement.textContent = text;
|
||||
|
||||
// // Use the markdown-it parser
|
||||
// const html = parser.render(text);
|
||||
// messageElement.innerHTML = html;
|
||||
|
||||
// // Append the message element to the chatbox immediately
|
||||
// // chatbox.appendChild(messageElement);
|
||||
|
||||
// // Typeset math in the message element
|
||||
// MathJax.typesetPromise([messageElement]).then(() => {
|
||||
// // No need to append anything here, it's already appended above
|
||||
// chatbox.appendChild(messageElement);
|
||||
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Make the button toggle colour when user presses Enter on keyboard
|
||||
// const sendButton = document.getElementById('sendButton');
|
||||
|
||||
// document.addEventListener('keydown', function(event) {
|
||||
// if (event.key === 'Enter') {
|
||||
// sendButton.style.backgroundColor = '#6f6f6f'; // Dark Grey when Enter is pressed
|
||||
// }
|
||||
// });
|
||||
|
||||
// document.addEventListener('keyup', function() {
|
||||
// sendButton.style.backgroundColor = ''; // Restore the original style when any key is released
|
||||
// });
|
||||
|
||||
|
||||
|
||||
// Get the user input element from the DOM
|
||||
const chatbox = document.getElementById('chatbox');
|
||||
const userInput = document.getElementById('userInput');
|
||||
const parser = window.markdownit({
|
||||
linkify: true,
|
||||
strikethrough: true,
|
||||
});
|
||||
parser.enable(['table']);
|
||||
|
||||
let apiEndpoint; // Make variable available outside of the scope of the event listener
|
||||
let useModel; // Make variable available outside of the scope of the event listener
|
||||
|
||||
const frontendApi = {
|
||||
// Define a function to send the user's message to the AI
|
||||
sendMessage: function() {
|
||||
if (!window.apiEndpoint || !window.useModel) { // Check if we're ready before proceeding
|
||||
console.error("Not ready yet. Please wait for apiEndpoint and useModel to be set.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user's input message and trim any whitespace
|
||||
const query = userInput.value.trim();
|
||||
// Check if the message is not empty
|
||||
if (query !== '') {
|
||||
fetch(window.apiEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, model: window.useModel }), // Add these parameters here
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Get the AI's response from the API data
|
||||
const aiResponse = data.response;
|
||||
// Render the user's original message in the chatbox
|
||||
this.renderMessage(query, 'user-message');
|
||||
// Render the AI's response in the chatbox
|
||||
this.renderMessage(aiResponse, 'ai-response');
|
||||
// Clear the user input field for the next message
|
||||
userInput.value = '';
|
||||
})
|
||||
.catch(error => console.error('Error sending message:', error));
|
||||
}
|
||||
},
|
||||
// Define a function to render a message in the chatbox with a specific class name
|
||||
renderMessage: function(text, className) {
|
||||
// Create a new div element to hold the message
|
||||
const messageElement = document.createElement('div');
|
||||
// Add the specified class name to the element
|
||||
messageElement.className = className;
|
||||
// Use the markdown-it parser
|
||||
const html = parser.render(text);
|
||||
messageElement.innerHTML = html;
|
||||
// Append the message element to the chatbox immediately
|
||||
chatbox.appendChild(messageElement);
|
||||
},
|
||||
};
|
||||
|
||||
// Wait for the event listener to set apiEndpoint and useModel
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.origin === 'http://localhost:5004') { // Make sure this matches your origin
|
||||
const { apiEndpoint, useModel } = event.data;
|
||||
console.log("fronend.js - API Endpoint: ", apiEndpoint);
|
||||
console.log("fronend.js - use model: ", useModel);
|
||||
window.apiEndpoint = apiEndpoint;
|
||||
window.useModel = useModel;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the DOM to be fully loaded before making the API available
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.frontendApi = frontendApi;
|
||||
});
|
||||
|
||||
|
||||
// Make the button toggle colour when user presses Enter on keyboard
|
||||
const sendButton = document.getElementById('sendButton');
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Enter') {
|
||||
sendButton.style.backgroundColor = '#6f6f6f'; // Dark Grey when Enter is pressed
|
||||
}
|
||||
});
|
||||
document.addEventListener('keyup', function() {
|
||||
sendButton.style.backgroundColor = ''; // Restore the original style when any key is released
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- <title>Frontend</title> -->
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<!-- This iframe will hold the content from client.html -->
|
||||
<!-- Passing the API endpoint as a query parameter to the srcdoc attribute -->
|
||||
<!-- srcdoc="{{ client_content }}?apiEndpoint={{ api_endpoint }}/"> -->
|
||||
|
||||
<iframe id="client-frame"
|
||||
style="width: 100%; height: 100vh;"
|
||||
srcdoc="{{ client_content }}">
|
||||
</iframe>
|
||||
|
||||
<!-- <script>
|
||||
// Extract apiEndpoint for use in your frontend code...
|
||||
const apiEndpoint = '{{ api_endpoint }}'; // Templating syntax (Jinja2)
|
||||
const useModel = '{{ use_model }}'; // Templating syntax (Jinja2)
|
||||
// Tell the iframe about the apiEndpoint
|
||||
document.getElementById('client-frame').contentWindow.apiEndpoint = apiEndpoint;
|
||||
document.getElementById('client-frame').contentWindow.useModel = useModel;
|
||||
console.log("index.html - API Endpoint: ", apiEndpoint);
|
||||
console.log("index.html - use model: ", useModel);
|
||||
</script> -->
|
||||
|
||||
<script>
|
||||
// Extract apiEndpoint for use in frontend.js
|
||||
const apiEndpoint = '{{ api_endpoint }}'; // Templating syntax (Jinja2)
|
||||
const useModel = '{{ use_model }}'; // Templating syntax (Jinja2)
|
||||
window.addEventListener('load', function() {
|
||||
const clientFrame = document.getElementById('client-frame').contentWindow;
|
||||
clientFrame.postMessage({ apiEndpoint, useModel }, '*'); // Send the data to the iframe
|
||||
});
|
||||
console.log("index.html - API Endpoint: ", apiEndpoint);
|
||||
console.log("index.html - use model: ", useModel);
|
||||
</script>
|
||||
|
||||
<!-- Responsive scaling and some padding -->
|
||||
<script>
|
||||
const clientFrame = document.getElementById('client-frame');
|
||||
|
||||
function resizeIframe() {
|
||||
clientFrame.style.height = window.innerHeight - 50 + 'px'; // Adjust the subtraction for padding/margins if needed
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeIframe);
|
||||
resizeIframe(); // Call it once on page load
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,75 @@
|
||||
# This module contains definitions of variables, functions, classes, et cetera, that are
|
||||
# imported to more than one other module. The rational for defining these things here
|
||||
# is that it is easier to avoid circular imports when they are defined in a central location.
|
||||
import logging
|
||||
|
||||
class GlobalState:
|
||||
"""
|
||||
This class holds various variables and methods which are accessible across
|
||||
different modules in the Python project using the Singleton design pattern.
|
||||
This ensures that only one instance of the class is created and shared among
|
||||
all modules, preventing circular imports and providing a centralized location
|
||||
for managing shared resources.
|
||||
"""
|
||||
_instance = None # Private class attribute to hold the single instance of the class
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(GlobalState, cls).__new__(cls)
|
||||
cls._instance.log_level = 'INFO' # Default logging level
|
||||
cls._instance.logger = logging.getLogger() # Get root logger for the caller module
|
||||
handler = logging.StreamHandler() # Or other handler (FileHandler for logs to file)
|
||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
handler.setFormatter(formatter)
|
||||
cls._instance.logger.addHandler(handler)
|
||||
cls._instance.logger.setLevel(getattr(logging, cls._instance.log_level)) # Initialize root logger level
|
||||
cls._instance.logger.info(" __new__(cls): Logger in GlobalState created: %s", cls._instance.logger)
|
||||
cls._instance.llm = "phi3:mini" # Default LLM for queries. TODO: Check with ollama server that it actually exists
|
||||
cls._instance.backend_api_ep = "http://localhost:5005/api/chat" # Default backend API endpoint
|
||||
return cls._instance
|
||||
|
||||
def configure_logging(self, level=None):
|
||||
"""Set up logging for the project."""
|
||||
if level is None:
|
||||
level = self.log_level
|
||||
# numeric_level = getattr(logging, level.upper()) # Convert string to numeric level
|
||||
numeric_level = getattr(logging, level.upper()) # Convert string to numeric level
|
||||
self.logger.setLevel(numeric_level)
|
||||
self.logger.debug(f"utils.py -- configure_logging(): effective log level is {level} which is {self.logger.getEffectiveLevel()}")
|
||||
|
||||
def set_log_level(self, level = 'INFO'):
|
||||
"""Set the logging level."""
|
||||
self.log_level = level
|
||||
self.configure_logging()
|
||||
|
||||
def get_log_level(self):
|
||||
"""Getter for log_level attribute."""
|
||||
return self.log_level
|
||||
|
||||
def get_effective_log_level(self):
|
||||
"""Getter for effective log level of loggerattribute."""
|
||||
return self.logger.getEffectiveLevel()
|
||||
|
||||
def getLogger(self, module_name = None):
|
||||
"""Return a logger based on the module name."""
|
||||
if module_name is None:
|
||||
module_name = __name__
|
||||
logger = logging.getLogger(module_name)
|
||||
return logger
|
||||
|
||||
def set_llm(self, model_name="phi3:mini"):
|
||||
"""Set LLM for queries"""
|
||||
self.llm = model_name
|
||||
|
||||
def get_llm(self):
|
||||
"""Getter for which LLM is used for queries"""
|
||||
return self.llm
|
||||
|
||||
def set_backend_api_ep(self, be_api_ep=None):
|
||||
"""Set backend API endpoint"""
|
||||
self.backend_api_ep = be_api_ep
|
||||
|
||||
def get_backend_api_ep(self):
|
||||
"""Getter for backend API endpoint"""
|
||||
return self.backend_api_ep
|
||||
|
||||
Reference in New Issue
Block a user