diff --git "a/gradio_runner.py" "b/gradio_runner.py"
new file mode 100644--- /dev/null
+++ "b/gradio_runner.py"
@@ -0,0 +1,4594 @@
+import ast
+import copy
+import functools
+import inspect
+import itertools
+import json
+import os
+import pprint
+import random
+import shutil
+import sys
+import time
+import traceback
+import uuid
+import filelock
+import numpy as np
+import pandas as pd
+import requests
+from iterators import TimeoutIterator
+
+from gradio_utils.css import get_css
+from gradio_utils.prompt_form import make_chatbots
+
+# This is a hack to prevent Gradio from phoning home when it gets imported
+os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
+
+
+def my_get(url, **kwargs):
+    print('Gradio HTTP request redirected to localhost :)', flush=True)
+    kwargs.setdefault('allow_redirects', True)
+    return requests.api.request('get', 'http://127.0.0.1/', **kwargs)
+
+
+original_get = requests.get
+requests.get = my_get
+import gradio as gr
+
+requests.get = original_get
+
+
+def fix_pydantic_duplicate_validators_error():
+    try:
+        from pydantic import class_validators
+
+        class_validators.in_ipython = lambda: True  # type: ignore[attr-defined]
+    except ImportError:
+        pass
+
+
+fix_pydantic_duplicate_validators_error()
+
+from enums import DocumentSubset, no_model_str, no_lora_str, no_server_str, LangChainAction, LangChainMode, \
+    DocumentChoice, langchain_modes_intrinsic, LangChainTypes, langchain_modes_non_db, gr_to_lg, invalid_key_msg, \
+    LangChainAgent, docs_ordering_types
+from gradio_themes import H2oTheme, SoftTheme, get_h2o_title, get_simple_title, \
+    get_dark_js, get_heap_js, wrap_js_to_lambda, \
+    spacing_xsm, radius_xsm, text_xsm
+from prompter import prompt_type_to_model_name, prompt_types_strings, inv_prompt_type_to_model_lower, non_hf_types, \
+    get_prompt
+from utils import flatten_list, zip_data, s3up, clear_torch_cache, get_torch_allocated, system_info_print, \
+    ping, makedirs, get_kwargs, system_info, ping_gpu, get_url, get_local_ip, \
+    save_generate_output, url_alive, remove, dict_to_html, text_to_html, lg_to_gr, str_to_dict, have_serpapi
+from gen import get_model, languages_covered, evaluate, score_qa, inputs_kwargs_list, \
+    get_max_max_new_tokens, get_minmax_top_k_docs, history_to_context, langchain_actions, langchain_agents_list, \
+    evaluate_fake, merge_chat_conversation_history
+from evaluate_params import eval_func_param_names, no_default_param_names, eval_func_param_names_defaults, \
+    input_args_list, key_overrides
+
+from apscheduler.schedulers.background import BackgroundScheduler
+
+
+def fix_text_for_gradio(text, fix_new_lines=False, fix_latex_dollars=True):
+    if fix_latex_dollars:
+        ts = text.split('```')
+        for parti, part in enumerate(ts):
+            inside = parti % 2 == 1
+            if not inside:
+                ts[parti] = ts[parti].replace('$', '﹩')
+        text = '```'.join(ts)
+
+    if fix_new_lines:
+        # let Gradio handle code, since got improved recently
+        ## FIXME: below conflicts with Gradio, but need to see if can handle multiple \n\n\n etc. properly as is.
+        # ensure good visually, else markdown ignores multiple \n
+        # handle code blocks
+        ts = text.split('```')
+        for parti, part in enumerate(ts):
+            inside = parti % 2 == 1
+            if not inside:
+                ts[parti] = ts[parti].replace('\n', '<br>')
+        text = '```'.join(ts)
+    return text
+
+
+def is_valid_key(enforce_h2ogpt_api_key, h2ogpt_api_keys, h2ogpt_key1, requests_state1=None):
+    valid_key = False
+    if not enforce_h2ogpt_api_key:
+        # no token barrier
+        valid_key = 'not enforced'
+    else:
+        if isinstance(h2ogpt_api_keys, list) and h2ogpt_key1 in h2ogpt_api_keys:
+            # passed token barrier
+            valid_key = True
+        elif isinstance(h2ogpt_api_keys, str) and os.path.isfile(h2ogpt_api_keys):
+            with filelock.FileLock(h2ogpt_api_keys + '.lock'):
+                with open(h2ogpt_api_keys, 'rt') as f:
+                    h2ogpt_api_keys = json.load(f)
+                if h2ogpt_key1 in h2ogpt_api_keys:
+                    valid_key = True
+    if isinstance(requests_state1, dict) and 'username' in requests_state1 and requests_state1['username']:
+        # no UI limit currently
+        valid_key = True
+    return valid_key
+
+
+def go_gradio(**kwargs):
+    allow_api = kwargs['allow_api']
+    is_public = kwargs['is_public']
+    is_hf = kwargs['is_hf']
+    memory_restriction_level = kwargs['memory_restriction_level']
+    n_gpus = kwargs['n_gpus']
+    admin_pass = kwargs['admin_pass']
+    model_states = kwargs['model_states']
+    dbs = kwargs['dbs']
+    db_type = kwargs['db_type']
+    visible_langchain_actions = kwargs['visible_langchain_actions']
+    visible_langchain_agents = kwargs['visible_langchain_agents']
+    allow_upload_to_user_data = kwargs['allow_upload_to_user_data']
+    allow_upload_to_my_data = kwargs['allow_upload_to_my_data']
+    enable_sources_list = kwargs['enable_sources_list']
+    enable_url_upload = kwargs['enable_url_upload']
+    enable_text_upload = kwargs['enable_text_upload']
+    use_openai_embedding = kwargs['use_openai_embedding']
+    hf_embedding_model = kwargs['hf_embedding_model']
+    load_db_if_exists = kwargs['load_db_if_exists']
+    migrate_embedding_model = kwargs['migrate_embedding_model']
+    auto_migrate_db = kwargs['auto_migrate_db']
+    captions_model = kwargs['captions_model']
+    caption_loader = kwargs['caption_loader']
+    doctr_loader = kwargs['doctr_loader']
+
+    n_jobs = kwargs['n_jobs']
+    verbose = kwargs['verbose']
+
+    # for dynamic state per user session in gradio
+    model_state0 = kwargs['model_state0']
+    score_model_state0 = kwargs['score_model_state0']
+    my_db_state0 = kwargs['my_db_state0']
+    selection_docs_state0 = kwargs['selection_docs_state0']
+    visible_models_state0 = kwargs['visible_models_state0']
+    # For Heap analytics
+    is_heap_analytics_enabled = kwargs['enable_heap_analytics']
+    heap_app_id = kwargs['heap_app_id']
+
+    # easy update of kwargs needed for evaluate() etc.
+    queue = True
+    allow_upload = allow_upload_to_user_data or allow_upload_to_my_data
+    allow_upload_api = allow_api and allow_upload
+
+    kwargs.update(locals())
+
+    # import control
+    if kwargs['langchain_mode'] != 'Disabled':
+        from gpt_langchain import file_types, have_arxiv
+    else:
+        have_arxiv = False
+        file_types = []
+
+    if 'mbart-' in kwargs['model_lower']:
+        instruction_label_nochat = "Text to translate"
+    else:
+        instruction_label_nochat = "Instruction (Shift-Enter or push Submit to send message," \
+                                   " use Enter for multiple input lines)"
+
+    title = 'h2oGPT'
+    if kwargs['visible_h2ogpt_header']:
+        description = """<iframe src="https://ghbtns.com/github-btn.html?user=h2oai&repo=h2ogpt&type=star&count=true&size=small" frameborder="0" scrolling="0" width="280" height="20" title="GitHub"></iframe><small><a href="https://github.com/h2oai/h2ogpt">h2oGPT</a> <a href="https://evalgpt.ai/">LLM Leaderboard</a> <a href="https://github.com/h2oai/h2o-llmstudio">LLM Studio</a><br /><a href="https://codellama.h2o.ai">CodeLlama</a> <br /><a href="https://huggingface.co/h2oai">🤗 Models</a>"""
+    else:
+        description = None
+    description_bottom = "If this host is busy, try<br>[Multi-Model](https://gpt.h2o.ai)<br>[CodeLlama](https://codellama.h2o.ai)<br>[Llama2 70B](https://llama.h2o.ai)<br>[Falcon 40B](https://falcon.h2o.ai)<br>[HF Spaces1](https://huggingface.co/spaces/h2oai/h2ogpt-chatbot)<br>[HF Spaces2](https://huggingface.co/spaces/h2oai/h2ogpt-chatbot2)<br>"
+    if is_hf:
+        description_bottom += '''<a href="https://huggingface.co/spaces/h2oai/h2ogpt-chatbot?duplicate=true"><img src="https://bit.ly/3gLdBN6" style="white-space: nowrap" alt="Duplicate Space"></a>'''
+    task_info_md = ''
+    css_code = get_css(kwargs)
+
+    if kwargs['gradio_offline_level'] >= 0:
+        # avoid GoogleFont that pulls from internet
+        if kwargs['gradio_offline_level'] == 1:
+            # front end would still have to download fonts or have cached it at some point
+            base_font = 'Source Sans Pro'
+        else:
+            base_font = 'Helvetica'
+        theme_kwargs = dict(font=(base_font, 'ui-sans-serif', 'system-ui', 'sans-serif'),
+                            font_mono=('IBM Plex Mono', 'ui-monospace', 'Consolas', 'monospace'))
+    else:
+        theme_kwargs = dict()
+    if kwargs['gradio_size'] == 'xsmall':
+        theme_kwargs.update(dict(spacing_size=spacing_xsm, text_size=text_xsm, radius_size=radius_xsm))
+    elif kwargs['gradio_size'] in [None, 'small']:
+        theme_kwargs.update(dict(spacing_size=gr.themes.sizes.spacing_sm, text_size=gr.themes.sizes.text_sm,
+                                 radius_size=gr.themes.sizes.spacing_sm))
+    elif kwargs['gradio_size'] == 'large':
+        theme_kwargs.update(dict(spacing_size=gr.themes.sizes.spacing_lg, text_size=gr.themes.sizes.text_lg),
+                            radius_size=gr.themes.sizes.spacing_lg)
+    elif kwargs['gradio_size'] == 'medium':
+        theme_kwargs.update(dict(spacing_size=gr.themes.sizes.spacing_md, text_size=gr.themes.sizes.text_md,
+                                 radius_size=gr.themes.sizes.spacing_md))
+
+    theme = H2oTheme(**theme_kwargs) if kwargs['h2ocolors'] else SoftTheme(**theme_kwargs)
+    demo = gr.Blocks(theme=theme, css=css_code, title="h2oGPT", analytics_enabled=False)
+    callback = gr.CSVLogger()
+
+    model_options0 = flatten_list(list(prompt_type_to_model_name.values())) + kwargs['extra_model_options']
+    if kwargs['base_model'].strip() not in model_options0:
+        model_options0 = [kwargs['base_model'].strip()] + model_options0
+    lora_options = kwargs['extra_lora_options']
+    if kwargs['lora_weights'].strip() not in lora_options:
+        lora_options = [kwargs['lora_weights'].strip()] + lora_options
+    server_options = kwargs['extra_server_options']
+    if kwargs['inference_server'].strip() not in server_options:
+        server_options = [kwargs['inference_server'].strip()] + server_options
+    if os.getenv('OPENAI_API_KEY'):
+        if 'openai_chat' not in server_options:
+            server_options += ['openai_chat']
+        if 'openai' not in server_options:
+            server_options += ['openai']
+
+    # always add in no lora case
+    # add fake space so doesn't go away in gradio dropdown
+    model_options0 = [no_model_str] + sorted(model_options0)
+    lora_options = [no_lora_str] + sorted(lora_options)
+    server_options = [no_server_str] + sorted(server_options)
+    # always add in no model case so can free memory
+    # add fake space so doesn't go away in gradio dropdown
+
+    # transcribe, will be detranscribed before use by evaluate()
+    if not kwargs['base_model'].strip():
+        kwargs['base_model'] = no_model_str
+
+    if not kwargs['lora_weights'].strip():
+        kwargs['lora_weights'] = no_lora_str
+
+    if not kwargs['inference_server'].strip():
+        kwargs['inference_server'] = no_server_str
+
+    # transcribe for gradio
+    kwargs['gpu_id'] = str(kwargs['gpu_id'])
+
+    no_model_msg = 'h2oGPT [   !!! Please Load Model in Models Tab !!!   ]'
+    output_label0 = f'h2oGPT [Model: {kwargs.get("base_model")}]' if kwargs.get(
+        'base_model') else no_model_msg
+    output_label0_model2 = no_model_msg
+
+    def update_prompt(prompt_type1, prompt_dict1, model_state1, which_model=0):
+        if not prompt_type1 or which_model != 0:
+            # keep prompt_type and prompt_dict in sync if possible
+            prompt_type1 = kwargs.get('prompt_type', prompt_type1)
+            prompt_dict1 = kwargs.get('prompt_dict', prompt_dict1)
+            # prefer model specific prompt type instead of global one
+            if not prompt_type1 or which_model != 0:
+                prompt_type1 = model_state1.get('prompt_type', prompt_type1)
+                prompt_dict1 = model_state1.get('prompt_dict', prompt_dict1)
+
+        if not prompt_dict1 or which_model != 0:
+            # if still not defined, try to get
+            prompt_dict1 = kwargs.get('prompt_dict', prompt_dict1)
+            if not prompt_dict1 or which_model != 0:
+                prompt_dict1 = model_state1.get('prompt_dict', prompt_dict1)
+        return prompt_type1, prompt_dict1
+
+    def visible_models_to_model_choice(visible_models1):
+        if isinstance(visible_models1, list):
+            assert len(
+                visible_models1) >= 1, "Invalid visible_models1=%s, can only be single entry" % visible_models1
+            # just take first
+            model_active_choice1 = visible_models1[0]
+        elif isinstance(visible_models1, (str, int)):
+            model_active_choice1 = visible_models1
+        else:
+            assert isinstance(visible_models1, type(None)), "Invalid visible_models1=%s" % visible_models1
+            model_active_choice1 = visible_models1
+        if model_active_choice1 is not None:
+            if isinstance(model_active_choice1, str):
+                base_model_list = [x['base_model'] for x in model_states]
+                if model_active_choice1 in base_model_list:
+                    # if dups, will just be first one
+                    model_active_choice1 = base_model_list.index(model_active_choice1)
+                else:
+                    # NOTE: Could raise, but sometimes raising in certain places fails too hard and requires UI restart
+                    model_active_choice1 = 0
+        else:
+            model_active_choice1 = 0
+        return model_active_choice1
+
+    default_kwargs = {k: kwargs[k] for k in eval_func_param_names_defaults}
+    # ensure prompt_type consistent with prep_bot(), so nochat API works same way
+    default_kwargs['prompt_type'], default_kwargs['prompt_dict'] = \
+        update_prompt(default_kwargs['prompt_type'], default_kwargs['prompt_dict'],
+                      model_state1=model_state0,
+                      which_model=visible_models_to_model_choice(kwargs['visible_models']))
+    for k in no_default_param_names:
+        default_kwargs[k] = ''
+
+    def dummy_fun(x):
+        # need dummy function to block new input from being sent until output is done,
+        # else gets input_list at time of submit that is old, and shows up as truncated in chatbot
+        return x
+
+    def update_auth_selection(auth_user, selection_docs_state1, save=False):
+        # in-place update of both
+        if 'selection_docs_state' not in auth_user:
+            auth_user['selection_docs_state'] = selection_docs_state0
+        for k, v in auth_user['selection_docs_state'].items():
+            if isinstance(selection_docs_state1[k], dict):
+                if save:
+                    auth_user['selection_docs_state'][k].clear()
+                    auth_user['selection_docs_state'][k].update(selection_docs_state1[k])
+                else:
+                    selection_docs_state1[k].clear()
+                    selection_docs_state1[k].update(auth_user['selection_docs_state'][k])
+            elif isinstance(selection_docs_state1[k], list):
+                if save:
+                    auth_user['selection_docs_state'][k].clear()
+                    auth_user['selection_docs_state'][k].extend(selection_docs_state1[k])
+                else:
+                    selection_docs_state1[k].clear()
+                    selection_docs_state1[k].extend(auth_user['selection_docs_state'][k])
+            else:
+                raise RuntimeError("Bad type: %s" % selection_docs_state1[k])
+
+    # BEGIN AUTH THINGS
+    def auth_func(username1, password1, auth_pairs=None, auth_filename=None,
+                  auth_access=None,
+                  auth_freeze=None,
+                  guest_name=None,
+                  selection_docs_state1=None,
+                  selection_docs_state00=None,
+                  **kwargs):
+        assert auth_freeze is not None
+        if selection_docs_state1 is None:
+            selection_docs_state1 = selection_docs_state00
+        assert selection_docs_state1 is not None
+        assert auth_filename and isinstance(auth_filename, str), "Auth file must be a non-empty string, got: %s" % str(
+            auth_filename)
+        if auth_access == 'open' and username1 == guest_name:
+            return True
+        if username1 == '':
+            # some issue with login
+            return False
+        with filelock.FileLock(auth_filename + '.lock'):
+            auth_dict = {}
+            if os.path.isfile(auth_filename):
+                try:
+                    with open(auth_filename, 'rt') as f:
+                        auth_dict = json.load(f)
+                except json.decoder.JSONDecodeError as e:
+                    print("Auth exception: %s" % str(e), flush=True)
+                    shutil.move(auth_filename, auth_filename + '.bak' + str(uuid.uuid4()))
+                    auth_dict = {}
+            if username1 in auth_dict and username1 in auth_pairs:
+                if password1 == auth_dict[username1]['password'] and password1 == auth_pairs[username1]:
+                    auth_user = auth_dict[username1]
+                    update_auth_selection(auth_user, selection_docs_state1)
+                    save_auth_dict(auth_dict, auth_filename)
+                    return True
+                else:
+                    return False
+            elif username1 in auth_dict:
+                if password1 == auth_dict[username1]['password']:
+                    auth_user = auth_dict[username1]
+                    update_auth_selection(auth_user, selection_docs_state1)
+                    save_auth_dict(auth_dict, auth_filename)
+                    return True
+                else:
+                    return False
+            elif username1 in auth_pairs:
+                # copy over CLI auth to file so only one state to manage
+                auth_dict[username1] = dict(password=auth_pairs[username1], userid=str(uuid.uuid4()))
+                auth_user = auth_dict[username1]
+                update_auth_selection(auth_user, selection_docs_state1)
+                save_auth_dict(auth_dict, auth_filename)
+                return True
+            else:
+                if auth_access == 'closed':
+                    return False
+                # open access
+                auth_dict[username1] = dict(password=password1, userid=str(uuid.uuid4()))
+                auth_user = auth_dict[username1]
+                update_auth_selection(auth_user, selection_docs_state1)
+                save_auth_dict(auth_dict, auth_filename)
+                if auth_access == 'open':
+                    return True
+                else:
+                    raise RuntimeError("Invalid auth_access: %s" % auth_access)
+
+    def auth_func_open(*args, **kwargs):
+        return True
+
+    def get_username(requests_state1):
+        username1 = None
+        if 'username' in requests_state1:
+            username1 = requests_state1['username']
+        return username1
+
+    def get_userid_auth_func(requests_state1, auth_filename=None, auth_access=None, guest_name=None, **kwargs):
+        if auth_filename and isinstance(auth_filename, str):
+            username1 = get_username(requests_state1)
+            if username1:
+                if username1 == guest_name:
+                    return str(uuid.uuid4())
+                with filelock.FileLock(auth_filename + '.lock'):
+                    if os.path.isfile(auth_filename):
+                        with open(auth_filename, 'rt') as f:
+                            auth_dict = json.load(f)
+                        if username1 in auth_dict:
+                            return auth_dict[username1]['userid']
+        # if here, then not persistently associated with username1,
+        # but should only be one-time asked if going to persist within a single session!
+        return str(uuid.uuid4())
+
+    get_userid_auth = functools.partial(get_userid_auth_func,
+                                        auth_filename=kwargs['auth_filename'],
+                                        auth_access=kwargs['auth_access'],
+                                        guest_name=kwargs['guest_name'],
+                                        )
+    if kwargs['auth_access'] == 'closed':
+        auth_message1 = "Closed access"
+    else:
+        auth_message1 = "WELCOME!  Open access" \
+                        " (%s/%s or any unique user/pass)" % (kwargs['guest_name'], kwargs['guest_name'])
+
+    if kwargs['auth_message'] is not None:
+        auth_message = kwargs['auth_message']
+    else:
+        auth_message = auth_message1
+
+    # always use same callable
+    auth_pairs0 = {}
+    if isinstance(kwargs['auth'], list):
+        for k, v in kwargs['auth']:
+            auth_pairs0[k] = v
+    authf = functools.partial(auth_func,
+                              auth_pairs=auth_pairs0,
+                              auth_filename=kwargs['auth_filename'],
+                              auth_access=kwargs['auth_access'],
+                              auth_freeze=kwargs['auth_freeze'],
+                              guest_name=kwargs['guest_name'],
+                              selection_docs_state00=copy.deepcopy(selection_docs_state0))
+
+    def get_request_state(requests_state1, request, db1s):
+        # if need to get state, do it now
+        if not requests_state1:
+            requests_state1 = requests_state0.copy()
+        if requests:
+            if not requests_state1.get('headers', '') and hasattr(request, 'headers'):
+                requests_state1.update(request.headers)
+            if not requests_state1.get('host', '') and hasattr(request, 'host'):
+                requests_state1.update(dict(host=request.host))
+            if not requests_state1.get('host2', '') and hasattr(request, 'client') and hasattr(request.client, 'host'):
+                requests_state1.update(dict(host2=request.client.host))
+            if not requests_state1.get('username', '') and hasattr(request, 'username'):
+                from src.gpt_langchain import get_username_direct
+                # use already-defined username instead of keep changing to new uuid
+                # should be same as in requests_state1
+                db_username = get_username_direct(db1s)
+                requests_state1.update(dict(username=request.username or db_username or str(uuid.uuid4())))
+        requests_state1 = {str(k): str(v) for k, v in requests_state1.items()}
+        return requests_state1
+
+    def user_state_setup(db1s, requests_state1, request: gr.Request, *args):
+        requests_state1 = get_request_state(requests_state1, request, db1s)
+        from src.gpt_langchain import set_userid
+        set_userid(db1s, requests_state1, get_userid_auth)
+        args_list = [db1s, requests_state1] + list(args)
+        return tuple(args_list)
+
+    # END AUTH THINGS
+
+    def allow_empty_instruction(langchain_mode1, document_subset1, langchain_action1):
+        allow = False
+        allow |= langchain_action1 not in LangChainAction.QUERY.value
+        allow |= document_subset1 in DocumentSubset.TopKSources.name
+        if langchain_mode1 in [LangChainMode.LLM.value]:
+            allow = False
+        return allow
+
+    image_loaders_options0, image_loaders_options, \
+        pdf_loaders_options0, pdf_loaders_options, \
+        url_loaders_options0, url_loaders_options = lg_to_gr(**kwargs)
+    jq_schema0 = '.[]'
+
+    with demo:
+        # avoid actual model/tokenizer here or anything that would be bad to deepcopy
+        # https://github.com/gradio-app/gradio/issues/3558
+        model_state = gr.State(
+            dict(model='model', tokenizer='tokenizer', device=kwargs['device'],
+                 base_model=kwargs['base_model'],
+                 tokenizer_base_model=kwargs['tokenizer_base_model'],
+                 lora_weights=kwargs['lora_weights'],
+                 inference_server=kwargs['inference_server'],
+                 prompt_type=kwargs['prompt_type'],
+                 prompt_dict=kwargs['prompt_dict'],
+                 )
+        )
+
+        def update_langchain_mode_paths(selection_docs_state1):
+            dup = selection_docs_state1['langchain_mode_paths'].copy()
+            for k, v in dup.items():
+                if k not in selection_docs_state1['langchain_modes']:
+                    selection_docs_state1['langchain_mode_paths'].pop(k)
+            for k in selection_docs_state1['langchain_modes']:
+                if k not in selection_docs_state1['langchain_mode_types']:
+                    # if didn't specify shared, then assume scratch if didn't login or personal if logged in
+                    selection_docs_state1['langchain_mode_types'][k] = LangChainTypes.PERSONAL.value
+            return selection_docs_state1
+
+        # Setup some gradio states for per-user dynamic state
+        model_state2 = gr.State(kwargs['model_state_none'].copy())
+        model_options_state = gr.State([model_options0])
+        lora_options_state = gr.State([lora_options])
+        server_options_state = gr.State([server_options])
+        my_db_state = gr.State(my_db_state0)
+        chat_state = gr.State({})
+        docs_state00 = kwargs['document_choice'] + [DocumentChoice.ALL.value]
+        docs_state0 = []
+        [docs_state0.append(x) for x in docs_state00 if x not in docs_state0]
+        docs_state = gr.State(docs_state0)
+        viewable_docs_state0 = []
+        viewable_docs_state = gr.State(viewable_docs_state0)
+        selection_docs_state0 = update_langchain_mode_paths(selection_docs_state0)
+        selection_docs_state = gr.State(selection_docs_state0)
+        requests_state0 = dict(headers='', host='', username='')
+        requests_state = gr.State(requests_state0)
+
+        if description is not None:
+            gr.Markdown(f"""
+                {get_h2o_title(title, description) if kwargs['h2ocolors'] else get_simple_title(title, description)}
+                """)
+
+        # go button visible if
+        base_wanted = kwargs['base_model'] != no_model_str and kwargs['login_mode_if_model0']
+        go_btn = gr.Button(value="ENTER", visible=base_wanted, variant="primary")
+
+        nas = ' '.join(['NA'] * len(kwargs['model_states']))
+        res_value = "Response Score: NA" if not kwargs[
+            'model_lock'] else "Response Scores: %s" % nas
+
+        user_can_do_sum = kwargs['langchain_mode'] != LangChainMode.DISABLED.value and \
+                          (kwargs['visible_side_bar'] or kwargs['visible_system_tab'])
+        if user_can_do_sum:
+            extra_prompt_form = ".  For summarization, no query required, just click submit"
+        else:
+            extra_prompt_form = ""
+        if kwargs['input_lines'] > 1:
+            instruction_label = "Shift-Enter to Submit, Enter for more lines%s" % extra_prompt_form
+        else:
+            instruction_label = "Enter to Submit, Shift-Enter for more lines%s" % extra_prompt_form
+
+        def get_langchain_choices(selection_docs_state1):
+            langchain_modes = selection_docs_state1['langchain_modes']
+
+            if is_hf:
+                # don't show 'wiki' since only usually useful for internal testing at moment
+                no_show_modes = ['Disabled', 'wiki']
+            else:
+                no_show_modes = ['Disabled']
+            allowed_modes = langchain_modes.copy()
+            # allowed_modes = [x for x in allowed_modes if x in dbs]
+            allowed_modes += ['LLM']
+            if allow_upload_to_my_data and 'MyData' not in allowed_modes:
+                allowed_modes += ['MyData']
+            if allow_upload_to_user_data and 'UserData' not in allowed_modes:
+                allowed_modes += ['UserData']
+            choices = [x for x in langchain_modes if x in allowed_modes and x not in no_show_modes]
+            return choices
+
+        def get_df_langchain_mode_paths(selection_docs_state1, db1s, dbs1=None):
+            langchain_choices1 = get_langchain_choices(selection_docs_state1)
+            langchain_mode_paths = selection_docs_state1['langchain_mode_paths']
+            langchain_mode_paths = {k: v for k, v in langchain_mode_paths.items() if k in langchain_choices1}
+            if langchain_mode_paths:
+                langchain_mode_paths = langchain_mode_paths.copy()
+                for langchain_mode1 in langchain_modes_non_db:
+                    langchain_mode_paths.pop(langchain_mode1, None)
+                df1 = pd.DataFrame.from_dict(langchain_mode_paths.items(), orient='columns')
+                df1.columns = ['Collection', 'Path']
+                df1 = df1.set_index('Collection')
+            else:
+                df1 = pd.DataFrame(None)
+            langchain_mode_types = selection_docs_state1['langchain_mode_types']
+            langchain_mode_types = {k: v for k, v in langchain_mode_types.items() if k in langchain_choices1}
+            if langchain_mode_types:
+                langchain_mode_types = langchain_mode_types.copy()
+                for langchain_mode1 in langchain_modes_non_db:
+                    langchain_mode_types.pop(langchain_mode1, None)
+
+                df2 = pd.DataFrame.from_dict(langchain_mode_types.items(), orient='columns')
+                df2.columns = ['Collection', 'Type']
+                df2 = df2.set_index('Collection')
+
+                from src.gpt_langchain import get_persist_directory, load_embed
+                persist_directory_dict = {}
+                embed_dict = {}
+                chroma_version_dict = {}
+                for langchain_mode3 in langchain_mode_types:
+                    langchain_type3 = langchain_mode_types.get(langchain_mode3, LangChainTypes.EITHER.value)
+                    persist_directory3, langchain_type3 = get_persist_directory(langchain_mode3,
+                                                                                langchain_type=langchain_type3,
+                                                                                db1s=db1s, dbs=dbs1)
+                    got_embedding3, use_openai_embedding3, hf_embedding_model3 = load_embed(
+                        persist_directory=persist_directory3)
+                    persist_directory_dict[langchain_mode3] = persist_directory3
+                    embed_dict[langchain_mode3] = 'OpenAI' if not hf_embedding_model3 else hf_embedding_model3
+
+                    if os.path.isfile(os.path.join(persist_directory3, 'chroma.sqlite3')):
+                        chroma_version_dict[langchain_mode3] = 'ChromaDB>=0.4'
+                    elif os.path.isdir(os.path.join(persist_directory3, 'index')):
+                        chroma_version_dict[langchain_mode3] = 'ChromaDB<0.4'
+                    elif not os.listdir(persist_directory3):
+                        if db_type == 'chroma':
+                            chroma_version_dict[langchain_mode3] = 'ChromaDB>=0.4'  # will be
+                        elif db_type == 'chroma_old':
+                            chroma_version_dict[langchain_mode3] = 'ChromaDB<0.4'  # will be
+                        else:
+                            chroma_version_dict[langchain_mode3] = 'Weaviate'  # will be
+                        if isinstance(hf_embedding_model, dict):
+                            hf_embedding_model3 = hf_embedding_model['name']
+                        else:
+                            hf_embedding_model3 = hf_embedding_model
+                        assert isinstance(hf_embedding_model3, str)
+                        embed_dict[langchain_mode3] = hf_embedding_model3  # will be
+                    else:
+                        chroma_version_dict[langchain_mode3] = 'Weaviate'
+
+                df3 = pd.DataFrame.from_dict(persist_directory_dict.items(), orient='columns')
+                df3.columns = ['Collection', 'Directory']
+                df3 = df3.set_index('Collection')
+
+                df4 = pd.DataFrame.from_dict(embed_dict.items(), orient='columns')
+                df4.columns = ['Collection', 'Embedding']
+                df4 = df4.set_index('Collection')
+
+                df5 = pd.DataFrame.from_dict(chroma_version_dict.items(), orient='columns')
+                df5.columns = ['Collection', 'DB']
+                df5 = df5.set_index('Collection')
+            else:
+                df2 = pd.DataFrame(None)
+                df3 = pd.DataFrame(None)
+                df4 = pd.DataFrame(None)
+                df5 = pd.DataFrame(None)
+            df_list = [df2, df1, df3, df4, df5]
+            df_list = [x for x in df_list if x.shape[1] > 0]
+            if len(df_list) > 1:
+                df = df_list[0].join(df_list[1:]).replace(np.nan, '').reset_index()
+            elif len(df_list) == 0:
+                df = df_list[0].replace(np.nan, '').reset_index()
+            else:
+                df = pd.DataFrame(None)
+            return df
+
+        normal_block = gr.Row(visible=not base_wanted, equal_height=False, elem_id="col_container")
+        with normal_block:
+            side_bar = gr.Column(elem_id="sidebar", scale=1, min_width=100, visible=kwargs['visible_side_bar'])
+            with side_bar:
+                with gr.Accordion("Chats", open=False, visible=True):
+                    radio_chats = gr.Radio(value=None, label="Saved Chats", show_label=False,
+                                           visible=True, interactive=True,
+                                           type='value')
+                upload_visible = kwargs['langchain_mode'] != 'Disabled' and allow_upload
+                with gr.Accordion("Upload", open=False, visible=upload_visible):
+                    with gr.Column():
+                        with gr.Row(equal_height=False):
+                            fileup_output = gr.File(show_label=False,
+                                                    file_types=['.' + x for x in file_types],
+                                                    # file_types=['*', '*.*'],  # for iPhone etc. needs to be unconstrained else doesn't work with extension-based restrictions
+                                                    file_count="multiple",
+                                                    scale=1,
+                                                    min_width=0,
+                                                    elem_id="warning", elem_classes="feedback",
+                                                    )
+                            fileup_output_text = gr.Textbox(visible=False)
+                    max_quality = gr.Checkbox(label="Maximum Ingest Quality", value=kwargs['max_quality'],
+                                              visible=not is_public)
+                    url_visible = kwargs['langchain_mode'] != 'Disabled' and allow_upload and enable_url_upload
+                    url_label = 'URL/ArXiv' if have_arxiv else 'URL'
+                    url_text = gr.Textbox(label=url_label,
+                                          # placeholder="Enter Submits",
+                                          max_lines=1,
+                                          interactive=True)
+                    text_visible = kwargs['langchain_mode'] != 'Disabled' and allow_upload and enable_text_upload
+                    user_text_text = gr.Textbox(label='Paste Text',
+                                                # placeholder="Enter Submits",
+                                                interactive=True,
+                                                visible=text_visible)
+                    github_textbox = gr.Textbox(label="Github URL", visible=False)  # FIXME WIP
+                database_visible = kwargs['langchain_mode'] != 'Disabled'
+                with gr.Accordion("Resources", open=False, visible=database_visible):
+                    langchain_choices0 = get_langchain_choices(selection_docs_state0)
+                    langchain_mode = gr.Radio(
+                        langchain_choices0,
+                        value=kwargs['langchain_mode'],
+                        label="Collections",
+                        show_label=True,
+                        visible=kwargs['langchain_mode'] != 'Disabled',
+                        min_width=100)
+                    add_chat_history_to_context = gr.Checkbox(label="Chat History",
+                                                              value=kwargs['add_chat_history_to_context'])
+                    add_search_to_context = gr.Checkbox(label="Web Search",
+                                                        value=kwargs['add_search_to_context'],
+                                                        visible=os.environ.get('SERPAPI_API_KEY') is not None \
+                                                                and have_serpapi)
+                    document_subset = gr.Radio([x.name for x in DocumentSubset],
+                                               label="Subset",
+                                               value=DocumentSubset.Relevant.name,
+                                               interactive=True,
+                                               )
+                    allowed_actions = [x for x in langchain_actions if x in visible_langchain_actions]
+                    langchain_action = gr.Radio(
+                        allowed_actions,
+                        value=allowed_actions[0] if len(allowed_actions) > 0 else None,
+                        label="Action",
+                        visible=True)
+                    allowed_agents = [x for x in langchain_agents_list if x in visible_langchain_agents]
+                    if os.getenv('OPENAI_API_KEY') is None and LangChainAgent.JSON.value in allowed_agents:
+                        allowed_agents.remove(LangChainAgent.JSON.value)
+                    if os.getenv('OPENAI_API_KEY') is None and LangChainAgent.PYTHON.value in allowed_agents:
+                        allowed_agents.remove(LangChainAgent.PYTHON.value)
+                    if LangChainAgent.PANDAS.value in allowed_agents:
+                        allowed_agents.remove(LangChainAgent.PANDAS.value)
+                    langchain_agents = gr.Dropdown(
+                        allowed_agents,
+                        value=None,
+                        label="Agents",
+                        multiselect=True,
+                        interactive=True,
+                        visible=True,
+                        elem_id="langchain_agents",
+                        filterable=False)
+                visible_doc_track = upload_visible and kwargs['visible_doc_track'] and not kwargs['large_file_count_mode']
+                row_doc_track = gr.Row(visible=visible_doc_track)
+                with row_doc_track:
+                    if kwargs['langchain_mode'] in langchain_modes_non_db:
+                        doc_counts_str = "Pure LLM Mode"
+                    else:
+                        doc_counts_str = "Name: %s\nDocs: Unset\nChunks: Unset" % kwargs['langchain_mode']
+                    text_doc_count = gr.Textbox(lines=3, label="Doc Counts", value=doc_counts_str,
+                                                visible=visible_doc_track)
+                    text_file_last = gr.Textbox(lines=1, label="Newest Doc", value=None, visible=visible_doc_track)
+                    text_viewable_doc_count = gr.Textbox(lines=2, label=None, visible=False)
+            col_tabs = gr.Column(elem_id="col-tabs", scale=10)
+            with col_tabs, gr.Tabs():
+                if kwargs['chat_tables']:
+                    chat_tab = gr.Row(visible=True)
+                else:
+                    chat_tab = gr.TabItem("Chat") \
+                        if kwargs['visible_chat_tab'] else gr.Row(visible=False)
+                with chat_tab:
+                    if kwargs['langchain_mode'] == 'Disabled':
+                        text_output_nochat = gr.Textbox(lines=5, label=output_label0, show_copy_button=True,
+                                                        visible=not kwargs['chat'])
+                    else:
+                        # text looks a bit worse, but HTML links work
+                        text_output_nochat = gr.HTML(label=output_label0, visible=not kwargs['chat'])
+                    with gr.Row():
+                        # NOCHAT
+                        instruction_nochat = gr.Textbox(
+                            lines=kwargs['input_lines'],
+                            label=instruction_label_nochat,
+                            placeholder=kwargs['placeholder_instruction'],
+                            visible=not kwargs['chat'],
+                        )
+                        iinput_nochat = gr.Textbox(lines=4, label="Input context for Instruction",
+                                                   placeholder=kwargs['placeholder_input'],
+                                                   value=kwargs['iinput'],
+                                                   visible=not kwargs['chat'])
+                        submit_nochat = gr.Button("Submit", size='sm', visible=not kwargs['chat'])
+                        flag_btn_nochat = gr.Button("Flag", size='sm', visible=not kwargs['chat'])
+                        score_text_nochat = gr.Textbox("Response Score: NA", show_label=False,
+                                                       visible=not kwargs['chat'])
+                        submit_nochat_api = gr.Button("Submit nochat API", visible=False)
+                        submit_nochat_api_plain = gr.Button("Submit nochat API Plain", visible=False)
+                        inputs_dict_str = gr.Textbox(label='API input for nochat', show_label=False, visible=False)
+                        text_output_nochat_api = gr.Textbox(lines=5, label='API nochat output', visible=False,
+                                                            show_copy_button=True)
+
+                        # CHAT
+                        col_chat = gr.Column(visible=kwargs['chat'])
+                        with col_chat:
+                            with gr.Row():
+                                with gr.Column(scale=50):
+                                    with gr.Row(elem_id="prompt-form-row"):
+                                        label_instruction = 'Ask anything'
+                                        instruction = gr.Textbox(
+                                            lines=kwargs['input_lines'],
+                                            label=label_instruction,
+                                            placeholder=instruction_label,
+                                            info=None,
+                                            elem_id='prompt-form',
+                                            container=True,
+                                        )
+                                        attach_button = gr.UploadButton(
+                                            elem_id="attach-button",
+                                            value="",
+                                            label="Upload File(s)",
+                                            size="sm",
+                                            min_width=24,
+                                            file_types=['.' + x for x in file_types],
+                                            file_count="multiple")
+
+                                submit_buttons = gr.Row(equal_height=False, visible=kwargs['visible_submit_buttons'])
+                                with submit_buttons:
+                                    mw1 = 50
+                                    mw2 = 50
+                                    with gr.Column(min_width=mw1):
+                                        submit = gr.Button(value='Submit', variant='primary', size='sm',
+                                                           min_width=mw1)
+                                        stop_btn = gr.Button(value="Stop", variant='secondary', size='sm',
+                                                             min_width=mw1)
+                                        save_chat_btn = gr.Button("Save", size='sm', min_width=mw1)
+                                    with gr.Column(min_width=mw2):
+                                        retry_btn = gr.Button("Redo", size='sm', min_width=mw2)
+                                        undo = gr.Button("Undo", size='sm', min_width=mw2)
+                                        clear_chat_btn = gr.Button(value="Clear", size='sm', min_width=mw2)
+
+                            visible_model_choice = bool(kwargs['model_lock']) and \
+                                                   len(model_states) > 1 and \
+                                                   kwargs['visible_visible_models']
+                            with gr.Row(visible=visible_model_choice):
+                                visible_models = gr.Dropdown(kwargs['all_models'],
+                                                             label="Visible Models",
+                                                             value=visible_models_state0,
+                                                             interactive=True,
+                                                             multiselect=True,
+                                                             visible=visible_model_choice,
+                                                             elem_id="visible-models",
+                                                             filterable=False,
+                                                             )
+
+                            text_output, text_output2, text_outputs = make_chatbots(output_label0, output_label0_model2,
+                                                                                    **kwargs)
+
+                            with gr.Row():
+                                with gr.Column(visible=kwargs['score_model']):
+                                    score_text = gr.Textbox(res_value,
+                                                            show_label=False,
+                                                            visible=True)
+                                    score_text2 = gr.Textbox("Response Score2: NA", show_label=False,
+                                                             visible=False and not kwargs['model_lock'])
+
+                doc_selection_tab = gr.TabItem("Document Selection") \
+                    if kwargs['visible_doc_selection_tab'] else gr.Row(visible=False)
+                with doc_selection_tab:
+                    if kwargs['langchain_mode'] in langchain_modes_non_db:
+                        dlabel1 = 'Choose Resources->Collections and Pick Collection'
+                        active_collection = gr.Markdown(value="#### Not Chatting with Any Collection\n%s" % dlabel1)
+                    else:
+                        dlabel1 = 'Select Subset of Document(s) for Chat with Collection: %s' % kwargs['langchain_mode']
+                        active_collection = gr.Markdown(
+                            value="#### Chatting with Collection: %s" % kwargs['langchain_mode'])
+                    document_choice = gr.Dropdown(docs_state0,
+                                                  label=dlabel1,
+                                                  value=[DocumentChoice.ALL.value],
+                                                  interactive=True,
+                                                  multiselect=True,
+                                                  visible=kwargs['langchain_mode'] != 'Disabled',
+                                                  )
+                    sources_visible = kwargs['langchain_mode'] != 'Disabled' and enable_sources_list
+                    with gr.Row():
+                        with gr.Column(scale=1):
+                            get_sources_btn = gr.Button(value="Update UI with Document(s) from DB", scale=0, size='sm',
+                                                        visible=sources_visible and kwargs['large_file_count_mode'])
+                            # handle API get sources
+                            get_sources_api_btn = gr.Button(visible=False)
+                            get_sources_api_text = gr.Textbox(visible=False)
+
+                            get_document_api_btn = gr.Button(visible=False)
+                            get_document_api_text = gr.Textbox(visible=False)
+
+                            show_sources_btn = gr.Button(value="Show Sources from DB", scale=0, size='sm',
+                                                         visible=sources_visible and kwargs['large_file_count_mode'])
+                            delete_sources_btn = gr.Button(value="Delete Selected Sources from DB", scale=0, size='sm',
+                                                           visible=sources_visible)
+                            refresh_sources_btn = gr.Button(value="Update DB with new/changed files on disk", scale=0,
+                                                            size='sm',
+                                                            visible=sources_visible and allow_upload_to_user_data)
+                        with gr.Column(scale=4):
+                            pass
+                    with gr.Row():
+                        with gr.Column(scale=1):
+                            visible_add_remove_collection = (allow_upload_to_user_data or
+                                                             allow_upload_to_my_data) and \
+                                                            kwargs['langchain_mode'] != 'Disabled'
+                            add_placeholder = "e.g. UserData2, shared, user_path2" \
+                                if not is_public else "e.g. MyData2, personal (optional)"
+                            remove_placeholder = "e.g. UserData2" if not is_public else "e.g. MyData2"
+                            new_langchain_mode_text = gr.Textbox(value="", visible=visible_add_remove_collection,
+                                                                 label='Add Collection',
+                                                                 placeholder=add_placeholder,
+                                                                 interactive=True)
+                            remove_langchain_mode_text = gr.Textbox(value="", visible=visible_add_remove_collection,
+                                                                    label='Remove Collection from UI',
+                                                                    placeholder=remove_placeholder,
+                                                                    interactive=True)
+                            purge_langchain_mode_text = gr.Textbox(value="", visible=visible_add_remove_collection,
+                                                                   label='Purge Collection (UI, DB, & source files)',
+                                                                   placeholder=remove_placeholder,
+                                                                   interactive=True)
+                            sync_sources_btn = gr.Button(
+                                value="Synchronize DB and UI [only required if did not login and have shared docs]",
+                                scale=0, size='sm',
+                                visible=sources_visible and allow_upload_to_user_data and not kwargs[
+                                    'large_file_count_mode'])
+                            load_langchain = gr.Button(
+                                value="Load Collections State [only required if logged in another user ", scale=0,
+                                size='sm',
+                                visible=False and allow_upload_to_user_data and
+                                        kwargs['langchain_mode'] != 'Disabled')
+                        with gr.Column(scale=5):
+                            if kwargs['langchain_mode'] != 'Disabled' and visible_add_remove_collection:
+                                df0 = get_df_langchain_mode_paths(selection_docs_state0, None, dbs1=dbs)
+                            else:
+                                df0 = pd.DataFrame(None)
+                            langchain_mode_path_text = gr.Dataframe(value=df0,
+                                                                    visible=visible_add_remove_collection,
+                                                                    label='LangChain Mode-Path',
+                                                                    show_label=False,
+                                                                    interactive=False)
+
+                    sources_row = gr.Row(visible=kwargs['langchain_mode'] != 'Disabled' and enable_sources_list,
+                                         equal_height=False)
+                    with sources_row:
+                        with gr.Column(scale=1):
+                            file_source = gr.File(interactive=False,
+                                                  label="Download File w/Sources")
+                        with gr.Column(scale=2):
+                            sources_text = gr.HTML(label='Sources Added', interactive=False)
+
+                    doc_exception_text = gr.Textbox(value="", label='Document Exceptions',
+                                                    interactive=False,
+                                                    visible=kwargs['langchain_mode'] != 'Disabled')
+                    file_types_str = ' '.join(file_types) + ' URL ArXiv TEXT'
+                    gr.Textbox(value=file_types_str, label='Document Types Supported',
+                               lines=2,
+                               interactive=False,
+                               visible=kwargs['langchain_mode'] != 'Disabled')
+
+                doc_view_tab = gr.TabItem("Document Viewer") \
+                    if kwargs['visible_doc_view_tab'] else gr.Row(visible=False)
+                with doc_view_tab:
+                    with gr.Row(visible=kwargs['langchain_mode'] != 'Disabled'):
+                        with gr.Column(scale=2):
+                            get_viewable_sources_btn = gr.Button(value="Update UI with Document(s) from DB", scale=0,
+                                                                 size='sm',
+                                                                 visible=sources_visible and kwargs[
+                                                                     'large_file_count_mode'])
+                            view_document_choice = gr.Dropdown(viewable_docs_state0,
+                                                               label="Select Single Document to View",
+                                                               value=None,
+                                                               interactive=True,
+                                                               multiselect=False,
+                                                               visible=True,
+                                                               )
+                            info_view_raw = "Raw text shown if render of original doc fails"
+                            if is_public:
+                                info_view_raw += " (Up to %s chunks in public portal)" % kwargs['max_raw_chunks']
+                            view_raw_text_checkbox = gr.Checkbox(label="View Database Text", value=False,
+                                                                 info=info_view_raw,
+                                                                 visible=kwargs['db_type'] in ['chroma', 'chroma_old'])
+                        with gr.Column(scale=4):
+                            pass
+                    doc_view = gr.HTML(visible=False)
+                    doc_view2 = gr.Dataframe(visible=False)
+                    doc_view3 = gr.JSON(visible=False)
+                    doc_view4 = gr.Markdown(visible=False)
+                    doc_view5 = gr.HTML(visible=False)
+
+                chat_tab = gr.TabItem("Chat History") \
+                    if kwargs['visible_chat_history_tab'] else gr.Row(visible=False)
+                with chat_tab:
+                    with gr.Row():
+                        with gr.Column(scale=1):
+                            remove_chat_btn = gr.Button(value="Remove Selected Saved Chats", visible=True, size='sm')
+                            flag_btn = gr.Button("Flag Current Chat", size='sm')
+                            export_chats_btn = gr.Button(value="Export Chats to Download", size='sm')
+                        with gr.Column(scale=4):
+                            pass
+                    with gr.Row():
+                        chats_file = gr.File(interactive=False, label="Download Exported Chats")
+                        chatsup_output = gr.File(label="Upload Chat File(s)",
+                                                 file_types=['.json'],
+                                                 file_count='multiple',
+                                                 elem_id="warning", elem_classes="feedback")
+                    with gr.Row():
+                        if 'mbart-' in kwargs['model_lower']:
+                            src_lang = gr.Dropdown(list(languages_covered().keys()),
+                                                   value=kwargs['src_lang'],
+                                                   label="Input Language")
+                            tgt_lang = gr.Dropdown(list(languages_covered().keys()),
+                                                   value=kwargs['tgt_lang'],
+                                                   label="Output Language")
+
+                    chat_exception_text = gr.Textbox(value="", visible=True, label='Chat Exceptions',
+                                                     interactive=False)
+                expert_tab = gr.TabItem("Expert") \
+                    if kwargs['visible_expert_tab'] else gr.Row(visible=False)
+                with expert_tab:
+                    with gr.Row():
+                        with gr.Column():
+                            prompt_type = gr.Dropdown(prompt_types_strings,
+                                                      value=kwargs['prompt_type'], label="Prompt Type",
+                                                      visible=not kwargs['model_lock'],
+                                                      interactive=not is_public,
+                                                      )
+                            prompt_type2 = gr.Dropdown(prompt_types_strings,
+                                                       value=kwargs['prompt_type'], label="Prompt Type Model 2",
+                                                       visible=False and not kwargs['model_lock'],
+                                                       interactive=not is_public)
+                            system_prompt = gr.Textbox(label="System Prompt",
+                                                       info="If 'auto', then uses model's system prompt,"
+                                                            " else use this message."
+                                                            " If empty, no system message is used",
+                                                       value=kwargs['system_prompt'])
+                            context = gr.Textbox(lines=2, label="System Pre-Context",
+                                                 info="Directly pre-appended without prompt processing (before Pre-Conversation)",
+                                                 value=kwargs['context'])
+                            chat_conversation = gr.Textbox(lines=2, label="Pre-Conversation",
+                                                           info="Pre-append conversation for instruct/chat models as List of tuple of (human, bot)",
+                                                           value=kwargs['chat_conversation'])
+                            text_context_list = gr.Textbox(lines=2, label="Text Doc Q/A",
+                                                           info="List of strings, for document Q/A, for bypassing database (i.e. also works in LLM Mode)",
+                                                           value=kwargs['chat_conversation'],
+                                                           visible=not is_public,  # primarily meant for API
+                                                           )
+                            iinput = gr.Textbox(lines=2, label="Input for Instruct prompt types",
+                                                info="If given for document query, added after query",
+                                                value=kwargs['iinput'],
+                                                placeholder=kwargs['placeholder_input'],
+                                                interactive=not is_public)
+                        with gr.Column():
+                            pre_prompt_query = gr.Textbox(label="Query Pre-Prompt",
+                                                          info="Added before documents",
+                                                          value=kwargs['pre_prompt_query'] or '')
+                            prompt_query = gr.Textbox(label="Query Prompt",
+                                                      info="Added after documents",
+                                                      value=kwargs['prompt_query'] or '')
+                            pre_prompt_summary = gr.Textbox(label="Summary Pre-Prompt",
+                                                            info="Added before documents",
+                                                            value=kwargs['pre_prompt_summary'] or '')
+                            prompt_summary = gr.Textbox(label="Summary Prompt",
+                                                        info="Added after documents (if query given, 'Focusing on {query}, ' is pre-appended)",
+                                                        value=kwargs['prompt_summary'] or '')
+                    with gr.Row(visible=not is_public):
+                        image_loaders = gr.CheckboxGroup(image_loaders_options,
+                                                         label="Force Image Reader",
+                                                         value=image_loaders_options0)
+                        pdf_loaders = gr.CheckboxGroup(pdf_loaders_options,
+                                                       label="Force PDF Reader",
+                                                       value=pdf_loaders_options0)
+                        url_loaders = gr.CheckboxGroup(url_loaders_options,
+                                                       label="Force URL Reader", value=url_loaders_options0)
+                        jq_schema = gr.Textbox(label="JSON jq_schema", value=jq_schema0)
+
+                        min_top_k_docs, max_top_k_docs, label_top_k_docs = get_minmax_top_k_docs(is_public)
+                        top_k_docs = gr.Slider(minimum=min_top_k_docs, maximum=max_top_k_docs, step=1,
+                                               value=kwargs['top_k_docs'],
+                                               label=label_top_k_docs,
+                                               # info="For LangChain",
+                                               visible=kwargs['langchain_mode'] != 'Disabled',
+                                               interactive=not is_public)
+                        chunk_size = gr.Number(value=kwargs['chunk_size'],
+                                               label="Chunk size for document chunking",
+                                               info="For LangChain (ignored if chunk=False)",
+                                               minimum=128,
+                                               maximum=2048,
+                                               visible=kwargs['langchain_mode'] != 'Disabled',
+                                               interactive=not is_public,
+                                               precision=0)
+                        docs_ordering_type = gr.Radio(
+                            docs_ordering_types,
+                            value=kwargs['docs_ordering_type'],
+                            label="Document Sorting in LLM Context",
+                            visible=True)
+                        chunk = gr.components.Checkbox(value=kwargs['chunk'],
+                                                       label="Whether to chunk documents",
+                                                       info="For LangChain",
+                                                       visible=kwargs['langchain_mode'] != 'Disabled',
+                                                       interactive=not is_public)
+                        embed = gr.components.Checkbox(value=True,
+                                                       label="Whether to embed text",
+                                                       info="For LangChain",
+                                                       visible=False)
+                    with gr.Row():
+                        stream_output = gr.components.Checkbox(label="Stream output",
+                                                               value=kwargs['stream_output'])
+                        do_sample = gr.Checkbox(label="Sample",
+                                                info="Enable sampler (required for use of temperature, top_p, top_k)",
+                                                value=kwargs['do_sample'])
+                        max_time = gr.Slider(minimum=0, maximum=kwargs['max_max_time'], step=1,
+                                             value=min(kwargs['max_max_time'],
+                                                       kwargs['max_time']), label="Max. time",
+                                             info="Max. time to search optimal output.")
+                        temperature = gr.Slider(minimum=0.01, maximum=2,
+                                                value=kwargs['temperature'],
+                                                label="Temperature",
+                                                info="Lower is deterministic, higher more creative")
+                        top_p = gr.Slider(minimum=1e-3, maximum=1.0 - 1e-3,
+                                          value=kwargs['top_p'], label="Top p",
+                                          info="Cumulative probability of tokens to sample from")
+                        top_k = gr.Slider(
+                            minimum=1, maximum=100, step=1,
+                            value=kwargs['top_k'], label="Top k",
+                            info='Num. tokens to sample from'
+                        )
+                        # FIXME: https://github.com/h2oai/h2ogpt/issues/106
+                        if os.getenv('TESTINGFAIL'):
+                            max_beams = 8 if not (memory_restriction_level or is_public) else 1
+                        else:
+                            max_beams = 1
+                        num_beams = gr.Slider(minimum=1, maximum=max_beams, step=1,
+                                              value=min(max_beams, kwargs['num_beams']), label="Beams",
+                                              info="Number of searches for optimal overall probability.  "
+                                                   "Uses more GPU memory/compute",
+                                              interactive=False, visible=max_beams > 1)
+                        max_max_new_tokens = get_max_max_new_tokens(model_state0, **kwargs)
+                        max_new_tokens = gr.Slider(
+                            minimum=1, maximum=max_max_new_tokens, step=1,
+                            value=min(max_max_new_tokens, kwargs['max_new_tokens']), label="Max output length",
+                        )
+                        min_new_tokens = gr.Slider(
+                            minimum=0, maximum=max_max_new_tokens, step=1,
+                            value=min(max_max_new_tokens, kwargs['min_new_tokens']), label="Min output length",
+                        )
+                        max_new_tokens2 = gr.Slider(
+                            minimum=1, maximum=max_max_new_tokens, step=1,
+                            value=min(max_max_new_tokens, kwargs['max_new_tokens']), label="Max output length 2",
+                            visible=False and not kwargs['model_lock'],
+                        )
+                        min_new_tokens2 = gr.Slider(
+                            minimum=0, maximum=max_max_new_tokens, step=1,
+                            value=min(max_max_new_tokens, kwargs['min_new_tokens']), label="Min output length 2",
+                            visible=False and not kwargs['model_lock'],
+                        )
+                        min_max_new_tokens = gr.Slider(
+                            minimum=1, maximum=max_max_new_tokens, step=1,
+                            value=min(max_max_new_tokens, kwargs['min_max_new_tokens']), label="Min. of Max output length",
+                        )
+                        early_stopping = gr.Checkbox(label="EarlyStopping", info="Stop early in beam search",
+                                                     value=kwargs['early_stopping'], visible=max_beams > 1)
+                        repetition_penalty = gr.Slider(minimum=0.01, maximum=3.0,
+                                                       value=kwargs['repetition_penalty'],
+                                                       label="Repetition Penalty")
+                        num_return_sequences = gr.Slider(minimum=1, maximum=10, step=1,
+                                                         value=kwargs['num_return_sequences'],
+                                                         label="Number Returns", info="Must be <= num_beams",
+                                                         interactive=not is_public, visible=max_beams > 1)
+                        chat = gr.components.Checkbox(label="Chat mode", value=kwargs['chat'],
+                                                      visible=False,  # no longer support nochat in UI
+                                                      interactive=not is_public,
+                                                      )
+                    with gr.Row():
+                        count_chat_tokens_btn = gr.Button(value="Count Chat Tokens",
+                                                          visible=not is_public and not kwargs['model_lock'],
+                                                          interactive=not is_public, size='sm')
+                        chat_token_count = gr.Textbox(label="Chat Token Count Result", value=None,
+                                                      visible=not is_public and not kwargs['model_lock'],
+                                                      interactive=False)
+
+                models_tab = gr.TabItem("Models") \
+                    if kwargs['visible_models_tab'] and not bool(kwargs['model_lock']) else gr.Row(visible=False)
+                with models_tab:
+                    load_msg = "Download/Load Model" if not is_public \
+                        else "LOAD-UNLOAD DISABLED FOR HOSTED DEMO"
+                    if kwargs['base_model'] not in ['', None, no_model_str]:
+                        load_msg += '   [WARNING: Avoid --base_model on CLI for memory efficient Load-Unload]'
+                    load_msg2 = load_msg + "(Model 2)"
+                    variant_load_msg = 'primary' if not is_public else 'secondary'
+                    with gr.Row():
+                        n_gpus_list = [str(x) for x in list(range(-1, n_gpus))]
+                        with gr.Column():
+                            with gr.Row():
+                                with gr.Column(scale=20, visible=not kwargs['model_lock']):
+                                    load_model_button = gr.Button(load_msg, variant=variant_load_msg, scale=0,
+                                                                  size='sm', interactive=not is_public)
+                                    model_choice = gr.Dropdown(model_options_state.value[0], label="Choose Base Model",
+                                                               value=kwargs['base_model'])
+                                    lora_choice = gr.Dropdown(lora_options_state.value[0], label="Choose LORA",
+                                                              value=kwargs['lora_weights'], visible=kwargs['show_lora'])
+                                    server_choice = gr.Dropdown(server_options_state.value[0], label="Choose Server",
+                                                                value=kwargs['inference_server'], visible=not is_public)
+                                    max_seq_len = gr.Number(value=kwargs['max_seq_len'] or 2048,
+                                                            minimum=128,
+                                                            maximum=2 ** 18,
+                                                            info="If standard LLaMa-2, choose up to 4096",
+                                                            label="max_seq_len")
+                                    rope_scaling = gr.Textbox(value=str(kwargs['rope_scaling'] or {}),
+                                                              label="rope_scaling")
+                                    row_llama = gr.Row(visible=kwargs['show_llama'] and kwargs['base_model'] == 'llama')
+                                    with row_llama:
+                                        model_path_llama = gr.Textbox(value=kwargs['llamacpp_dict']['model_path_llama'],
+                                                                      lines=4,
+                                                                      label="Choose LLaMa.cpp Model Path/URL (for Base Model: llama)",
+                                                                      visible=kwargs['show_llama'])
+                                        n_gpu_layers = gr.Number(value=kwargs['llamacpp_dict']['n_gpu_layers'],
+                                                                 minimum=0, maximum=100,
+                                                                 label="LLaMa.cpp Num. GPU Layers Offloaded",
+                                                                 visible=kwargs['show_llama'])
+                                        n_batch = gr.Number(value=kwargs['llamacpp_dict']['n_batch'],
+                                                            minimum=0, maximum=2048,
+                                                            label="LLaMa.cpp Batch Size",
+                                                            visible=kwargs['show_llama'])
+                                        n_gqa = gr.Number(value=kwargs['llamacpp_dict']['n_gqa'],
+                                                          minimum=0, maximum=32,
+                                                          label="LLaMa.cpp Num. Group Query Attention (8 for 70B LLaMa2)",
+                                                          visible=kwargs['show_llama'])
+                                        llamacpp_dict_more = gr.Textbox(value="{}",
+                                                                        lines=4,
+                                                                        label="Dict for other LLaMa.cpp/GPT4All options",
+                                                                        visible=kwargs['show_llama'])
+                                    row_gpt4all = gr.Row(
+                                        visible=kwargs['show_gpt4all'] and kwargs['base_model'] in ['gptj',
+                                                                                                    'gpt4all_llama'])
+                                    with row_gpt4all:
+                                        model_name_gptj = gr.Textbox(value=kwargs['llamacpp_dict']['model_name_gptj'],
+                                                                     label="Choose GPT4All GPTJ Model Path/URL (for Base Model: gptj)",
+                                                                     visible=kwargs['show_gpt4all'])
+                                        model_name_gpt4all_llama = gr.Textbox(
+                                            value=kwargs['llamacpp_dict']['model_name_gpt4all_llama'],
+                                            label="Choose GPT4All LLaMa Model Path/URL (for Base Model: gpt4all_llama)",
+                                            visible=kwargs['show_gpt4all'])
+                                with gr.Column(scale=1, visible=not kwargs['model_lock']):
+                                    model_load8bit_checkbox = gr.components.Checkbox(
+                                        label="Load 8-bit [requires support]",
+                                        value=kwargs['load_8bit'], interactive=not is_public)
+                                    model_load4bit_checkbox = gr.components.Checkbox(
+                                        label="Load 4-bit [requires support]",
+                                        value=kwargs['load_4bit'], interactive=not is_public)
+                                    model_low_bit_mode = gr.Slider(value=kwargs['low_bit_mode'],
+                                                                   minimum=0, maximum=4, step=1,
+                                                                   label="low_bit_mode")
+                                    model_load_gptq = gr.Textbox(label="gptq", value=kwargs['load_gptq'],
+                                                                 interactive=not is_public)
+                                    model_load_exllama_checkbox = gr.components.Checkbox(
+                                        label="Load load_exllama [requires support]",
+                                        value=kwargs['load_exllama'], interactive=not is_public)
+                                    model_safetensors_checkbox = gr.components.Checkbox(
+                                        label="Safetensors [requires support]",
+                                        value=kwargs['use_safetensors'], interactive=not is_public)
+                                    model_revision = gr.Textbox(label="revision", value=kwargs['revision'],
+                                                                interactive=not is_public)
+                                    model_use_gpu_id_checkbox = gr.components.Checkbox(
+                                        label="Choose Devices [If not Checked, use all GPUs]",
+                                        value=kwargs['use_gpu_id'], interactive=not is_public,
+                                        visible=n_gpus != 0)
+                                    model_gpu = gr.Dropdown(n_gpus_list,
+                                                            label="GPU ID [-1 = all GPUs, if Choose is enabled]",
+                                                            value=kwargs['gpu_id'], interactive=not is_public,
+                                                            visible=n_gpus != 0)
+                                    model_used = gr.Textbox(label="Current Model", value=kwargs['base_model'],
+                                                            interactive=False)
+                                    lora_used = gr.Textbox(label="Current LORA", value=kwargs['lora_weights'],
+                                                           visible=kwargs['show_lora'], interactive=False)
+                                    server_used = gr.Textbox(label="Current Server",
+                                                             value=kwargs['inference_server'],
+                                                             visible=bool(kwargs['inference_server']) and not is_public,
+                                                             interactive=False)
+                                    prompt_dict = gr.Textbox(label="Prompt (or Custom)",
+                                                             value=pprint.pformat(kwargs['prompt_dict'], indent=4),
+                                                             interactive=not is_public, lines=4)
+                        col_model2 = gr.Column(visible=False)
+                        with col_model2:
+                            with gr.Row():
+                                with gr.Column(scale=20, visible=not kwargs['model_lock']):
+                                    load_model_button2 = gr.Button(load_msg2, variant=variant_load_msg, scale=0,
+                                                                   size='sm', interactive=not is_public)
+                                    model_choice2 = gr.Dropdown(model_options_state.value[0], label="Choose Model 2",
+                                                                value=no_model_str)
+                                    lora_choice2 = gr.Dropdown(lora_options_state.value[0], label="Choose LORA 2",
+                                                               value=no_lora_str,
+                                                               visible=kwargs['show_lora'])
+                                    server_choice2 = gr.Dropdown(server_options_state.value[0], label="Choose Server 2",
+                                                                 value=no_server_str,
+                                                                 visible=not is_public)
+                                    max_seq_len2 = gr.Number(value=kwargs['max_seq_len'] or 2048,
+                                                             minimum=128,
+                                                             maximum=2 ** 18,
+                                                             info="If standard LLaMa-2, choose up to 4096",
+                                                             label="max_seq_len Model 2")
+                                    rope_scaling2 = gr.Textbox(value=str(kwargs['rope_scaling'] or {}),
+                                                               label="rope_scaling Model 2")
+
+                                    row_llama2 = gr.Row(
+                                        visible=kwargs['show_llama'] and kwargs['base_model'] == 'llama')
+                                    with row_llama2:
+                                        model_path_llama2 = gr.Textbox(
+                                            value=kwargs['llamacpp_dict']['model_path_llama'],
+                                            label="Choose LLaMa.cpp Model 2 Path/URL (for Base Model: llama)",
+                                            lines=4,
+                                            visible=kwargs['show_llama'])
+                                        n_gpu_layers2 = gr.Number(value=kwargs['llamacpp_dict']['n_gpu_layers'],
+                                                                  minimum=0, maximum=100,
+                                                                  label="LLaMa.cpp Num. GPU 2 Layers Offloaded",
+                                                                  visible=kwargs['show_llama'])
+                                        n_batch2 = gr.Number(value=kwargs['llamacpp_dict']['n_batch'],
+                                                             minimum=0, maximum=2048,
+                                                             label="LLaMa.cpp Model 2 Batch Size",
+                                                             visible=kwargs['show_llama'])
+                                        n_gqa2 = gr.Number(value=kwargs['llamacpp_dict']['n_gqa'],
+                                                           minimum=0, maximum=32,
+                                                           label="LLaMa.cpp Model 2 Num. Group Query Attention (8 for 70B LLaMa2)",
+                                                           visible=kwargs['show_llama'])
+                                        llamacpp_dict_more2 = gr.Textbox(value="{}",
+                                                                         lines=4,
+                                                                         label="Model 2 Dict for other LLaMa.cpp/GPT4All options",
+                                                                         visible=kwargs['show_llama'])
+                                    row_gpt4all2 = gr.Row(
+                                        visible=kwargs['show_gpt4all'] and kwargs['base_model'] in ['gptj',
+                                                                                                    'gpt4all_llama'])
+                                    with row_gpt4all2:
+                                        model_name_gptj2 = gr.Textbox(value=kwargs['llamacpp_dict']['model_name_gptj'],
+                                                                      label="Choose GPT4All GPTJ Model 2 Path/URL (for Base Model: gptj)",
+                                                                      visible=kwargs['show_gpt4all'])
+                                        model_name_gpt4all_llama2 = gr.Textbox(
+                                            value=kwargs['llamacpp_dict']['model_name_gpt4all_llama'],
+                                            label="Choose GPT4All LLaMa Model 2 Path/URL (for Base Model: gpt4all_llama)",
+                                            visible=kwargs['show_gpt4all'])
+
+                                with gr.Column(scale=1, visible=not kwargs['model_lock']):
+                                    model_load8bit_checkbox2 = gr.components.Checkbox(
+                                        label="Load 8-bit (Model 2) [requires support]",
+                                        value=kwargs['load_8bit'], interactive=not is_public)
+                                    model_load4bit_checkbox2 = gr.components.Checkbox(
+                                        label="Load 4-bit (Model 2) [requires support]",
+                                        value=kwargs['load_4bit'], interactive=not is_public)
+                                    model_low_bit_mode2 = gr.Slider(value=kwargs['low_bit_mode'],
+                                                                    # ok that same as Model 1
+                                                                    minimum=0, maximum=4, step=1,
+                                                                    label="low_bit_mode (Model 2)")
+                                    model_load_gptq2 = gr.Textbox(label="gptq (Model 2)", value='',
+                                                                  interactive=not is_public)
+                                    model_load_exllama_checkbox2 = gr.components.Checkbox(
+                                        label="Load load_exllama (Model 2) [requires support]",
+                                        value=False, interactive=not is_public)
+                                    model_safetensors_checkbox2 = gr.components.Checkbox(
+                                        label="Safetensors (Model 2) [requires support]",
+                                        value=False, interactive=not is_public)
+                                    model_revision2 = gr.Textbox(label="revision (Model 2)", value='',
+                                                                 interactive=not is_public)
+                                    model_use_gpu_id_checkbox2 = gr.components.Checkbox(
+                                        label="Choose Devices (Model 2) [If not Checked, use all GPUs]",
+                                        value=kwargs[
+                                            'use_gpu_id'], interactive=not is_public)
+                                    model_gpu2 = gr.Dropdown(n_gpus_list,
+                                                             label="GPU ID (Model 2) [-1 = all GPUs, if choose is enabled]",
+                                                             value=kwargs['gpu_id'], interactive=not is_public)
+                                    # no model/lora loaded ever in model2 by default
+                                    model_used2 = gr.Textbox(label="Current Model 2", value=no_model_str,
+                                                             interactive=False)
+                                    lora_used2 = gr.Textbox(label="Current LORA (Model 2)", value=no_lora_str,
+                                                            visible=kwargs['show_lora'], interactive=False)
+                                    server_used2 = gr.Textbox(label="Current Server (Model 2)", value=no_server_str,
+                                                              interactive=False,
+                                                              visible=not is_public)
+                                    prompt_dict2 = gr.Textbox(label="Prompt (or Custom) (Model 2)",
+                                                              value=pprint.pformat(kwargs['prompt_dict'], indent=4),
+                                                              interactive=not is_public, lines=4)
+                    compare_checkbox = gr.components.Checkbox(label="Compare Two Models",
+                                                              value=kwargs['model_lock'],
+                                                              visible=not is_public and not kwargs['model_lock'])
+                    with gr.Row(visible=not kwargs['model_lock']):
+                        with gr.Column(scale=50):
+                            new_model = gr.Textbox(label="New Model name/path/URL", interactive=not is_public)
+                        with gr.Column(scale=50):
+                            new_lora = gr.Textbox(label="New LORA name/path/URL", visible=kwargs['show_lora'],
+                                                  interactive=not is_public)
+                        with gr.Column(scale=50):
+                            new_server = gr.Textbox(label="New Server url:port", interactive=not is_public)
+                        with gr.Row():
+                            add_model_lora_server_button = gr.Button("Add new Model, Lora, Server url:port", scale=0,
+                                                                     variant=variant_load_msg,
+                                                                     size='sm', interactive=not is_public)
+                system_tab = gr.TabItem("System") \
+                    if kwargs['visible_system_tab'] else gr.Row(visible=False)
+                with system_tab:
+                    with gr.Row():
+                        with gr.Column(scale=1):
+                            side_bar_text = gr.Textbox('on' if kwargs['visible_side_bar'] else 'off',
+                                                       visible=False, interactive=False)
+                            doc_count_text = gr.Textbox('on' if kwargs['visible_doc_track'] else 'off',
+                                                        visible=False, interactive=False)
+                            submit_buttons_text = gr.Textbox('on' if kwargs['visible_submit_buttons'] else 'off',
+                                                             visible=False, interactive=False)
+                            visible_models_text = gr.Textbox('on' if kwargs['visible_visible_models'] else 'off',
+                                                             visible=False, interactive=False)
+
+                            side_bar_btn = gr.Button("Toggle SideBar", variant="secondary", size="sm")
+                            doc_count_btn = gr.Button("Toggle SideBar Document Count/Show Newest", variant="secondary",
+                                                      size="sm")
+                            submit_buttons_btn = gr.Button("Toggle Submit Buttons", variant="secondary", size="sm")
+                            visible_model_btn = gr.Button("Toggle Visible Models", variant="secondary", size="sm")
+                            col_tabs_scale = gr.Slider(minimum=1, maximum=20, value=10, step=1, label='Window Size')
+                            text_outputs_height = gr.Slider(minimum=100, maximum=2000, value=kwargs['height'] or 400,
+                                                            step=50, label='Chat Height')
+                            dark_mode_btn = gr.Button("Dark Mode", variant="secondary", size="sm")
+                        with gr.Column(scale=4):
+                            pass
+                    system_visible0 = not is_public and not admin_pass
+                    admin_row = gr.Row()
+                    with admin_row:
+                        with gr.Column(scale=1):
+                            admin_pass_textbox = gr.Textbox(label="Admin Password",
+                                                            type='password',
+                                                            visible=not system_visible0)
+                        with gr.Column(scale=4):
+                            pass
+                    system_row = gr.Row(visible=system_visible0)
+                    with system_row:
+                        with gr.Column():
+                            with gr.Row():
+                                system_btn = gr.Button(value='Get System Info', size='sm')
+                                system_text = gr.Textbox(label='System Info', interactive=False, show_copy_button=True)
+                            with gr.Row():
+                                system_input = gr.Textbox(label='System Info Dict Password', interactive=True,
+                                                          visible=not is_public)
+                                system_btn2 = gr.Button(value='Get System Info Dict', visible=not is_public, size='sm')
+                                system_text2 = gr.Textbox(label='System Info Dict', interactive=False,
+                                                          visible=not is_public, show_copy_button=True)
+                            with gr.Row():
+                                system_btn3 = gr.Button(value='Get Hash', visible=not is_public, size='sm')
+                                system_text3 = gr.Textbox(label='Hash', interactive=False,
+                                                          visible=not is_public, show_copy_button=True)
+                                system_btn4 = gr.Button(value='Get Model Names', visible=not is_public, size='sm')
+                                system_text4 = gr.Textbox(label='Model Names', interactive=False,
+                                                          visible=not is_public, show_copy_button=True)
+
+                            with gr.Row():
+                                zip_btn = gr.Button("Zip", size='sm')
+                                zip_text = gr.Textbox(label="Zip file name", interactive=False)
+                                file_output = gr.File(interactive=False, label="Zip file to Download")
+                            with gr.Row():
+                                s3up_btn = gr.Button("S3UP", size='sm')
+                                s3up_text = gr.Textbox(label='S3UP result', interactive=False)
+
+                tos_tab = gr.TabItem("Terms of Service") \
+                    if kwargs['visible_tos_tab'] else gr.Row(visible=False)
+                with tos_tab:
+                    description = ""
+                    description += """<p><b> DISCLAIMERS: </b><ul><i><li>The model was trained on The Pile and other data, which may contain objectionable content.  Use at own risk.</i></li>"""
+                    if kwargs['load_8bit']:
+                        description += """<i><li> Model is loaded in 8-bit and has other restrictions on this host. UX can be worse than non-hosted version.</i></li>"""
+                    description += """<i><li>Conversations may be used to improve h2oGPT.  Do not share sensitive information.</i></li>"""
+                    if 'h2ogpt-research' in kwargs['base_model']:
+                        description += """<i><li>Research demonstration only, not used for commercial purposes.</i></li>"""
+                    description += """<i><li>By using h2oGPT, you accept our <a href="https://github.com/h2oai/h2ogpt/blob/main/docs/tos.md">Terms of Service</a></i></li></ul></p>"""
+                    gr.Markdown(value=description, show_label=False, interactive=False)
+
+                login_tab = gr.TabItem("Login") \
+                    if kwargs['visible_login_tab'] else gr.Row(visible=False)
+                with login_tab:
+                    gr.Markdown(
+                        value="#### Login page to persist your state (database, documents, chat, chat history)\nDaily maintenance at midnight PST will not allow reconnection to state otherwise.")
+                    username_text = gr.Textbox(label="Username")
+                    password_text = gr.Textbox(label="Password", type='password', visible=True)
+                    login_msg = "Login (pick unique user/pass to persist your state)" if kwargs[
+                                                                                             'auth_access'] == 'open' else "Login (closed access)"
+                    login_btn = gr.Button(value=login_msg)
+                    login_result_text = gr.Text(label="Login Result", interactive=False)
+                    h2ogpt_key = gr.Text(value=kwargs['h2ogpt_key'], label="h2oGPT Token for API access",
+                                         type='password', visible=False)
+
+                hosts_tab = gr.TabItem("Hosts") \
+                    if kwargs['visible_hosts_tab'] else gr.Row(visible=False)
+                with hosts_tab:
+                    gr.Markdown(f"""
+                        {description_bottom}
+                        {task_info_md}
+                        """)
+
+        # Get flagged data
+        zip_data1 = functools.partial(zip_data, root_dirs=['flagged_data_points', kwargs['save_dir']])
+        zip_event = zip_btn.click(zip_data1, inputs=None, outputs=[file_output, zip_text], queue=False,
+                                  api_name='zip_data' if allow_api else None)
+        s3up_event = s3up_btn.click(s3up, inputs=zip_text, outputs=s3up_text, queue=False,
+                                    api_name='s3up_data' if allow_api else None)
+
+        def clear_file_list():
+            return None
+
+        def set_loaders(max_quality1,
+                        image_loaders_options1=None,
+                        pdf_loaders_options1=None,
+                        url_loaders_options1=None,
+                        image_loaders_options01=None,
+                        pdf_loaders_options01=None,
+                        url_loaders_options01=None,
+                        ):
+            if not max_quality1:
+                return image_loaders_options01, pdf_loaders_options01, url_loaders_options01
+            else:
+                return image_loaders_options1, pdf_loaders_options1, url_loaders_options1
+
+        set_loaders_func = functools.partial(set_loaders,
+                                             image_loaders_options1=image_loaders_options,
+                                             pdf_loaders_options1=pdf_loaders_options,
+                                             url_loaders_options1=url_loaders_options,
+                                             image_loaders_options01=image_loaders_options0,
+                                             pdf_loaders_options01=pdf_loaders_options0,
+                                             url_loaders_options01=url_loaders_options0,
+                                             )
+
+        max_quality.change(fn=set_loaders_func,
+                           inputs=max_quality,
+                           outputs=[image_loaders, pdf_loaders, url_loaders])
+
+        def get_model_lock_visible_list(visible_models1, all_models):
+            visible_list = []
+            for modeli, model in enumerate(all_models):
+                if visible_models1 is None or model in visible_models1 or modeli in visible_models1:
+                    visible_list.append(True)
+                else:
+                    visible_list.append(False)
+            return visible_list
+
+        def set_visible_models(visible_models1, num_model_lock=0, all_models=None):
+            if num_model_lock == 0:
+                num_model_lock = 3  # 2 + 1 (which is dup of first)
+                ret_list = [gr.update(visible=True)] * num_model_lock
+            else:
+                assert isinstance(all_models, list)
+                assert num_model_lock == len(all_models)
+                visible_list = [False, False] + get_model_lock_visible_list(visible_models1, all_models)
+                ret_list = [gr.update(visible=x) for x in visible_list]
+            return tuple(ret_list)
+
+        visible_models_func = functools.partial(set_visible_models,
+                                                num_model_lock=len(text_outputs),
+                                                all_models=kwargs['all_models'])
+        visible_models.change(fn=visible_models_func,
+                              inputs=visible_models,
+                              outputs=[text_output, text_output2] + text_outputs,
+                              )
+
+        # Add to UserData or custom user db
+        update_db_func = functools.partial(update_user_db_gr,
+                                           dbs=dbs,
+                                           db_type=db_type,
+                                           use_openai_embedding=use_openai_embedding,
+                                           hf_embedding_model=hf_embedding_model,
+                                           migrate_embedding_model=migrate_embedding_model,
+                                           auto_migrate_db=auto_migrate_db,
+                                           captions_model=captions_model,
+                                           caption_loader=caption_loader,
+                                           doctr_loader=doctr_loader,
+                                           verbose=kwargs['verbose'],
+                                           n_jobs=kwargs['n_jobs'],
+                                           get_userid_auth=get_userid_auth,
+                                           image_loaders_options0=image_loaders_options0,
+                                           pdf_loaders_options0=pdf_loaders_options0,
+                                           url_loaders_options0=url_loaders_options0,
+                                           jq_schema0=jq_schema0,
+                                           enforce_h2ogpt_api_key=kwargs['enforce_h2ogpt_api_key'],
+                                           h2ogpt_api_keys=kwargs['h2ogpt_api_keys'],
+                                           )
+        add_file_outputs = [fileup_output, langchain_mode]
+        add_file_kwargs = dict(fn=update_db_func,
+                               inputs=[fileup_output, my_db_state, selection_docs_state, requests_state,
+                                       langchain_mode, chunk, chunk_size, embed,
+                                       image_loaders,
+                                       pdf_loaders,
+                                       url_loaders,
+                                       jq_schema,
+                                       h2ogpt_key,
+                                       ],
+                               outputs=add_file_outputs + [sources_text, doc_exception_text, text_file_last],
+                               queue=queue,
+                               api_name='add_file' if allow_upload_api else None)
+
+        # then no need for add buttons, only single changeable db
+        user_state_kwargs = dict(fn=user_state_setup,
+                                 inputs=[my_db_state, requests_state, langchain_mode],
+                                 outputs=[my_db_state, requests_state, langchain_mode],
+                                 show_progress='minimal')
+        eventdb1a = fileup_output.upload(**user_state_kwargs)
+        eventdb1 = eventdb1a.then(**add_file_kwargs, show_progress='full')
+
+        event_attach1 = attach_button.upload(**user_state_kwargs)
+        attach_file_kwargs = add_file_kwargs.copy()
+        attach_file_kwargs['inputs'][0] = attach_button
+        attach_file_kwargs['outputs'][0] = attach_button
+        attach_file_kwargs['api_name'] = 'attach_file'
+        event_attach2 = event_attach1.then(**attach_file_kwargs, show_progress='full')
+
+        sync1 = sync_sources_btn.click(**user_state_kwargs)
+
+        # deal with challenge to have fileup_output itself as input
+        add_file_kwargs2 = dict(fn=update_db_func,
+                                inputs=[fileup_output_text, my_db_state, selection_docs_state, requests_state,
+                                        langchain_mode, chunk, chunk_size, embed,
+                                        image_loaders,
+                                        pdf_loaders,
+                                        url_loaders,
+                                        jq_schema,
+                                        h2ogpt_key,
+                                        ],
+                                outputs=add_file_outputs + [sources_text, doc_exception_text, text_file_last],
+                                queue=queue,
+                                api_name='add_file_api' if allow_upload_api else None)
+        eventdb1_api = fileup_output_text.submit(**add_file_kwargs2, show_progress='full')
+
+        # note for update_user_db_func output is ignored for db
+
+        def clear_textbox():
+            return gr.Textbox.update(value='')
+
+        update_user_db_url_func = functools.partial(update_db_func, is_url=True)
+
+        add_url_outputs = [url_text, langchain_mode]
+        add_url_kwargs = dict(fn=update_user_db_url_func,
+                              inputs=[url_text, my_db_state, selection_docs_state, requests_state,
+                                      langchain_mode, chunk, chunk_size, embed,
+                                      image_loaders,
+                                      pdf_loaders,
+                                      url_loaders,
+                                      jq_schema,
+                                      h2ogpt_key,
+                                      ],
+                              outputs=add_url_outputs + [sources_text, doc_exception_text, text_file_last],
+                              queue=queue,
+                              api_name='add_url' if allow_upload_api else None)
+
+        eventdb2a = url_text.submit(fn=user_state_setup,
+                                    inputs=[my_db_state, requests_state, url_text, url_text],
+                                    outputs=[my_db_state, requests_state, url_text],
+                                    queue=queue,
+                                    show_progress='minimal')
+        # work around https://github.com/gradio-app/gradio/issues/4733
+        eventdb2 = eventdb2a.then(**add_url_kwargs, show_progress='full')
+
+        update_user_db_txt_func = functools.partial(update_db_func, is_txt=True)
+        add_text_outputs = [user_text_text, langchain_mode]
+        add_text_kwargs = dict(fn=update_user_db_txt_func,
+                               inputs=[user_text_text, my_db_state, selection_docs_state, requests_state,
+                                       langchain_mode, chunk, chunk_size, embed,
+                                       image_loaders,
+                                       pdf_loaders,
+                                       url_loaders,
+                                       jq_schema,
+                                       h2ogpt_key,
+                                       ],
+                               outputs=add_text_outputs + [sources_text, doc_exception_text, text_file_last],
+                               queue=queue,
+                               api_name='add_text' if allow_upload_api else None
+                               )
+        eventdb3a = user_text_text.submit(fn=user_state_setup,
+                                          inputs=[my_db_state, requests_state, user_text_text, user_text_text],
+                                          outputs=[my_db_state, requests_state, user_text_text],
+                                          queue=queue,
+                                          show_progress='minimal')
+        eventdb3 = eventdb3a.then(**add_text_kwargs, show_progress='full')
+
+        db_events = [eventdb1a, eventdb1, eventdb1_api,
+                     eventdb2a, eventdb2,
+                     eventdb3a, eventdb3]
+        db_events.extend([event_attach1, event_attach2])
+
+        get_sources1 = functools.partial(get_sources_gr, dbs=dbs, docs_state0=docs_state0,
+                                         load_db_if_exists=load_db_if_exists,
+                                         db_type=db_type,
+                                         use_openai_embedding=use_openai_embedding,
+                                         hf_embedding_model=hf_embedding_model,
+                                         migrate_embedding_model=migrate_embedding_model,
+                                         auto_migrate_db=auto_migrate_db,
+                                         verbose=verbose,
+                                         get_userid_auth=get_userid_auth,
+                                         n_jobs=n_jobs,
+                                         )
+
+        # if change collection source, must clear doc selections from it to avoid inconsistency
+        def clear_doc_choice(langchain_mode1):
+            if langchain_mode1 in langchain_modes_non_db:
+                label1 = 'Choose Resources->Collections and Pick Collection'
+                active_collection1 = "#### Not Chatting with Any Collection\n%s" % label1
+            else:
+                label1 = 'Select Subset of Document(s) for Chat with Collection: %s' % langchain_mode1
+                active_collection1 = "#### Chatting with Collection: %s" % langchain_mode1
+            return gr.Dropdown.update(choices=docs_state0, value=DocumentChoice.ALL.value,
+                                      label=label1), gr.Markdown.update(value=active_collection1)
+
+        lg_change_event = langchain_mode.change(clear_doc_choice, inputs=langchain_mode,
+                                                outputs=[document_choice, active_collection],
+                                                queue=not kwargs['large_file_count_mode'])
+
+        def change_visible_llama(x):
+            if x == 'llama':
+                return gr.update(visible=True), \
+                    gr.update(visible=True), \
+                    gr.update(visible=False), \
+                    gr.update(visible=False)
+            elif x in ['gptj', 'gpt4all_llama']:
+                return gr.update(visible=False), \
+                    gr.update(visible=False), \
+                    gr.update(visible=True), \
+                    gr.update(visible=True)
+            else:
+                return gr.update(visible=False), \
+                    gr.update(visible=False), \
+                    gr.update(visible=False), \
+                    gr.update(visible=False)
+
+        model_choice.change(change_visible_llama,
+                            inputs=model_choice,
+                            outputs=[row_llama, row_llama2, row_gpt4all, row_gpt4all2])
+
+        def resize_col_tabs(x):
+            return gr.Dropdown.update(scale=x)
+
+        col_tabs_scale.change(fn=resize_col_tabs, inputs=col_tabs_scale, outputs=col_tabs, queue=False)
+
+        def resize_chatbots(x, num_model_lock=0):
+            if num_model_lock == 0:
+                num_model_lock = 3  # 2 + 1 (which is dup of first)
+            else:
+                num_model_lock = 2 + num_model_lock
+            return tuple([gr.update(height=x)] * num_model_lock)
+
+        resize_chatbots_func = functools.partial(resize_chatbots, num_model_lock=len(text_outputs))
+        text_outputs_height.change(fn=resize_chatbots_func, inputs=text_outputs_height,
+                                   outputs=[text_output, text_output2] + text_outputs, queue=False)
+
+        def update_dropdown(x):
+            if DocumentChoice.ALL.value in x:
+                x.remove(DocumentChoice.ALL.value)
+            source_list = [DocumentChoice.ALL.value] + x
+            return gr.Dropdown.update(choices=source_list, value=[DocumentChoice.ALL.value])
+
+        get_sources_kwargs = dict(fn=get_sources1,
+                                  inputs=[my_db_state, selection_docs_state, requests_state, langchain_mode],
+                                  outputs=[file_source, docs_state, text_doc_count],
+                                  queue=queue)
+
+        eventdb7a = get_sources_btn.click(user_state_setup,
+                                          inputs=[my_db_state, requests_state, get_sources_btn, get_sources_btn],
+                                          outputs=[my_db_state, requests_state, get_sources_btn],
+                                          show_progress='minimal')
+        eventdb7 = eventdb7a.then(**get_sources_kwargs,
+                                  api_name='get_sources' if allow_api else None) \
+            .then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+
+        get_sources_api_args = dict(fn=functools.partial(get_sources1, api=True),
+                                    inputs=[my_db_state, selection_docs_state, requests_state, langchain_mode],
+                                    outputs=get_sources_api_text,
+                                    queue=queue)
+        get_sources_api_btn.click(**get_sources_api_args,
+                                  api_name='get_sources_api' if allow_api else None)
+
+        # show button, else only show when add.
+        # Could add to above get_sources for download/dropdown, but bit much maybe
+        show_sources1 = functools.partial(get_source_files_given_langchain_mode_gr,
+                                          dbs=dbs,
+                                          load_db_if_exists=load_db_if_exists,
+                                          db_type=db_type,
+                                          use_openai_embedding=use_openai_embedding,
+                                          hf_embedding_model=hf_embedding_model,
+                                          migrate_embedding_model=migrate_embedding_model,
+                                          auto_migrate_db=auto_migrate_db,
+                                          verbose=verbose,
+                                          get_userid_auth=get_userid_auth,
+                                          n_jobs=n_jobs)
+        eventdb8a = show_sources_btn.click(user_state_setup,
+                                           inputs=[my_db_state, requests_state, show_sources_btn, show_sources_btn],
+                                           outputs=[my_db_state, requests_state, show_sources_btn],
+                                           show_progress='minimal')
+        show_sources_kwargs = dict(fn=show_sources1,
+                                   inputs=[my_db_state, selection_docs_state, requests_state, langchain_mode],
+                                   outputs=sources_text)
+        eventdb8 = eventdb8a.then(**show_sources_kwargs,
+                                  api_name='show_sources' if allow_api else None)
+
+        def update_viewable_dropdown(x):
+            return gr.Dropdown.update(choices=x,
+                                      value=viewable_docs_state0[0] if len(viewable_docs_state0) > 0 else None)
+
+        get_viewable_sources1 = functools.partial(get_sources_gr, dbs=dbs, docs_state0=viewable_docs_state0,
+                                                  load_db_if_exists=load_db_if_exists,
+                                                  db_type=db_type,
+                                                  use_openai_embedding=use_openai_embedding,
+                                                  hf_embedding_model=hf_embedding_model,
+                                                  migrate_embedding_model=migrate_embedding_model,
+                                                  auto_migrate_db=auto_migrate_db,
+                                                  verbose=kwargs['verbose'],
+                                                  get_userid_auth=get_userid_auth,
+                                                  n_jobs=n_jobs)
+        get_viewable_sources_args = dict(fn=get_viewable_sources1,
+                                         inputs=[my_db_state, selection_docs_state, requests_state, langchain_mode],
+                                         outputs=[file_source, viewable_docs_state, text_viewable_doc_count],
+                                         queue=queue)
+        eventdb12a = get_viewable_sources_btn.click(user_state_setup,
+                                                    inputs=[my_db_state, requests_state,
+                                                            get_viewable_sources_btn, get_viewable_sources_btn],
+                                                    outputs=[my_db_state, requests_state, get_viewable_sources_btn],
+                                                    show_progress='minimal')
+        viewable_kwargs = dict(fn=update_viewable_dropdown, inputs=viewable_docs_state, outputs=view_document_choice)
+        eventdb12 = eventdb12a.then(**get_viewable_sources_args,
+                                    api_name='get_viewable_sources' if allow_api else None) \
+            .then(**viewable_kwargs)
+
+        eventdb_viewa = view_document_choice.select(user_state_setup,
+                                                    inputs=[my_db_state, requests_state,
+                                                            view_document_choice, view_document_choice],
+                                                    outputs=[my_db_state, requests_state, view_document_choice],
+                                                    show_progress='minimal')
+        show_doc_func = functools.partial(show_doc,
+                                          dbs1=dbs,
+                                          load_db_if_exists1=load_db_if_exists,
+                                          db_type1=db_type,
+                                          use_openai_embedding1=use_openai_embedding,
+                                          hf_embedding_model1=hf_embedding_model,
+                                          migrate_embedding_model_or_db1=migrate_embedding_model,
+                                          auto_migrate_db1=auto_migrate_db,
+                                          verbose1=verbose,
+                                          get_userid_auth1=get_userid_auth,
+                                          max_raw_chunks=kwargs['max_raw_chunks'],
+                                          api=False,
+                                          n_jobs=n_jobs,
+                                          )
+        # Note: Not really useful for API, so no api_name
+        eventdb_viewa.then(fn=show_doc_func,
+                           inputs=[my_db_state, selection_docs_state, requests_state, langchain_mode,
+                                   view_document_choice, view_raw_text_checkbox,
+                                   text_context_list],
+                           outputs=[doc_view, doc_view2, doc_view3, doc_view4, doc_view5])
+
+        show_doc_func_api = functools.partial(show_doc_func, api=True)
+        get_document_api_btn.click(fn=show_doc_func_api,
+                                   inputs=[my_db_state, selection_docs_state, requests_state, langchain_mode,
+                                           view_document_choice, view_raw_text_checkbox,
+                                           text_context_list],
+                                   outputs=get_document_api_text, api_name='get_document_api')
+
+        # Get inputs to evaluate() and make_db()
+        # don't deepcopy, can contain model itself
+        all_kwargs = kwargs.copy()
+        all_kwargs.update(locals())
+
+        refresh_sources1 = functools.partial(update_and_get_source_files_given_langchain_mode_gr,
+                                             captions_model=captions_model,
+                                             caption_loader=caption_loader,
+                                             doctr_loader=doctr_loader,
+                                             dbs=dbs,
+                                             first_para=kwargs['first_para'],
+                                             hf_embedding_model=hf_embedding_model,
+                                             use_openai_embedding=use_openai_embedding,
+                                             migrate_embedding_model=migrate_embedding_model,
+                                             auto_migrate_db=auto_migrate_db,
+                                             text_limit=kwargs['text_limit'],
+                                             db_type=db_type,
+                                             load_db_if_exists=load_db_if_exists,
+                                             n_jobs=n_jobs, verbose=verbose,
+                                             get_userid_auth=get_userid_auth,
+                                             image_loaders_options0=image_loaders_options0,
+                                             pdf_loaders_options0=pdf_loaders_options0,
+                                             url_loaders_options0=url_loaders_options0,
+                                             jq_schema0=jq_schema0,
+                                             )
+        eventdb9a = refresh_sources_btn.click(user_state_setup,
+                                              inputs=[my_db_state, requests_state,
+                                                      refresh_sources_btn, refresh_sources_btn],
+                                              outputs=[my_db_state, requests_state, refresh_sources_btn],
+                                              show_progress='minimal')
+        eventdb9 = eventdb9a.then(fn=refresh_sources1,
+                                  inputs=[my_db_state, selection_docs_state, requests_state,
+                                          langchain_mode, chunk, chunk_size,
+                                          image_loaders,
+                                          pdf_loaders,
+                                          url_loaders,
+                                          jq_schema,
+                                          ],
+                                  outputs=sources_text,
+                                  api_name='refresh_sources' if allow_api else None)
+
+        delete_sources1 = functools.partial(del_source_files_given_langchain_mode_gr,
+                                            dbs=dbs,
+                                            load_db_if_exists=load_db_if_exists,
+                                            db_type=db_type,
+                                            use_openai_embedding=use_openai_embedding,
+                                            hf_embedding_model=hf_embedding_model,
+                                            migrate_embedding_model=migrate_embedding_model,
+                                            auto_migrate_db=auto_migrate_db,
+                                            verbose=verbose,
+                                            get_userid_auth=get_userid_auth,
+                                            n_jobs=n_jobs)
+        eventdb90a = delete_sources_btn.click(user_state_setup,
+                                              inputs=[my_db_state, requests_state,
+                                                      delete_sources_btn, delete_sources_btn],
+                                              outputs=[my_db_state, requests_state, delete_sources_btn],
+                                              show_progress='minimal')
+        eventdb90 = eventdb90a.then(fn=delete_sources1,
+                                    inputs=[my_db_state, selection_docs_state, requests_state, document_choice,
+                                            langchain_mode],
+                                    outputs=sources_text,
+                                    api_name='delete_sources' if allow_api else None)
+        db_events.extend([eventdb90a, eventdb90])
+
+        def check_admin_pass(x):
+            return gr.update(visible=x == admin_pass)
+
+        def close_admin(x):
+            return gr.update(visible=not (x == admin_pass))
+
+        eventdb_logina = login_btn.click(user_state_setup,
+                                         inputs=[my_db_state, requests_state, login_btn, login_btn],
+                                         outputs=[my_db_state, requests_state, login_btn],
+                                         show_progress='minimal')
+
+        def login(db1s, selection_docs_state1, requests_state1, chat_state1, langchain_mode1,
+                  username1, password1,
+                  text_output1, text_output21, *text_outputs1,
+                  auth_filename=None, num_model_lock=0, pre_authorized=False):
+            # use full auth login to allow new users if open access etc.
+            if pre_authorized:
+                username1 = requests_state1['username']
+                password1 = None
+                authorized1 = True
+            else:
+                authorized1 = authf(username1, password1, selection_docs_state1=selection_docs_state1)
+            if authorized1:
+                set_userid_gr(db1s, requests_state1, get_userid_auth)
+                username2 = get_username(requests_state1)
+                text_outputs1 = list(text_outputs1)
+
+                success1, text_result, text_output1, text_output21, text_outputs1, langchain_mode1 = \
+                    load_auth(db1s, requests_state1, auth_filename, selection_docs_state1=selection_docs_state1,
+                              chat_state1=chat_state1, langchain_mode1=langchain_mode1,
+                              text_output1=text_output1, text_output21=text_output21, text_outputs1=text_outputs1,
+                              username_override=username1, password_to_check=password1)
+            else:
+                success1 = False
+                text_result = "Wrong password for user %s" % username1
+            df_langchain_mode_paths1 = get_df_langchain_mode_paths(selection_docs_state1, db1s, dbs1=dbs)
+            if success1:
+                requests_state1['username'] = username1
+            label_instruction1 = 'Ask anything, %s' % requests_state1['username']
+            return db1s, selection_docs_state1, requests_state1, chat_state1, \
+                text_result, \
+                gr.update(label=label_instruction1), \
+                df_langchain_mode_paths1, \
+                gr.update(choices=list(chat_state1.keys()), value=None), \
+                gr.update(choices=get_langchain_choices(selection_docs_state1),
+                          value=langchain_mode1), \
+                text_output1, text_output21, *tuple(text_outputs1)
+
+        login_func = functools.partial(login,
+                                       auth_filename=kwargs['auth_filename'],
+                                       num_model_lock=len(text_outputs),
+                                       pre_authorized=False,
+                                       )
+        load_login_func = functools.partial(login,
+                                            auth_filename=kwargs['auth_filename'],
+                                            num_model_lock=len(text_outputs),
+                                            pre_authorized=True,
+                                            )
+        login_inputs = [my_db_state, selection_docs_state, requests_state, chat_state,
+                        langchain_mode,
+                        username_text, password_text,
+                        text_output, text_output2] + text_outputs
+        login_outputs = [my_db_state, selection_docs_state, requests_state, chat_state,
+                         login_result_text,
+                         instruction,
+                         langchain_mode_path_text,
+                         radio_chats,
+                         langchain_mode,
+                         text_output, text_output2] + text_outputs
+        eventdb_logina.then(login_func,
+                            inputs=login_inputs,
+                            outputs=login_outputs,
+                            queue=False)
+
+        admin_pass_textbox.submit(check_admin_pass, inputs=admin_pass_textbox, outputs=system_row, queue=False) \
+            .then(close_admin, inputs=admin_pass_textbox, outputs=admin_row, queue=False)
+
+        def load_auth(db1s, requests_state1, auth_filename=None, selection_docs_state1=None,
+                      chat_state1=None, langchain_mode1=None,
+                      text_output1=None, text_output21=None, text_outputs1=None,
+                      username_override=None, password_to_check=None):
+            # in-place assignment
+            if not auth_filename:
+                return False, "No auth file", text_output1, text_output21, text_outputs1
+            # if first time here, need to set userID
+            set_userid_gr(db1s, requests_state1, get_userid_auth)
+            if username_override:
+                username1 = username_override
+            else:
+                username1 = get_username(requests_state1)
+            success1 = False
+            with filelock.FileLock(auth_filename + '.lock'):
+                if os.path.isfile(auth_filename):
+                    with open(auth_filename, 'rt') as f:
+                        auth_dict = json.load(f)
+                        if username1 in auth_dict:
+                            auth_user = auth_dict[username1]
+                            if password_to_check:
+                                if auth_user['password'] != password_to_check:
+                                    return False, [], [], [], "Invalid password for user %s" % username1
+                            if username_override:
+                                # then use original user id
+                                set_userid_direct_gr(db1s, auth_dict[username1]['userid'], username1)
+                            if 'selection_docs_state' in auth_user:
+                                update_auth_selection(auth_user, selection_docs_state1)
+                            if 'chat_state' in auth_user:
+                                chat_state1.update(auth_user['chat_state'])
+                            if 'text_output' in auth_user:
+                                text_output1 = auth_user['text_output']
+                            if 'text_output2' in auth_user:
+                                text_output21 = auth_user['text_output2']
+                            if 'text_outputs' in auth_user:
+                                text_outputs1 = auth_user['text_outputs']
+                            if 'langchain_mode' in auth_user:
+                                langchain_mode1 = auth_user['langchain_mode']
+                            text_result = "Successful login for %s" % username1
+                            success1 = True
+                        else:
+                            text_result = "No user %s" % username1
+                else:
+                    text_result = "No auth file"
+            return success1, text_result, text_output1, text_output21, text_outputs1, langchain_mode1
+
+        def save_auth_dict(auth_dict, auth_filename):
+            backup_file = auth_filename + '.bak' + str(uuid.uuid4())
+            if os.path.isfile(auth_filename):
+                shutil.copy(auth_filename, backup_file)
+            try:
+                with open(auth_filename, 'wt') as f:
+                    f.write(json.dumps(auth_dict, indent=2))
+            except BaseException as e:
+                print("Failure to save auth %s, restored backup: %s: %s" % (auth_filename, backup_file, str(e)),
+                      flush=True)
+                shutil.copy(backup_file, auth_dict)
+                if os.getenv('HARD_ASSERTS'):
+                    # unexpected in testing or normally
+                    raise
+
+        def save_auth(requests_state1, auth_filename, auth_freeze,
+                      selection_docs_state1=None, chat_state1=None, langchain_mode1=None,
+                      text_output1=None, text_output21=None, text_outputs1=None):
+            if auth_freeze:
+                return
+            if not auth_filename:
+                return
+            # save to auth file
+            username1 = get_username(requests_state1)
+            with filelock.FileLock(auth_filename + '.lock'):
+                if os.path.isfile(auth_filename):
+                    with open(auth_filename, 'rt') as f:
+                        auth_dict = json.load(f)
+                    if username1 in auth_dict:
+                        auth_user = auth_dict[username1]
+                        if selection_docs_state1:
+                            update_auth_selection(auth_user, selection_docs_state1, save=True)
+                        if chat_state1:
+                            # overwrite
+                            auth_user['chat_state'] = chat_state1
+                        if text_output1:
+                            auth_user['text_output'] = text_output1
+                        if text_output21:
+                            auth_user['text_output2'] = text_output21
+                        if text_outputs1:
+                            auth_user['text_outputs'] = text_outputs1
+                        if langchain_mode1:
+                            auth_user['langchain_mode'] = langchain_mode1
+                        save_auth_dict(auth_dict, auth_filename)
+
+        def add_langchain_mode(db1s, selection_docs_state1, requests_state1, langchain_mode1, y,
+                               auth_filename=None, auth_freeze=None, guest_name=None):
+            assert auth_filename is not None
+            assert auth_freeze is not None
+
+            set_userid_gr(db1s, requests_state1, get_userid_auth)
+            username1 = get_username(requests_state1)
+            for k in db1s:
+                set_dbid_gr(db1s[k])
+            langchain_modes = selection_docs_state1['langchain_modes']
+            langchain_mode_paths = selection_docs_state1['langchain_mode_paths']
+            langchain_mode_types = selection_docs_state1['langchain_mode_types']
+
+            user_path = None
+            valid = True
+            y2 = y.strip().replace(' ', '').split(',')
+            if len(y2) >= 1:
+                langchain_mode2 = y2[0]
+                if len(langchain_mode2) >= 3 and langchain_mode2.isalnum():
+                    # real restriction is:
+                    # ValueError: Expected collection name that (1) contains 3-63 characters, (2) starts and ends with an alphanumeric character, (3) otherwise contains only alphanumeric characters, underscores or hyphens (-), (4) contains no two consecutive periods (..) and (5) is not a valid IPv4 address, got me
+                    # but just make simpler
+                    # assume personal if don't have user_path
+                    langchain_mode_type = y2[1] if len(y2) > 1 else LangChainTypes.PERSONAL.value
+                    user_path = y2[2] if len(y2) > 2 else None  # assume None if don't have user_path
+                    if user_path in ['', "''"]:
+                        # transcribe UI input
+                        user_path = None
+                    if langchain_mode_type not in [x.value for x in list(LangChainTypes)]:
+                        textbox = "Invalid type %s" % langchain_mode_type
+                        valid = False
+                        langchain_mode2 = langchain_mode1
+                    elif langchain_mode_type == LangChainTypes.SHARED.value and username1 == guest_name:
+                        textbox = "Guests cannot add shared collections"
+                        valid = False
+                        langchain_mode2 = langchain_mode1
+                    elif user_path is not None and langchain_mode_type == LangChainTypes.PERSONAL.value:
+                        textbox = "Do not pass user_path for personal/scratch types"
+                        valid = False
+                        langchain_mode2 = langchain_mode1
+                    elif user_path is not None and username1 == guest_name:
+                        textbox = "Guests cannot add collections with path"
+                        valid = False
+                        langchain_mode2 = langchain_mode1
+                    elif langchain_mode2 in langchain_modes_intrinsic:
+                        user_path = None
+                        textbox = "Invalid access to use internal name: %s" % langchain_mode2
+                        valid = False
+                        langchain_mode2 = langchain_mode1
+                    elif user_path and allow_upload_to_user_data or not user_path and allow_upload_to_my_data:
+                        if user_path:
+                            user_path = makedirs(user_path, exist_ok=True, use_base=True)
+                        langchain_mode_paths.update({langchain_mode2: user_path})
+                        langchain_mode_types.update({langchain_mode2: langchain_mode_type})
+                        if langchain_mode2 not in langchain_modes:
+                            langchain_modes.append(langchain_mode2)
+                        textbox = ''
+                    else:
+                        valid = False
+                        langchain_mode2 = langchain_mode1
+                        textbox = "Invalid access.  user allowed: %s " \
+                                  "personal/scratch allowed: %s" % (allow_upload_to_user_data, allow_upload_to_my_data)
+                else:
+                    valid = False
+                    langchain_mode2 = langchain_mode1
+                    textbox = "Invalid, collection must be >=3 characters and alphanumeric"
+            else:
+                valid = False
+                langchain_mode2 = langchain_mode1
+                textbox = "Invalid, must be like UserData2, user_path2"
+            selection_docs_state1 = update_langchain_mode_paths(selection_docs_state1)
+            df_langchain_mode_paths1 = get_df_langchain_mode_paths(selection_docs_state1, db1s, dbs1=dbs)
+            choices = get_langchain_choices(selection_docs_state1)
+
+            if valid and not user_path:
+                # needs to have key for it to make it known different from userdata case in _update_user_db()
+                from src.gpt_langchain import length_db1
+                db1s[langchain_mode2] = [None] * length_db1()
+            if valid:
+                save_auth(requests_state1, auth_filename, auth_freeze, selection_docs_state1=selection_docs_state1,
+                          langchain_mode1=langchain_mode2)
+
+            return db1s, selection_docs_state1, gr.update(choices=choices,
+                                                          value=langchain_mode2), textbox, df_langchain_mode_paths1
+
+        def remove_langchain_mode(db1s, selection_docs_state1, requests_state1,
+                                  langchain_mode1, langchain_mode2, dbsu=None, auth_filename=None, auth_freeze=None,
+                                  guest_name=None,
+                                  purge=False):
+            assert auth_filename is not None
+            assert auth_freeze is not None
+
+            set_userid_gr(db1s, requests_state1, get_userid_auth)
+            for k in db1s:
+                set_dbid_gr(db1s[k])
+            assert dbsu is not None
+            langchain_modes = selection_docs_state1['langchain_modes']
+            langchain_mode_paths = selection_docs_state1['langchain_mode_paths']
+            langchain_mode_types = selection_docs_state1['langchain_mode_types']
+            langchain_type2 = langchain_mode_types.get(langchain_mode2, LangChainTypes.EITHER.value)
+
+            changed_state = False
+            textbox = "Invalid access, cannot remove %s" % langchain_mode2
+            in_scratch_db = langchain_mode2 in db1s
+            in_user_db = dbsu is not None and langchain_mode2 in dbsu
+            if in_scratch_db and not allow_upload_to_my_data or \
+                    in_user_db and not allow_upload_to_user_data or \
+                    langchain_mode2 in langchain_modes_intrinsic:
+                can_remove = False
+                can_purge = False
+                if langchain_mode2 in langchain_modes_intrinsic:
+                    can_purge = True
+            else:
+                can_remove = True
+                can_purge = True
+
+            # change global variables
+            if langchain_mode2 in langchain_modes or langchain_mode2 in langchain_mode_paths or langchain_mode2 in db1s:
+                if can_purge and purge:
+                    # remove source files
+                    from src.gpt_langchain import get_sources, del_from_db
+                    sources_file, source_list, num_chunks, db = \
+                        get_sources(db1s, selection_docs_state1,
+                                    requests_state1, langchain_mode2, dbs=dbsu,
+                                    docs_state0=docs_state0,
+                                    load_db_if_exists=load_db_if_exists,
+                                    db_type=db_type,
+                                    use_openai_embedding=use_openai_embedding,
+                                    hf_embedding_model=hf_embedding_model,
+                                    migrate_embedding_model=migrate_embedding_model,
+                                    auto_migrate_db=auto_migrate_db,
+                                    verbose=verbose,
+                                    get_userid_auth=get_userid_auth,
+                                    n_jobs=n_jobs)
+                    del_from_db(db, source_list, db_type=db_type)
+                    for fil in source_list:
+                        if os.path.isfile(fil):
+                            print("Purged %s" % fil, flush=True)
+                            remove(fil)
+                    # remove db directory
+                    from src.gpt_langchain import get_persist_directory
+                    persist_directory, langchain_type2 = \
+                        get_persist_directory(langchain_mode2, langchain_type=langchain_type2,
+                                              db1s=db1s, dbs=dbsu)
+                    print("removed persist_directory %s" % persist_directory, flush=True)
+                    remove(persist_directory)
+                    textbox = "Purged, but did not remove %s" % langchain_mode2
+                if can_remove:
+                    if langchain_mode2 in langchain_modes:
+                        langchain_modes.remove(langchain_mode2)
+                    if langchain_mode2 in langchain_mode_paths:
+                        langchain_mode_paths.pop(langchain_mode2)
+                    if langchain_mode2 in langchain_mode_types:
+                        langchain_mode_types.pop(langchain_mode2)
+                    if langchain_mode2 in db1s and langchain_mode2 != LangChainMode.MY_DATA.value:
+                        # don't remove last MyData, used as user hash
+                        db1s.pop(langchain_mode2)
+                    textbox = ""
+                    changed_state = True
+            else:
+                textbox = "%s is not visible" % langchain_mode2
+
+            # update
+            selection_docs_state1 = update_langchain_mode_paths(selection_docs_state1)
+            df_langchain_mode_paths1 = get_df_langchain_mode_paths(selection_docs_state1, db1s, dbs1=dbs)
+
+            if changed_state:
+                save_auth(requests_state1, auth_filename, auth_freeze, selection_docs_state1=selection_docs_state1,
+                          langchain_mode1=langchain_mode2)
+
+            return db1s, selection_docs_state1, \
+                gr.update(choices=get_langchain_choices(selection_docs_state1),
+                          value=langchain_mode2), textbox, df_langchain_mode_paths1
+
+        eventdb20a = new_langchain_mode_text.submit(user_state_setup,
+                                                    inputs=[my_db_state, requests_state,
+                                                            new_langchain_mode_text, new_langchain_mode_text],
+                                                    outputs=[my_db_state, requests_state, new_langchain_mode_text],
+                                                    show_progress='minimal')
+        add_langchain_mode_func = functools.partial(add_langchain_mode,
+                                                    auth_filename=kwargs['auth_filename'],
+                                                    auth_freeze=kwargs['auth_freeze'],
+                                                    guest_name=kwargs['guest_name'],
+                                                    )
+        eventdb20b = eventdb20a.then(fn=add_langchain_mode_func,
+                                     inputs=[my_db_state, selection_docs_state, requests_state,
+                                             langchain_mode,
+                                             new_langchain_mode_text],
+                                     outputs=[my_db_state, selection_docs_state, langchain_mode,
+                                              new_langchain_mode_text,
+                                              langchain_mode_path_text],
+                                     api_name='new_langchain_mode_text' if allow_api and allow_upload_to_user_data else None)
+        db_events.extend([eventdb20a, eventdb20b])
+
+        remove_langchain_mode_func = functools.partial(remove_langchain_mode,
+                                                       dbsu=dbs,
+                                                       auth_filename=kwargs['auth_filename'],
+                                                       auth_freeze=kwargs['auth_freeze'],
+                                                       guest_name=kwargs['guest_name'],
+                                                       )
+        eventdb21a = remove_langchain_mode_text.submit(user_state_setup,
+                                                       inputs=[my_db_state,
+                                                               requests_state,
+                                                               remove_langchain_mode_text, remove_langchain_mode_text],
+                                                       outputs=[my_db_state,
+                                                                requests_state, remove_langchain_mode_text],
+                                                       show_progress='minimal')
+        remove_langchain_mode_kwargs = dict(fn=remove_langchain_mode_func,
+                                            inputs=[my_db_state, selection_docs_state, requests_state,
+                                                    langchain_mode,
+                                                    remove_langchain_mode_text],
+                                            outputs=[my_db_state, selection_docs_state, langchain_mode,
+                                                     remove_langchain_mode_text,
+                                                     langchain_mode_path_text])
+        eventdb21b = eventdb21a.then(**remove_langchain_mode_kwargs,
+                                     api_name='remove_langchain_mode_text' if allow_api and allow_upload_to_user_data else None)
+        db_events.extend([eventdb21a, eventdb21b])
+
+        eventdb22a = purge_langchain_mode_text.submit(user_state_setup,
+                                                      inputs=[my_db_state,
+                                                              requests_state,
+                                                              purge_langchain_mode_text, purge_langchain_mode_text],
+                                                      outputs=[my_db_state,
+                                                               requests_state, purge_langchain_mode_text],
+                                                      show_progress='minimal')
+        purge_langchain_mode_func = functools.partial(remove_langchain_mode_func, purge=True)
+        purge_langchain_mode_kwargs = dict(fn=purge_langchain_mode_func,
+                                           inputs=[my_db_state, selection_docs_state, requests_state,
+                                                   langchain_mode,
+                                                   purge_langchain_mode_text],
+                                           outputs=[my_db_state, selection_docs_state, langchain_mode,
+                                                    purge_langchain_mode_text,
+                                                    langchain_mode_path_text])
+        # purge_langchain_mode_kwargs = remove_langchain_mode_kwargs.copy()
+        # purge_langchain_mode_kwargs['fn'] = functools.partial(remove_langchain_mode_kwargs['fn'], purge=True)
+        eventdb22b = eventdb22a.then(**purge_langchain_mode_kwargs,
+                                     api_name='purge_langchain_mode_text' if allow_api and allow_upload_to_user_data else None)
+        db_events.extend([eventdb22a, eventdb22b])
+
+        def load_langchain_gr(db1s, selection_docs_state1, requests_state1, langchain_mode1, auth_filename=None):
+            load_auth(db1s, requests_state1, auth_filename, selection_docs_state1=selection_docs_state1)
+
+            selection_docs_state1 = update_langchain_mode_paths(selection_docs_state1)
+            df_langchain_mode_paths1 = get_df_langchain_mode_paths(selection_docs_state1, db1s, dbs1=dbs)
+            return selection_docs_state1, \
+                gr.update(choices=get_langchain_choices(selection_docs_state1),
+                          value=langchain_mode1), df_langchain_mode_paths1
+
+        eventdbloadla = load_langchain.click(user_state_setup,
+                                             inputs=[my_db_state, requests_state, langchain_mode],
+                                             outputs=[my_db_state, requests_state, langchain_mode],
+                                             show_progress='minimal')
+        load_langchain_gr_func = functools.partial(load_langchain_gr,
+                                                   auth_filename=kwargs['auth_filename'])
+        eventdbloadlb = eventdbloadla.then(fn=load_langchain_gr_func,
+                                           inputs=[my_db_state, selection_docs_state, requests_state, langchain_mode],
+                                           outputs=[selection_docs_state, langchain_mode, langchain_mode_path_text],
+                                           api_name='load_langchain' if allow_api and allow_upload_to_user_data else None)
+
+        if not kwargs['large_file_count_mode']:
+            # FIXME: Could add all these functions, inputs, outputs into single function for snappier GUI
+            # all update events when not doing large file count mode
+            # Note: Login touches langchain_mode, which triggers all these
+            lg_change_event2 = lg_change_event.then(**get_sources_kwargs)
+            lg_change_event3 = lg_change_event2.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            lg_change_event4 = lg_change_event3.then(**show_sources_kwargs)
+            lg_change_event5 = lg_change_event4.then(**get_viewable_sources_args)
+            lg_change_event6 = lg_change_event5.then(**viewable_kwargs)
+
+            eventdb2c = eventdb2.then(**get_sources_kwargs)
+            eventdb2d = eventdb2c.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            eventdb2e = eventdb2d.then(**show_sources_kwargs)
+            eventdb2f = eventdb2e.then(**get_viewable_sources_args)
+            eventdb2g = eventdb2f.then(**viewable_kwargs)
+
+            eventdb1c = eventdb1.then(**get_sources_kwargs)
+            eventdb1d = eventdb1c.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            eventdb1e = eventdb1d.then(**show_sources_kwargs)
+            eventdb1f = eventdb1e.then(**get_viewable_sources_args)
+            eventdb1g = eventdb1f.then(**viewable_kwargs)
+
+            eventdb3c = eventdb3.then(**get_sources_kwargs)
+            eventdb3d = eventdb3c.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            eventdb3e = eventdb3d.then(**show_sources_kwargs)
+            eventdb3f = eventdb3e.then(**get_viewable_sources_args)
+            eventdb3g = eventdb3f.then(**viewable_kwargs)
+
+            eventdb90ua = eventdb90.then(**get_sources_kwargs)
+            eventdb90ub = eventdb90ua.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            eventdb90uc = eventdb90ub.then(**show_sources_kwargs)
+            eventdb90ud = eventdb90uc.then(**get_viewable_sources_args)
+            eventdb90ue = eventdb90ud.then(**viewable_kwargs)
+
+            eventdb20c = eventdb20b.then(**get_sources_kwargs)
+            eventdb20d = eventdb20c.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            eventdb20e = eventdb20d.then(**show_sources_kwargs)
+            eventdb20f = eventdb20e.then(**get_viewable_sources_args)
+            eventdb20g = eventdb20f.then(**viewable_kwargs)
+
+            eventdb21c = eventdb21b.then(**get_sources_kwargs)
+            eventdb21d = eventdb21c.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            eventdb21e = eventdb21d.then(**show_sources_kwargs)
+            eventdb21f = eventdb21e.then(**get_viewable_sources_args)
+            eventdb21g = eventdb21f.then(**viewable_kwargs)
+
+            eventdb22c = eventdb22b.then(**get_sources_kwargs)
+            eventdb22d = eventdb22c.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            eventdb22e = eventdb22d.then(**show_sources_kwargs)
+            eventdb22f = eventdb22e.then(**get_viewable_sources_args)
+            eventdb22g = eventdb22f.then(**viewable_kwargs)
+
+            event_attach3 = event_attach2.then(**get_sources_kwargs)
+            event_attach4 = event_attach3.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            event_attach5 = event_attach4.then(**show_sources_kwargs)
+            event_attach6 = event_attach5.then(**get_viewable_sources_args)
+            event_attach7 = event_attach6.then(**viewable_kwargs)
+
+            sync2 = sync1.then(**get_sources_kwargs)
+            sync3 = sync2.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            sync4 = sync3.then(**show_sources_kwargs)
+            sync5 = sync4.then(**get_viewable_sources_args)
+            sync6 = sync5.then(**viewable_kwargs)
+
+            eventdb_loginb = eventdb_logina.then(**get_sources_kwargs)
+            eventdb_loginc = eventdb_loginb.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+            eventdb_logind = eventdb_loginc.then(**show_sources_kwargs)
+            eventdb_logine = eventdb_logind.then(**get_viewable_sources_args)
+            eventdb_loginf = eventdb_logine.then(**viewable_kwargs)
+
+            db_events.extend([lg_change_event, lg_change_event2, lg_change_event3, lg_change_event4, lg_change_event5,
+                              lg_change_event6] +
+                             [eventdb2c, eventdb2d, eventdb2e, eventdb2f, eventdb2g] +
+                             [eventdb1c, eventdb1d, eventdb1e, eventdb1f, eventdb1g] +
+                             [eventdb3c, eventdb3d, eventdb3e, eventdb3f, eventdb3g] +
+                             [eventdb90ua, eventdb90ub, eventdb90uc, eventdb90ud, eventdb90ue] +
+                             [eventdb20c, eventdb20d, eventdb20e, eventdb20f, eventdb20g] +
+                             [eventdb21c, eventdb21d, eventdb21e, eventdb21f, eventdb21g] +
+                             [eventdb22c, eventdb22d, eventdb22e, eventdb22f, eventdb22g] +
+                             [event_attach3, event_attach4, event_attach5, event_attach6, event_attach7] +
+                             [sync1, sync2, sync3, sync4, sync5, sync6] +
+                             [eventdb_logina, eventdb_loginb, eventdb_loginc, eventdb_logind, eventdb_logine,
+                              eventdb_loginf]
+                             ,
+                             )
+
+        inputs_list, inputs_dict = get_inputs_list(all_kwargs, kwargs['model_lower'], model_id=1)
+        inputs_list2, inputs_dict2 = get_inputs_list(all_kwargs, kwargs['model_lower'], model_id=2)
+        from functools import partial
+        kwargs_evaluate = {k: v for k, v in all_kwargs.items() if k in inputs_kwargs_list}
+        # ensure present
+        for k in inputs_kwargs_list:
+            assert k in kwargs_evaluate, "Missing %s" % k
+
+        def evaluate_nochat(*args1, default_kwargs1=None, str_api=False, plain_api=False, **kwargs1):
+            args_list = list(args1)
+            if str_api:
+                if plain_api:
+                    # i.e. not fresh model, tells evaluate to use model_state0
+                    args_list.insert(0, kwargs['model_state_none'].copy())
+                    args_list.insert(1, my_db_state0.copy())
+                    args_list.insert(2, selection_docs_state0.copy())
+                    args_list.insert(3, requests_state0.copy())
+                user_kwargs = args_list[len(input_args_list)]
+                assert isinstance(user_kwargs, str)
+                user_kwargs = ast.literal_eval(user_kwargs)
+            else:
+                assert not plain_api
+                user_kwargs = {k: v for k, v in zip(eval_func_param_names, args_list[len(input_args_list):])}
+            # control kwargs1 for evaluate
+            kwargs1['answer_with_sources'] = -1  # just text chunk, not URL etc.
+            kwargs1['show_accordions'] = False
+            kwargs1['append_sources_to_answer'] = False
+            kwargs1['show_link_in_sources'] = False
+            kwargs1['top_k_docs_max_show'] = 30
+
+            # only used for submit_nochat_api
+            user_kwargs['chat'] = False
+            if 'stream_output' not in user_kwargs:
+                user_kwargs['stream_output'] = False
+            if plain_api:
+                user_kwargs['stream_output'] = False
+            if 'langchain_mode' not in user_kwargs:
+                # if user doesn't specify, then assume disabled, not use default
+                if LangChainMode.LLM.value in kwargs['langchain_modes']:
+                    user_kwargs['langchain_mode'] = LangChainMode.LLM.value
+                elif len(kwargs['langchain_modes']) >= 1:
+                    user_kwargs['langchain_mode'] = kwargs['langchain_modes'][0]
+                else:
+                    # disabled should always be allowed
+                    user_kwargs['langchain_mode'] = LangChainMode.DISABLED.value
+            if 'langchain_action' not in user_kwargs:
+                user_kwargs['langchain_action'] = LangChainAction.QUERY.value
+            if 'langchain_agents' not in user_kwargs:
+                user_kwargs['langchain_agents'] = []
+            # be flexible
+            if 'instruction' in user_kwargs and 'instruction_nochat' not in user_kwargs:
+                user_kwargs['instruction_nochat'] = user_kwargs['instruction']
+            if 'iinput' in user_kwargs and 'iinput_nochat' not in user_kwargs:
+                user_kwargs['iinput_nochat'] = user_kwargs['iinput']
+            if 'visible_models' not in user_kwargs:
+                if kwargs['visible_models']:
+                    if isinstance(kwargs['visible_models'], int):
+                        user_kwargs['visible_models'] = [kwargs['visible_models']]
+                    elif isinstance(kwargs['visible_models'], list):
+                        # only take first one
+                        user_kwargs['visible_models'] = [kwargs['visible_models'][0]]
+                    else:
+                        user_kwargs['visible_models'] = [0]
+                else:
+                    # if no user version or default version, then just take first
+                    user_kwargs['visible_models'] = [0]
+
+            if 'h2ogpt_key' not in user_kwargs:
+                user_kwargs['h2ogpt_key'] = None
+            if 'system_prompt' in user_kwargs and user_kwargs['system_prompt'] is None:
+                # avoid worrying about below default_kwargs -> args_list that checks if None
+                user_kwargs['system_prompt'] = 'None'
+
+            set1 = set(list(default_kwargs1.keys()))
+            set2 = set(eval_func_param_names)
+            assert set1 == set2, "Set diff: %s %s: %s" % (set1, set2, set1.symmetric_difference(set2))
+            # correct ordering.  Note some things may not be in default_kwargs, so can't be default of user_kwargs.get()
+            model_state1 = args_list[0]
+            my_db_state1 = args_list[1]
+            selection_docs_state1 = args_list[2]
+            requests_state1 = args_list[3]
+            args_list = [user_kwargs[k] if k in user_kwargs and user_kwargs[k] is not None else default_kwargs1[k] for k
+                         in eval_func_param_names]
+            assert len(args_list) == len(eval_func_param_names)
+            stream_output1 = args_list[eval_func_param_names.index('stream_output')]
+            if len(model_states) > 1:
+                visible_models1 = args_list[eval_func_param_names.index('visible_models')]
+                model_active_choice1 = visible_models_to_model_choice(visible_models1)
+                model_state1 = model_states[model_active_choice1 % len(model_states)]
+                for key in key_overrides:
+                    if user_kwargs.get(key) is None and model_state1.get(key) is not None:
+                        args_list[eval_func_param_names.index(key)] = model_state1[key]
+                if hasattr(model_state1['tokenizer'], 'model_max_length'):
+                    # ensure listen to limit, with some buffer
+                    # buffer = 50
+                    buffer = 0
+                    args_list[eval_func_param_names.index('max_new_tokens')] = min(
+                        args_list[eval_func_param_names.index('max_new_tokens')],
+                        model_state1['tokenizer'].model_max_length - buffer)
+
+            # override overall visible_models and h2ogpt_key if have model_specific one
+            # NOTE: only applicable if len(model_states) > 1 at moment
+            # else controlled by evaluate()
+            if 'visible_models' in model_state1 and model_state1['visible_models'] is not None:
+                assert isinstance(model_state1['visible_models'], int)
+                args_list[eval_func_param_names.index('visible_models')] = model_state1['visible_models']
+            if 'h2ogpt_key' in model_state1 and model_state1['h2ogpt_key'] is not None:
+                # remote server key if present
+                # i.e. may be '' and used to override overall local key
+                assert isinstance(model_state1['h2ogpt_key'], str)
+                args_list[eval_func_param_names.index('h2ogpt_key')] = model_state1['h2ogpt_key']
+
+            # local key, not for remote server unless same, will be passed through
+            h2ogpt_key1 = args_list[eval_func_param_names.index('h2ogpt_key')]
+
+            # final full evaluate args list
+            args_list = [model_state1, my_db_state1, selection_docs_state1, requests_state1] + args_list
+
+            # NOTE: Don't allow UI-like access, in case modify state via API
+            valid_key = is_valid_key(kwargs['enforce_h2ogpt_api_key'], kwargs['h2ogpt_api_keys'], h2ogpt_key1,
+                                     requests_state1=None)
+            evaluate_local = evaluate if valid_key else evaluate_fake
+
+            save_dict = dict()
+            ret = {}
+            try:
+                for res_dict in evaluate_local(*tuple(args_list), **kwargs1):
+                    error = res_dict.get('error', '')
+                    extra = res_dict.get('extra', '')
+                    save_dict = res_dict.get('save_dict', {})
+
+                    # update save_dict
+                    save_dict['error'] = error
+                    save_dict['extra'] = extra
+                    save_dict['valid_key'] = valid_key
+                    save_dict['h2ogpt_key'] = h2ogpt_key1
+                    if str_api and plain_api:
+                        save_dict['which_api'] = 'str_plain_api'
+                    elif str_api:
+                        save_dict['which_api'] = 'str_api'
+                    elif plain_api:
+                        save_dict['which_api'] = 'plain_api'
+                    else:
+                        save_dict['which_api'] = 'nochat_api'
+                    if 'extra_dict' not in save_dict:
+                        save_dict['extra_dict'] = {}
+                    if requests_state1:
+                        save_dict['extra_dict'].update(requests_state1)
+                    else:
+                        save_dict['extra_dict'].update(dict(username='NO_REQUEST'))
+
+                    if is_public:
+                        # don't want to share actual endpoints
+                        if 'save_dict' in res_dict and isinstance(res_dict['save_dict'], dict):
+                            res_dict['save_dict'].pop('inference_server', None)
+                            if 'extra_dict' in res_dict['save_dict'] and isinstance(res_dict['save_dict']['extra_dict'],
+                                                                                    dict):
+                                res_dict['save_dict']['extra_dict'].pop('inference_server', None)
+
+                    # get response
+                    if str_api:
+                        # full return of dict
+                        ret = res_dict
+                    elif kwargs['langchain_mode'] == 'Disabled':
+                        ret = fix_text_for_gradio(res_dict['response'])
+                    else:
+                        ret = '<br>' + fix_text_for_gradio(res_dict['response'])
+                    if stream_output1:
+                        # yield as it goes, else need to wait since predict only returns first yield
+                        yield ret
+            finally:
+                clear_torch_cache()
+                clear_embeddings(user_kwargs['langchain_mode'], my_db_state1)
+            save_generate_output(**save_dict)
+            if not stream_output1:
+                # return back last ret
+                yield ret
+
+        kwargs_evaluate_nochat = kwargs_evaluate.copy()
+        # nominally never want sources appended for API calls, which is what nochat used for primarily
+        kwargs_evaluate_nochat.update(dict(append_sources_to_answer=False))
+        fun = partial(evaluate_nochat,
+                      default_kwargs1=default_kwargs,
+                      str_api=False,
+                      **kwargs_evaluate_nochat)
+        fun_with_dict_str = partial(evaluate_nochat,
+                                    default_kwargs1=default_kwargs,
+                                    str_api=True,
+                                    **kwargs_evaluate_nochat
+                                    )
+
+        fun_with_dict_str_plain = partial(evaluate_nochat,
+                                          default_kwargs1=default_kwargs,
+                                          str_api=True,
+                                          plain_api=True,
+                                          **kwargs_evaluate_nochat
+                                          )
+
+        dark_mode_btn.click(
+            None,
+            None,
+            None,
+            _js=wrap_js_to_lambda(0, get_dark_js()),
+            api_name="dark" if allow_api else None,
+            queue=False,
+        )
+
+        # Handle uploads from API
+        upload_api_btn = gr.UploadButton("Upload File Results", visible=False)
+        file_upload_api = gr.File(visible=False)
+        file_upload_text = gr.Textbox(visible=False)
+
+        def upload_file(files):
+            if isinstance(files, list):
+                file_paths = [file.name for file in files]
+            else:
+                file_paths = files.name
+            return file_paths, file_paths
+
+        upload_api_btn.upload(fn=upload_file,
+                              inputs=upload_api_btn,
+                              outputs=[file_upload_api, file_upload_text],
+                              api_name='upload_api' if allow_upload_api else None)
+
+        def visible_toggle(x):
+            x = 'off' if x == 'on' else 'on'
+            return x, gr.Column.update(visible=True if x == 'on' else False)
+
+        side_bar_btn.click(fn=visible_toggle,
+                           inputs=side_bar_text,
+                           outputs=[side_bar_text, side_bar],
+                           queue=False)
+
+        doc_count_btn.click(fn=visible_toggle,
+                            inputs=doc_count_text,
+                            outputs=[doc_count_text, row_doc_track],
+                            queue=False)
+
+        submit_buttons_btn.click(fn=visible_toggle,
+                                 inputs=submit_buttons_text,
+                                 outputs=[submit_buttons_text, submit_buttons],
+                                 queue=False)
+
+        visible_model_btn.click(fn=visible_toggle,
+                                inputs=visible_models_text,
+                                outputs=[visible_models_text, visible_models],
+                                queue=False)
+
+        # examples after submit or any other buttons for chat or no chat
+        if kwargs['examples'] is not None and kwargs['show_examples']:
+            gr.Examples(examples=kwargs['examples'], inputs=inputs_list)
+
+        # Score
+        def score_last_response(*args, nochat=False, num_model_lock=0):
+            try:
+                if num_model_lock > 0:
+                    # then lock way
+                    args_list = list(args).copy()
+                    outputs = args_list[-num_model_lock:]
+                    score_texts1 = []
+                    for output in outputs:
+                        # same input, put into form good for _score_last_response()
+                        args_list[-1] = output
+                        score_texts1.append(
+                            _score_last_response(*tuple(args_list), nochat=nochat,
+                                                 num_model_lock=num_model_lock, prefix=''))
+                    if len(score_texts1) > 1:
+                        return "Response Scores: %s" % ' '.join(score_texts1)
+                    else:
+                        return "Response Scores: %s" % score_texts1[0]
+                else:
+                    return _score_last_response(*args, nochat=nochat, num_model_lock=num_model_lock)
+            finally:
+                clear_torch_cache()
+
+        def _score_last_response(*args, nochat=False, num_model_lock=0, prefix='Response Score: '):
+            """ Similar to user() """
+            args_list = list(args)
+            smodel = score_model_state0['model']
+            stokenizer = score_model_state0['tokenizer']
+            sdevice = score_model_state0['device']
+
+            if memory_restriction_level > 0:
+                max_length_tokenize = 768 - 256 if memory_restriction_level <= 2 else 512 - 256
+            elif hasattr(stokenizer, 'model_max_length'):
+                max_length_tokenize = stokenizer.model_max_length
+            else:
+                # limit to 1024, not worth OOMing on reward score
+                max_length_tokenize = 2048 - 1024
+            cutoff_len = max_length_tokenize * 4  # restrict deberta related to max for LLM
+
+            if not nochat:
+                history = args_list[-1]
+                if history is None:
+                    history = []
+                if smodel is not None and \
+                        stokenizer is not None and \
+                        sdevice is not None and \
+                        history is not None and len(history) > 0 and \
+                        history[-1] is not None and \
+                        len(history[-1]) >= 2:
+                    os.environ['TOKENIZERS_PARALLELISM'] = 'false'
+
+                    question = history[-1][0]
+
+                    answer = history[-1][1]
+                else:
+                    return '%sNA' % prefix
+            else:
+                answer = args_list[-1]
+                instruction_nochat_arg_id = eval_func_param_names.index('instruction_nochat')
+                question = args_list[instruction_nochat_arg_id]
+
+            if question is None:
+                return '%sBad Question' % prefix
+            if answer is None:
+                return '%sBad Answer' % prefix
+            try:
+                score = score_qa(smodel, stokenizer, max_length_tokenize, question, answer, cutoff_len)
+            finally:
+                clear_torch_cache()
+            if isinstance(score, str):
+                return '%sNA' % prefix
+            return '{}{:.1%}'.format(prefix, score)
+
+        def noop_score_last_response(*args, **kwargs):
+            return "Response Score: Disabled"
+
+        if kwargs['score_model']:
+            score_fun = score_last_response
+        else:
+            score_fun = noop_score_last_response
+
+        score_args = dict(fn=score_fun,
+                          inputs=inputs_list + [text_output],
+                          outputs=[score_text],
+                          )
+        score_args2 = dict(fn=partial(score_fun),
+                           inputs=inputs_list2 + [text_output2],
+                           outputs=[score_text2],
+                           )
+        score_fun_func = functools.partial(score_fun, num_model_lock=len(text_outputs))
+        all_score_args = dict(fn=score_fun_func,
+                              inputs=inputs_list + text_outputs,
+                              outputs=score_text,
+                              )
+
+        score_args_nochat = dict(fn=partial(score_fun, nochat=True),
+                                 inputs=inputs_list + [text_output_nochat],
+                                 outputs=[score_text_nochat],
+                                 )
+
+        def update_history(*args, undo=False, retry=False, sanitize_user_prompt=False):
+            """
+            User that fills history for bot
+            :param args:
+            :param undo:
+            :param retry:
+            :param sanitize_user_prompt:
+            :return:
+            """
+            args_list = list(args)
+            user_message = args_list[eval_func_param_names.index('instruction')]  # chat only
+            input1 = args_list[eval_func_param_names.index('iinput')]  # chat only
+            prompt_type1 = args_list[eval_func_param_names.index('prompt_type')]
+            langchain_mode1 = args_list[eval_func_param_names.index('langchain_mode')]
+            langchain_action1 = args_list[eval_func_param_names.index('langchain_action')]
+            langchain_agents1 = args_list[eval_func_param_names.index('langchain_agents')]
+            document_subset1 = args_list[eval_func_param_names.index('document_subset')]
+            document_choice1 = args_list[eval_func_param_names.index('document_choice')]
+            if not prompt_type1:
+                # shouldn't have to specify if CLI launched model
+                prompt_type1 = kwargs['prompt_type']
+                # apply back
+                args_list[eval_func_param_names.index('prompt_type')] = prompt_type1
+            if input1 and not user_message.endswith(':'):
+                user_message1 = user_message + ":" + input1
+            elif input1:
+                user_message1 = user_message + input1
+            else:
+                user_message1 = user_message
+            if sanitize_user_prompt:
+                pass
+                # requirements.txt has comment that need to re-enable the below 2 lines
+                # from better_profanity import profanity
+                # user_message1 = profanity.censor(user_message1)
+
+            history = args_list[-1]
+            if history is None:
+                # bad history
+                history = []
+            history = history.copy()
+
+            if undo:
+                if len(history) > 0:
+                    history.pop()
+                return history
+            if retry:
+                if history:
+                    history[-1][1] = None
+                return history
+            if user_message1 in ['', None, '\n']:
+                if not allow_empty_instruction(langchain_mode1, document_subset1, langchain_action1):
+                    # reject non-retry submit/enter
+                    return history
+            user_message1 = fix_text_for_gradio(user_message1)
+            return history + [[user_message1, None]]
+
+        def user(*args, undo=False, retry=False, sanitize_user_prompt=False):
+            return update_history(*args, undo=undo, retry=retry, sanitize_user_prompt=sanitize_user_prompt)
+
+        def all_user(*args, undo=False, retry=False, sanitize_user_prompt=False, num_model_lock=0, all_models=None):
+            args_list = list(args)
+
+            visible_models1 = args_list[eval_func_param_names.index('visible_models')]
+            assert isinstance(all_models, list)
+            visible_list = get_model_lock_visible_list(visible_models1, all_models)
+
+            history_list = args_list[-num_model_lock:]
+            assert len(all_models) == len(history_list)
+            assert len(history_list) > 0, "Bad history list: %s" % history_list
+            for hi, history in enumerate(history_list):
+                if not visible_list[hi]:
+                    continue
+                if num_model_lock > 0:
+                    hargs = args_list[:-num_model_lock].copy()
+                else:
+                    hargs = args_list.copy()
+                hargs += [history]
+                history_list[hi] = update_history(*hargs, undo=undo, retry=retry,
+                                                  sanitize_user_prompt=sanitize_user_prompt)
+            if len(history_list) > 1:
+                return tuple(history_list)
+            else:
+                return history_list[0]
+
+        def get_model_max_length(model_state1):
+            if model_state1 and not isinstance(model_state1["tokenizer"], str):
+                tokenizer = model_state1["tokenizer"]
+            elif model_state0 and not isinstance(model_state0["tokenizer"], str):
+                tokenizer = model_state0["tokenizer"]
+            else:
+                tokenizer = None
+            if tokenizer is not None:
+                return tokenizer.model_max_length
+            else:
+                return 2000
+
+        def prep_bot(*args, retry=False, which_model=0):
+            """
+
+            :param args:
+            :param retry:
+            :param which_model: identifies which model if doing model_lock
+                 API only called for which_model=0, default for inputs_list, but rest should ignore inputs_list
+            :return: last element is True if should run bot, False if should just yield history
+            """
+            isize = len(input_args_list) + 1  # states + chat history
+            # don't deepcopy, can contain model itself
+            args_list = list(args).copy()
+            model_state1 = args_list[-isize]
+            my_db_state1 = args_list[-isize + 1]
+            selection_docs_state1 = args_list[-isize + 2]
+            requests_state1 = args_list[-isize + 3]
+            history = args_list[-1]
+            if not history:
+                history = []
+            prompt_type1 = args_list[eval_func_param_names.index('prompt_type')]
+            prompt_dict1 = args_list[eval_func_param_names.index('prompt_dict')]
+            langchain_mode1 = args_list[eval_func_param_names.index('langchain_mode')]
+            langchain_action1 = args_list[eval_func_param_names.index('langchain_action')]
+            document_subset1 = args_list[eval_func_param_names.index('document_subset')]
+            h2ogpt_key1 = args_list[eval_func_param_names.index('h2ogpt_key')]
+            chat_conversation1 = args_list[eval_func_param_names.index('chat_conversation')]
+            valid_key = is_valid_key(kwargs['enforce_h2ogpt_api_key'], kwargs['h2ogpt_api_keys'], h2ogpt_key1,
+                                     requests_state1=requests_state1)
+
+            dummy_return = history, None, langchain_mode1, my_db_state1, requests_state1, valid_key, h2ogpt_key1
+
+            if model_state1['model'] is None or model_state1['model'] == no_model_str:
+                return dummy_return
+
+            args_list = args_list[:-isize]  # only keep rest needed for evaluate()
+            if not history:
+                print("No history", flush=True)
+                return dummy_return
+            instruction1 = history[-1][0]
+            if retry and history:
+                # if retry, pop history and move onto bot stuff
+                instruction1 = history[-1][0]
+                history[-1][1] = None
+            elif not instruction1:
+                if not allow_empty_instruction(langchain_mode1, document_subset1, langchain_action1):
+                    # if not retrying, then reject empty query
+                    return dummy_return
+            elif len(history) > 0 and history[-1][1] not in [None, '']:
+                # reject submit button if already filled and not retrying
+                # None when not filling with '' to keep client happy
+                return dummy_return
+
+            evaluate_local = evaluate if valid_key else evaluate_fake
+
+            # shouldn't have to specify in API prompt_type if CLI launched model, so prefer global CLI one if have it
+            prompt_type1, prompt_dict1 = update_prompt(prompt_type1, prompt_dict1, model_state1,
+                                                       which_model=which_model)
+            # apply back to args_list for evaluate()
+            args_list[eval_func_param_names.index('prompt_type')] = prompt_type1
+            args_list[eval_func_param_names.index('prompt_dict')] = prompt_dict1
+            context1 = args_list[eval_func_param_names.index('context')]
+
+            chat_conversation1 = merge_chat_conversation_history(chat_conversation1, history)
+            args_list[eval_func_param_names.index('chat_conversation')] = chat_conversation1
+
+            if 'visible_models' in model_state1 and model_state1['visible_models'] is not None:
+                assert isinstance(model_state1['visible_models'], int)
+                args_list[eval_func_param_names.index('visible_models')] = model_state1['visible_models']
+            if 'h2ogpt_key' in model_state1 and model_state1['h2ogpt_key'] is not None:
+                # i.e. may be '' and used to override overall local key
+                assert isinstance(model_state1['h2ogpt_key'], str)
+                args_list[eval_func_param_names.index('h2ogpt_key')] = model_state1['h2ogpt_key']
+
+            args_list[0] = instruction1  # override original instruction with history from user
+            args_list[2] = context1
+
+            fun1 = partial(evaluate_local,
+                           model_state1,
+                           my_db_state1,
+                           selection_docs_state1,
+                           requests_state1,
+                           *tuple(args_list),
+                           **kwargs_evaluate)
+
+            return history, fun1, langchain_mode1, my_db_state1, requests_state1, valid_key, h2ogpt_key1
+
+        def gen1_fake(fun1, history):
+            error = ''
+            extra = ''
+            save_dict = dict()
+            yield history, error, extra, save_dict
+            return
+
+        def get_response(fun1, history):
+            """
+            bot that consumes history for user input
+            instruction (from input_list) itself is not consumed by bot
+            :return:
+            """
+            error = ''
+            extra = ''
+            save_dict = dict()
+            if not fun1:
+                yield history, error, extra, save_dict
+                return
+            try:
+                for output_fun in fun1():
+                    output = output_fun['response']
+                    extra = output_fun['sources']  # FIXME: can show sources in separate text box etc.
+                    save_dict = output_fun.get('save_dict', {})
+                    # ensure good visually, else markdown ignores multiple \n
+                    bot_message = fix_text_for_gradio(output)
+                    history[-1][1] = bot_message
+                    yield history, error, extra, save_dict
+            except StopIteration:
+                yield history, error, extra, save_dict
+            except RuntimeError as e:
+                if "generator raised StopIteration" in str(e):
+                    # assume last entry was bad, undo
+                    history.pop()
+                    yield history, error, extra, save_dict
+                else:
+                    if history and len(history) > 0 and len(history[0]) > 1 and history[-1][1] is None:
+                        history[-1][1] = ''
+                    yield history, str(e), extra, save_dict
+                    raise
+            except Exception as e:
+                # put error into user input
+                ex = "Exception: %s" % str(e)
+                if history and len(history) > 0 and len(history[0]) > 1 and history[-1][1] is None:
+                    history[-1][1] = ''
+                yield history, ex, extra, save_dict
+                raise
+            finally:
+                # clear_torch_cache()
+                # don't clear torch cache here, too early and stalls generation if used for all_bot()
+                pass
+            return
+
+        def clear_embeddings(langchain_mode1, db1s):
+            # clear any use of embedding that sits on GPU, else keeps accumulating GPU usage even if clear torch cache
+            if db_type in ['chroma', 'chroma_old'] and langchain_mode1 not in ['LLM', 'Disabled', None, '']:
+                from gpt_langchain import clear_embedding, length_db1
+                db = dbs.get('langchain_mode1')
+                if db is not None and not isinstance(db, str):
+                    clear_embedding(db)
+                if db1s is not None and langchain_mode1 in db1s:
+                    db1 = db1s[langchain_mode1]
+                    if len(db1) == length_db1():
+                        clear_embedding(db1[0])
+
+        def bot(*args, retry=False):
+            history, fun1, langchain_mode1, db1, requests_state1, valid_key, h2ogpt_key1 = prep_bot(*args, retry=retry)
+            save_dict = dict()
+            error = ''
+            extra = ''
+            try:
+                for res in get_response(fun1, history):
+                    history, error, extra, save_dict = res
+                    # pass back to gradio only these, rest are consumed in this function
+                    yield history, error
+            finally:
+                clear_torch_cache()
+                clear_embeddings(langchain_mode1, db1)
+            if 'extra_dict' not in save_dict:
+                save_dict['extra_dict'] = {}
+            save_dict['valid_key'] = valid_key
+            save_dict['h2ogpt_key'] = h2ogpt_key1
+            if requests_state1:
+                save_dict['extra_dict'].update(requests_state1)
+            else:
+                save_dict['extra_dict'].update(dict(username='NO_REQUEST'))
+            save_dict['error'] = error
+            save_dict['extra'] = extra
+            save_dict['which_api'] = 'bot'
+            save_generate_output(**save_dict)
+
+        def all_bot(*args, retry=False, model_states1=None, all_models=None):
+            args_list = list(args).copy()
+            chatbots = args_list[-len(model_states1):]
+            args_list0 = args_list[:-len(model_states1)]  # same for all models
+            exceptions = []
+            stream_output1 = args_list[eval_func_param_names.index('stream_output')]
+            max_time1 = args_list[eval_func_param_names.index('max_time')]
+            langchain_mode1 = args_list[eval_func_param_names.index('langchain_mode')]
+
+            visible_models1 = args_list[eval_func_param_names.index('visible_models')]
+            assert isinstance(all_models, list)
+            assert len(all_models) == len(model_states1)
+            visible_list = get_model_lock_visible_list(visible_models1, all_models)
+
+            isize = len(input_args_list) + 1  # states + chat history
+            db1s = None
+            requests_state1 = None
+            valid_key = False
+            h2ogpt_key1 = ''
+            extras = []
+            exceptions = []
+            save_dicts = []
+            try:
+                gen_list = []
+                for chatboti, (chatbot1, model_state1) in enumerate(zip(chatbots, model_states1)):
+                    args_list1 = args_list0.copy()
+                    args_list1.insert(-isize + 2,
+                                      model_state1)  # insert at -2 so is at -3, and after chatbot1 added, at -4
+                    # if at start, have None in response still, replace with '' so client etc. acts like normal
+                    # assumes other parts of code treat '' and None as if no response yet from bot
+                    # can't do this later in bot code as racy with threaded generators
+                    if len(chatbot1) > 0 and len(chatbot1[-1]) == 2 and chatbot1[-1][1] is None:
+                        chatbot1[-1][1] = ''
+                    args_list1.append(chatbot1)
+                    # so consistent with prep_bot()
+                    # with model_state1 at -3, my_db_state1 at -2, and history(chatbot) at -1
+                    # langchain_mode1 and my_db_state1 and requests_state1 should be same for every bot
+                    history, fun1, langchain_mode1, db1s, requests_state1, valid_key, h2ogpt_key1, = \
+                        prep_bot(*tuple(args_list1), retry=retry,
+                                 which_model=chatboti)
+                    if visible_list[chatboti]:
+                        gen1 = get_response(fun1, history)
+                        if stream_output1:
+                            gen1 = TimeoutIterator(gen1, timeout=0.01, sentinel=None, raise_on_exception=False)
+                        # else timeout will truncate output for non-streaming case
+                    else:
+                        gen1 = gen1_fake(fun1, history)
+                    gen_list.append(gen1)
+
+                bots_old = chatbots.copy()
+                exceptions_old = [''] * len(bots_old)
+                extras_old = [''] * len(bots_old)
+                save_dicts_old = [{}] * len(bots_old)
+                tgen0 = time.time()
+                for res1 in itertools.zip_longest(*gen_list):
+                    if time.time() - tgen0 > max_time1:
+                        print("Took too long: %s" % max_time1, flush=True)
+                        break
+
+                    bots = [x[0] if x is not None and not isinstance(x, BaseException) else y
+                            for x, y in zip(res1, bots_old)]
+                    bots_old = bots.copy()
+
+                    def larger_str(x, y):
+                        return x if len(x) > len(y) else y
+
+                    exceptions = [x[1] if x is not None and not isinstance(x, BaseException) else larger_str(str(x), y)
+                                  for x, y in zip(res1, exceptions_old)]
+                    exceptions_old = exceptions.copy()
+
+                    extras = [x[2] if x is not None and not isinstance(x, BaseException) else y
+                              for x, y in zip(res1, extras_old)]
+                    extras_old = extras.copy()
+
+                    save_dicts = [x[3] if x is not None and not isinstance(x, BaseException) else y
+                                  for x, y in zip(res1, save_dicts_old)]
+                    save_dicts_old = save_dicts.copy()
+
+                    def choose_exc(x):
+                        # don't expose ports etc. to exceptions window
+                        if is_public:
+                            return "Endpoint unavailable or failed"
+                        else:
+                            return x
+
+                    exceptions_str = '\n'.join(
+                        ['Model %s: %s' % (iix, choose_exc(x)) for iix, x in enumerate(exceptions) if
+                         x not in [None, '', 'None']])
+                    # yield back to gradio only is bots + exceptions, rest are consumed locally
+                    if len(bots) > 1:
+                        yield tuple(bots + [exceptions_str])
+                    else:
+                        yield bots[0], exceptions_str
+                if exceptions:
+                    exceptions_reduced = [x for x in exceptions if x not in ['', None, 'None']]
+                    if exceptions_reduced:
+                        print("Generate exceptions: %s" % exceptions_reduced, flush=True)
+            finally:
+                clear_torch_cache()
+                clear_embeddings(langchain_mode1, db1s)
+            for extra, error, save_dict, model_name in zip(extras, exceptions, save_dicts, all_models):
+                if 'extra_dict' not in save_dict:
+                    save_dict['extra_dict'] = {}
+                if requests_state1:
+                    save_dict['extra_dict'].update(requests_state1)
+                else:
+                    save_dict['extra_dict'].update(dict(username='NO_REQUEST'))
+                save_dict['error'] = error
+                save_dict['extra'] = extra
+                save_dict['which_api'] = 'all_bot_%s' % model_name
+                save_dict['valid_key'] = valid_key
+                save_dict['h2ogpt_key'] = h2ogpt_key1
+                save_generate_output(**save_dict)
+
+        # NORMAL MODEL
+        user_args = dict(fn=functools.partial(user, sanitize_user_prompt=kwargs['sanitize_user_prompt']),
+                         inputs=inputs_list + [text_output],
+                         outputs=text_output,
+                         )
+        bot_args = dict(fn=bot,
+                        inputs=inputs_list + [model_state, my_db_state, selection_docs_state, requests_state] + [
+                            text_output],
+                        outputs=[text_output, chat_exception_text],
+                        )
+        retry_bot_args = dict(fn=functools.partial(bot, retry=True),
+                              inputs=inputs_list + [model_state, my_db_state, selection_docs_state, requests_state] + [
+                                  text_output],
+                              outputs=[text_output, chat_exception_text],
+                              )
+        retry_user_args = dict(fn=functools.partial(user, retry=True),
+                               inputs=inputs_list + [text_output],
+                               outputs=text_output,
+                               )
+        undo_user_args = dict(fn=functools.partial(user, undo=True),
+                              inputs=inputs_list + [text_output],
+                              outputs=text_output,
+                              )
+
+        # MODEL2
+        user_args2 = dict(fn=functools.partial(user, sanitize_user_prompt=kwargs['sanitize_user_prompt']),
+                          inputs=inputs_list2 + [text_output2],
+                          outputs=text_output2,
+                          )
+        bot_args2 = dict(fn=bot,
+                         inputs=inputs_list2 + [model_state2, my_db_state, selection_docs_state, requests_state] + [
+                             text_output2],
+                         outputs=[text_output2, chat_exception_text],
+                         )
+        retry_bot_args2 = dict(fn=functools.partial(bot, retry=True),
+                               inputs=inputs_list2 + [model_state2, my_db_state, selection_docs_state,
+                                                      requests_state] + [text_output2],
+                               outputs=[text_output2, chat_exception_text],
+                               )
+        retry_user_args2 = dict(fn=functools.partial(user, retry=True),
+                                inputs=inputs_list2 + [text_output2],
+                                outputs=text_output2,
+                                )
+        undo_user_args2 = dict(fn=functools.partial(user, undo=True),
+                               inputs=inputs_list2 + [text_output2],
+                               outputs=text_output2,
+                               )
+
+        # MODEL N
+        all_user_args = dict(fn=functools.partial(all_user,
+                                                  sanitize_user_prompt=kwargs['sanitize_user_prompt'],
+                                                  num_model_lock=len(text_outputs),
+                                                  all_models=kwargs['all_models']
+                                                  ),
+                             inputs=inputs_list + text_outputs,
+                             outputs=text_outputs,
+                             )
+        all_bot_args = dict(fn=functools.partial(all_bot, model_states1=model_states,
+                                                 all_models=kwargs['all_models']),
+                            inputs=inputs_list + [my_db_state, selection_docs_state, requests_state] +
+                                   text_outputs,
+                            outputs=text_outputs + [chat_exception_text],
+                            )
+        all_retry_bot_args = dict(fn=functools.partial(all_bot, model_states1=model_states,
+                                                       all_models=kwargs['all_models'],
+                                                       retry=True),
+                                  inputs=inputs_list + [my_db_state, selection_docs_state, requests_state] +
+                                         text_outputs,
+                                  outputs=text_outputs + [chat_exception_text],
+                                  )
+        all_retry_user_args = dict(fn=functools.partial(all_user, retry=True,
+                                                        sanitize_user_prompt=kwargs['sanitize_user_prompt'],
+                                                        num_model_lock=len(text_outputs),
+                                                        all_models=kwargs['all_models']
+                                                        ),
+                                   inputs=inputs_list + text_outputs,
+                                   outputs=text_outputs,
+                                   )
+        all_undo_user_args = dict(fn=functools.partial(all_user, undo=True,
+                                                       sanitize_user_prompt=kwargs['sanitize_user_prompt'],
+                                                       num_model_lock=len(text_outputs),
+                                                       all_models=kwargs['all_models']
+                                                       ),
+                                  inputs=inputs_list + text_outputs,
+                                  outputs=text_outputs,
+                                  )
+
+        def clear_instruct():
+            return gr.Textbox.update(value='')
+
+        def deselect_radio_chats():
+            return gr.update(value=None)
+
+        def clear_all():
+            return gr.Textbox.update(value=''), gr.Textbox.update(value=''), gr.update(value=None), \
+                gr.Textbox.update(value=''), gr.Textbox.update(value='')
+
+        if kwargs['model_states']:
+            submits1 = submits2 = submits3 = []
+            submits4 = []
+
+            triggers = [instruction, submit, retry_btn]
+            fun_source = [instruction.submit, submit.click, retry_btn.click]
+            fun_name = ['instruction', 'submit', 'retry']
+            user_args = [all_user_args, all_user_args, all_retry_user_args]
+            bot_args = [all_bot_args, all_bot_args, all_retry_bot_args]
+            for userargs1, botarg1, funn1, funs1, trigger1, in zip(user_args, bot_args, fun_name, fun_source, triggers):
+                submit_event11 = funs1(fn=user_state_setup,
+                                       inputs=[my_db_state, requests_state, trigger1, trigger1],
+                                       outputs=[my_db_state, requests_state, trigger1],
+                                       queue=queue)
+                submit_event1a = submit_event11.then(**userargs1, queue=queue,
+                                                     api_name='%s' % funn1 if allow_api else None)
+                # if hit enter on new instruction for submitting new query, no longer the saved chat
+                submit_event1b = submit_event1a.then(clear_all, inputs=None,
+                                                     outputs=[instruction, iinput, radio_chats, score_text,
+                                                              score_text2],
+                                                     queue=queue)
+                submit_event1c = submit_event1b.then(**botarg1,
+                                                     api_name='%s_bot' % funn1 if allow_api else None,
+                                                     queue=queue)
+                submit_event1d = submit_event1c.then(**all_score_args,
+                                                     api_name='%s_bot_score' % funn1 if allow_api else None,
+                                                     queue=queue)
+
+                submits1.extend([submit_event1a, submit_event1b, submit_event1c, submit_event1d])
+
+            # if undo, no longer the saved chat
+            submit_event4 = undo.click(fn=user_state_setup,
+                                       inputs=[my_db_state, requests_state, undo, undo],
+                                       outputs=[my_db_state, requests_state, undo],
+                                       queue=queue) \
+                .then(**all_undo_user_args, api_name='undo' if allow_api else None) \
+                .then(clear_all, inputs=None, outputs=[instruction, iinput, radio_chats, score_text,
+                                                       score_text2], queue=queue) \
+                .then(**all_score_args, api_name='undo_score' if allow_api else None)
+            submits4 = [submit_event4]
+
+        else:
+            # in case 2nd model, consume instruction first, so can clear quickly
+            # bot doesn't consume instruction itself, just history from user, so why works
+            submit_event11 = instruction.submit(fn=user_state_setup,
+                                                inputs=[my_db_state, requests_state, instruction, instruction],
+                                                outputs=[my_db_state, requests_state, instruction],
+                                                queue=queue)
+            submit_event1a = submit_event11.then(**user_args, queue=queue,
+                                                 api_name='instruction' if allow_api else None)
+            # if hit enter on new instruction for submitting new query, no longer the saved chat
+            submit_event1a2 = submit_event1a.then(deselect_radio_chats, inputs=None, outputs=radio_chats, queue=queue)
+            submit_event1b = submit_event1a2.then(**user_args2, api_name='instruction2' if allow_api else None)
+            submit_event1c = submit_event1b.then(clear_instruct, None, instruction) \
+                .then(clear_instruct, None, iinput)
+            submit_event1d = submit_event1c.then(**bot_args, api_name='instruction_bot' if allow_api else None,
+                                                 queue=queue)
+            submit_event1e = submit_event1d.then(**score_args,
+                                                 api_name='instruction_bot_score' if allow_api else None,
+                                                 queue=queue)
+            submit_event1f = submit_event1e.then(**bot_args2, api_name='instruction_bot2' if allow_api else None,
+                                                 queue=queue)
+            submit_event1g = submit_event1f.then(**score_args2,
+                                                 api_name='instruction_bot_score2' if allow_api else None, queue=queue)
+
+            submits1 = [submit_event1a, submit_event1a2, submit_event1b, submit_event1c, submit_event1d,
+                        submit_event1e,
+                        submit_event1f, submit_event1g]
+
+            submit_event21 = submit.click(fn=user_state_setup,
+                                          inputs=[my_db_state, requests_state, submit, submit],
+                                          outputs=[my_db_state, requests_state, submit],
+                                          queue=queue)
+            submit_event2a = submit_event21.then(**user_args, api_name='submit' if allow_api else None)
+            # if submit new query, no longer the saved chat
+            submit_event2a2 = submit_event2a.then(deselect_radio_chats, inputs=None, outputs=radio_chats, queue=queue)
+            submit_event2b = submit_event2a2.then(**user_args2, api_name='submit2' if allow_api else None)
+            submit_event2c = submit_event2b.then(clear_all, inputs=None,
+                                                 outputs=[instruction, iinput, radio_chats, score_text, score_text2],
+                                                 queue=queue)
+            submit_event2d = submit_event2c.then(**bot_args, api_name='submit_bot' if allow_api else None, queue=queue)
+            submit_event2e = submit_event2d.then(**score_args,
+                                                 api_name='submit_bot_score' if allow_api else None,
+                                                 queue=queue)
+            submit_event2f = submit_event2e.then(**bot_args2, api_name='submit_bot2' if allow_api else None,
+                                                 queue=queue)
+            submit_event2g = submit_event2f.then(**score_args2,
+                                                 api_name='submit_bot_score2' if allow_api else None,
+                                                 queue=queue)
+
+            submits2 = [submit_event2a, submit_event2a2, submit_event2b, submit_event2c, submit_event2d,
+                        submit_event2e,
+                        submit_event2f, submit_event2g]
+
+            submit_event31 = retry_btn.click(fn=user_state_setup,
+                                             inputs=[my_db_state, requests_state, retry_btn, retry_btn],
+                                             outputs=[my_db_state, requests_state, retry_btn],
+                                             queue=queue)
+            submit_event3a = submit_event31.then(**user_args, api_name='retry' if allow_api else None)
+            # if retry, no longer the saved chat
+            submit_event3a2 = submit_event3a.then(deselect_radio_chats, inputs=None, outputs=radio_chats, queue=queue)
+            submit_event3b = submit_event3a2.then(**user_args2, api_name='retry2' if allow_api else None)
+            submit_event3c = submit_event3b.then(clear_instruct, None, instruction) \
+                .then(clear_instruct, None, iinput)
+            submit_event3d = submit_event3c.then(**retry_bot_args, api_name='retry_bot' if allow_api else None,
+                                                 queue=queue)
+            submit_event3e = submit_event3d.then(**score_args,
+                                                 api_name='retry_bot_score' if allow_api else None,
+                                                 queue=queue)
+            submit_event3f = submit_event3e.then(**retry_bot_args2, api_name='retry_bot2' if allow_api else None,
+                                                 queue=queue)
+            submit_event3g = submit_event3f.then(**score_args2,
+                                                 api_name='retry_bot_score2' if allow_api else None,
+                                                 queue=queue)
+
+            submits3 = [submit_event3a, submit_event3a2, submit_event3b, submit_event3c, submit_event3d,
+                        submit_event3e,
+                        submit_event3f, submit_event3g]
+
+            # if undo, no longer the saved chat
+            submit_event4 = undo.click(fn=user_state_setup,
+                                       inputs=[my_db_state, requests_state, undo, undo],
+                                       outputs=[my_db_state, requests_state, undo],
+                                       queue=queue) \
+                .then(**undo_user_args, api_name='undo' if allow_api else None) \
+                .then(**undo_user_args2, api_name='undo2' if allow_api else None) \
+                .then(clear_all, inputs=None, outputs=[instruction, iinput, radio_chats, score_text,
+                                                       score_text2], queue=queue) \
+                .then(**score_args, api_name='undo_score' if allow_api else None) \
+                .then(**score_args2, api_name='undo_score2' if allow_api else None)
+            submits4 = [submit_event4]
+
+        # MANAGE CHATS
+        def dedup(short_chat, short_chats):
+            if short_chat not in short_chats:
+                return short_chat
+            for i in range(1, 1000):
+                short_chat_try = short_chat + "_" + str(i)
+                if short_chat_try not in short_chats:
+                    return short_chat_try
+            # fallback and hope for best
+            short_chat = short_chat + "_" + str(random.random())
+            return short_chat
+
+        def get_short_chat(x, short_chats, short_len=20, words=4):
+            if x and len(x[0]) == 2 and x[0][0] is not None:
+                short_chat = ' '.join(x[0][0][:short_len].split(' ')[:words]).strip()
+                if not short_chat:
+                    # e.g.summarization, try using answer
+                    short_chat = ' '.join(x[0][1][:short_len].split(' ')[:words]).strip()
+                    if not short_chat:
+                        short_chat = 'Unk'
+                short_chat = dedup(short_chat, short_chats)
+            else:
+                short_chat = None
+            return short_chat
+
+        def is_chat_same(x, y):
+            # <p> etc. added in chat, try to remove some of that to help avoid dup entries when hit new conversation
+            is_same = True
+            # length of conversation has to be same
+            if len(x) != len(y):
+                return False
+            if len(x) != len(y):
+                return False
+            for stepx, stepy in zip(x, y):
+                if len(stepx) != len(stepy):
+                    # something off with a conversation
+                    return False
+                for stepxx, stepyy in zip(stepx, stepy):
+                    if len(stepxx) != len(stepyy):
+                        # something off with a conversation
+                        return False
+                    if len(stepxx) != 2:
+                        # something off
+                        return False
+                    if len(stepyy) != 2:
+                        # something off
+                        return False
+                    questionx = stepxx[0].replace('<p>', '').replace('</p>', '') if stepxx[0] is not None else None
+                    answerx = stepxx[1].replace('<p>', '').replace('</p>', '') if stepxx[1] is not None else None
+
+                    questiony = stepyy[0].replace('<p>', '').replace('</p>', '') if stepyy[0] is not None else None
+                    answery = stepyy[1].replace('<p>', '').replace('</p>', '') if stepyy[1] is not None else None
+
+                    if questionx != questiony or answerx != answery:
+                        return False
+            return is_same
+
+        def save_chat(*args, chat_is_list=False, auth_filename=None, auth_freeze=None):
+            args_list = list(args)
+            db1s = args_list[0]
+            requests_state1 = args_list[1]
+            args_list = args_list[2:]
+            if not chat_is_list:
+                # list of chatbot histories,
+                # can't pass in list with list of chatbot histories and state due to gradio limits
+                chat_list = args_list[:-1]
+            else:
+                assert len(args_list) == 2
+                chat_list = args_list[0]
+            # if old chat file with single chatbot, get into shape
+            if isinstance(chat_list, list) and len(chat_list) > 0 and isinstance(chat_list[0], list) and len(
+                    chat_list[0]) == 2 and isinstance(chat_list[0][0], str) and isinstance(chat_list[0][1], str):
+                chat_list = [chat_list]
+            # remove None histories
+            chat_list_not_none = [x for x in chat_list if x and len(x) > 0 and len(x[0]) == 2 and x[0][1] is not None]
+            chat_list_none = [x for x in chat_list if x not in chat_list_not_none]
+            if len(chat_list_none) > 0 and len(chat_list_not_none) == 0:
+                raise ValueError("Invalid chat file")
+            # dict with keys of short chat names, values of list of list of chatbot histories
+            chat_state1 = args_list[-1]
+            short_chats = list(chat_state1.keys())
+            if len(chat_list_not_none) > 0:
+                # make short_chat key from only first history, based upon question that is same anyways
+                chat_first = chat_list_not_none[0]
+                short_chat = get_short_chat(chat_first, short_chats)
+                if short_chat:
+                    old_chat_lists = list(chat_state1.values())
+                    already_exists = any([is_chat_same(chat_list, x) for x in old_chat_lists])
+                    if not already_exists:
+                        chat_state1[short_chat] = chat_list.copy()
+
+            # reverse so newest at top
+            choices = list(chat_state1.keys()).copy()
+            choices.reverse()
+
+            # save saved chats and chatbots to auth file
+            text_output1 = chat_list[0]
+            text_output21 = chat_list[1]
+            text_outputs1 = chat_list[2:]
+            save_auth(requests_state1, auth_filename, auth_freeze, chat_state1=chat_state1,
+                      text_output1=text_output1, text_output21=text_output21, text_outputs1=text_outputs1)
+
+            return chat_state1, gr.update(choices=choices, value=None)
+
+        def switch_chat(chat_key, chat_state1, num_model_lock=0):
+            chosen_chat = chat_state1[chat_key]
+            # deal with possible different size of chat list vs. current list
+            ret_chat = [None] * (2 + num_model_lock)
+            for chati in range(0, 2 + num_model_lock):
+                ret_chat[chati % len(ret_chat)] = chosen_chat[chati % len(chosen_chat)]
+            return tuple(ret_chat)
+
+        def clear_texts(*args):
+            return tuple([gr.Textbox.update(value='')] * len(args))
+
+        def clear_scores():
+            return gr.Textbox.update(value=res_value), \
+                gr.Textbox.update(value='Response Score: NA'), \
+                gr.Textbox.update(value='Response Score: NA')
+
+        switch_chat_fun = functools.partial(switch_chat, num_model_lock=len(text_outputs))
+        radio_chats.input(switch_chat_fun,
+                          inputs=[radio_chats, chat_state],
+                          outputs=[text_output, text_output2] + text_outputs) \
+            .then(clear_scores, outputs=[score_text, score_text2, score_text_nochat])
+
+        def remove_chat(chat_key, chat_state1):
+            if isinstance(chat_key, str):
+                chat_state1.pop(chat_key, None)
+            return gr.update(choices=list(chat_state1.keys()), value=None), chat_state1
+
+        remove_chat_event = remove_chat_btn.click(remove_chat,
+                                                  inputs=[radio_chats, chat_state],
+                                                  outputs=[radio_chats, chat_state],
+                                                  queue=False, api_name='remove_chat')
+
+        def get_chats1(chat_state1):
+            base = 'chats'
+            base = makedirs(base, exist_ok=True, tmp_ok=True, use_base=True)
+            filename = os.path.join(base, 'chats_%s.json' % str(uuid.uuid4()))
+            with open(filename, "wt") as f:
+                f.write(json.dumps(chat_state1, indent=2))
+            return filename
+
+        export_chat_event = export_chats_btn.click(get_chats1, inputs=chat_state, outputs=chats_file, queue=False,
+                                                   api_name='export_chats' if allow_api else None)
+
+        def add_chats_from_file(db1s, requests_state1, file, chat_state1, radio_chats1, chat_exception_text1,
+                                auth_filename=None, auth_freeze=None):
+            if not file:
+                return None, chat_state1, gr.update(choices=list(chat_state1.keys()), value=None), chat_exception_text1
+            if isinstance(file, str):
+                files = [file]
+            else:
+                files = file
+            if not files:
+                return None, chat_state1, gr.update(choices=list(chat_state1.keys()), value=None), chat_exception_text1
+            chat_exception_list = []
+            for file1 in files:
+                try:
+                    if hasattr(file1, 'name'):
+                        file1 = file1.name
+                    with open(file1, "rt") as f:
+                        new_chats = json.loads(f.read())
+                        for chat1_k, chat1_v in new_chats.items():
+                            # ignore chat1_k, regenerate and de-dup to avoid loss
+                            chat_state1, _ = save_chat(db1s, requests_state1, chat1_v, chat_state1, chat_is_list=True)
+                except BaseException as e:
+                    t, v, tb = sys.exc_info()
+                    ex = ''.join(traceback.format_exception(t, v, tb))
+                    ex_str = "File %s exception: %s" % (file1, str(e))
+                    print(ex_str, flush=True)
+                    chat_exception_list.append(ex_str)
+                    chat_exception_text1 = '\n'.join(chat_exception_list)
+            # save chat to auth file
+            save_auth(requests_state1, auth_filename, auth_freeze, chat_state1=chat_state1)
+            return None, chat_state1, gr.update(choices=list(chat_state1.keys()), value=None), chat_exception_text1
+
+        # note for update_user_db_func output is ignored for db
+        chatup_change_eventa = chatsup_output.change(user_state_setup,
+                                                     inputs=[my_db_state, requests_state, langchain_mode],
+                                                     outputs=[my_db_state, requests_state, langchain_mode],
+                                                     show_progress='minimal')
+        add_chats_from_file_func = functools.partial(add_chats_from_file,
+                                                     auth_filename=kwargs['auth_filename'],
+                                                     auth_freeze=kwargs['auth_freeze'],
+                                                     )
+        chatup_change_event = chatup_change_eventa.then(add_chats_from_file_func,
+                                                        inputs=[my_db_state, requests_state] +
+                                                               [chatsup_output, chat_state, radio_chats,
+                                                                chat_exception_text],
+                                                        outputs=[chatsup_output, chat_state, radio_chats,
+                                                                 chat_exception_text],
+                                                        queue=False,
+                                                        api_name='add_to_chats' if allow_api else None)
+
+        clear_chat_event = clear_chat_btn.click(fn=clear_texts,
+                                                inputs=[text_output, text_output2] + text_outputs,
+                                                outputs=[text_output, text_output2] + text_outputs,
+                                                queue=False, api_name='clear' if allow_api else None) \
+            .then(deselect_radio_chats, inputs=None, outputs=radio_chats, queue=False) \
+            .then(clear_scores, outputs=[score_text, score_text2, score_text_nochat])
+
+        clear_eventa = save_chat_btn.click(user_state_setup,
+                                           inputs=[my_db_state, requests_state, langchain_mode],
+                                           outputs=[my_db_state, requests_state, langchain_mode],
+                                           show_progress='minimal')
+        save_chat_func = functools.partial(save_chat,
+                                           auth_filename=kwargs['auth_filename'],
+                                           auth_freeze=kwargs['auth_freeze'],
+                                           )
+        clear_event = clear_eventa.then(save_chat_func,
+                                        inputs=[my_db_state, requests_state] +
+                                               [text_output, text_output2] + text_outputs +
+                                               [chat_state],
+                                        outputs=[chat_state, radio_chats],
+                                        api_name='save_chat' if allow_api else None)
+        if kwargs['score_model']:
+            clear_event2 = clear_event.then(clear_scores, outputs=[score_text, score_text2, score_text_nochat])
+
+        # NOTE: clear of instruction/iinput for nochat has to come after score,
+        # because score for nochat consumes actual textbox, while chat consumes chat history filled by user()
+        no_chat_args = dict(fn=fun,
+                            inputs=[model_state, my_db_state, selection_docs_state, requests_state] + inputs_list,
+                            outputs=text_output_nochat,
+                            queue=queue,
+                            )
+        submit_event_nochat = submit_nochat.click(**no_chat_args, api_name='submit_nochat' if allow_api else None) \
+            .then(clear_torch_cache) \
+            .then(**score_args_nochat, api_name='instruction_bot_score_nochat' if allow_api else None, queue=queue) \
+            .then(clear_instruct, None, instruction_nochat) \
+            .then(clear_instruct, None, iinput_nochat) \
+            .then(clear_torch_cache)
+        # copy of above with text box submission
+        submit_event_nochat2 = instruction_nochat.submit(**no_chat_args) \
+            .then(clear_torch_cache) \
+            .then(**score_args_nochat, queue=queue) \
+            .then(clear_instruct, None, instruction_nochat) \
+            .then(clear_instruct, None, iinput_nochat) \
+            .then(clear_torch_cache)
+
+        submit_event_nochat_api = submit_nochat_api.click(fun_with_dict_str,
+                                                          inputs=[model_state, my_db_state, selection_docs_state,
+                                                                  requests_state,
+                                                                  inputs_dict_str],
+                                                          outputs=text_output_nochat_api,
+                                                          queue=True,  # required for generator
+                                                          api_name='submit_nochat_api' if allow_api else None)
+
+        submit_event_nochat_api_plain = submit_nochat_api_plain.click(fun_with_dict_str_plain,
+                                                                      inputs=inputs_dict_str,
+                                                                      outputs=text_output_nochat_api,
+                                                                      queue=False,
+                                                                      api_name='submit_nochat_plain_api' if allow_api else None)
+
+        def load_model(model_name, lora_weights, server_name, model_state_old, prompt_type_old,
+                       load_8bit, load_4bit, low_bit_mode,
+                       load_gptq, load_exllama, use_safetensors, revision,
+                       use_gpu_id, gpu_id, max_seq_len1, rope_scaling1,
+                       model_path_llama1, model_name_gptj1, model_name_gpt4all_llama1,
+                       n_gpu_layers1, n_batch1, n_gqa1, llamacpp_dict_more1,
+                       system_prompt1):
+            try:
+                llamacpp_dict = ast.literal_eval(llamacpp_dict_more1)
+            except:
+                print("Failed to use user input for llamacpp_dict_more1 dict", flush=True)
+                llamacpp_dict = {}
+            llamacpp_dict.update(dict(model_path_llama=model_path_llama1,
+                                      model_name_gptj=model_name_gptj1,
+                                      model_name_gpt4all_llama=model_name_gpt4all_llama1,
+                                      n_gpu_layers=n_gpu_layers1,
+                                      n_batch=n_batch1,
+                                      n_gqa=n_gqa1,
+                                      ))
+
+            # ensure no API calls reach here
+            if is_public:
+                raise RuntimeError("Illegal access for %s" % model_name)
+            # ensure old model removed from GPU memory
+            if kwargs['debug']:
+                print("Pre-switch pre-del GPU memory: %s" % get_torch_allocated(), flush=True)
+
+            model0 = model_state0['model']
+            if isinstance(model_state_old['model'], str) and \
+                    model0 is not None and \
+                    hasattr(model0, 'cpu'):
+                # best can do, move model loaded at first to CPU
+                model0.cpu()
+
+            if model_state_old['model'] is not None and \
+                    not isinstance(model_state_old['model'], str):
+                if hasattr(model_state_old['model'], 'cpu'):
+                    try:
+                        model_state_old['model'].cpu()
+                    except Exception as e:
+                        # sometimes hit NotImplementedError: Cannot copy out of meta tensor; no data!
+                        print("Unable to put model on CPU: %s" % str(e), flush=True)
+                del model_state_old['model']
+                model_state_old['model'] = None
+
+            if model_state_old['tokenizer'] is not None and not isinstance(model_state_old['tokenizer'], str):
+                del model_state_old['tokenizer']
+                model_state_old['tokenizer'] = None
+
+            clear_torch_cache()
+            if kwargs['debug']:
+                print("Pre-switch post-del GPU memory: %s" % get_torch_allocated(), flush=True)
+            if not model_name:
+                model_name = no_model_str
+            if model_name == no_model_str:
+                # no-op if no model, just free memory
+                # no detranscribe needed for model, never go into evaluate
+                lora_weights = no_lora_str
+                server_name = no_server_str
+                return kwargs['model_state_none'].copy(), \
+                    model_name, lora_weights, server_name, prompt_type_old, \
+                    gr.Slider.update(maximum=256), \
+                    gr.Slider.update(maximum=256)
+
+            # don't deepcopy, can contain model itself
+            all_kwargs1 = all_kwargs.copy()
+            all_kwargs1['base_model'] = model_name.strip()
+            all_kwargs1['load_8bit'] = load_8bit
+            all_kwargs1['load_4bit'] = load_4bit
+            all_kwargs1['low_bit_mode'] = low_bit_mode
+            all_kwargs1['load_gptq'] = load_gptq
+            all_kwargs1['load_exllama'] = load_exllama
+            all_kwargs1['use_safetensors'] = use_safetensors
+            all_kwargs1['revision'] = None if not revision else revision  # transcribe, don't pass ''
+            all_kwargs1['use_gpu_id'] = use_gpu_id
+            all_kwargs1['gpu_id'] = int(gpu_id) if gpu_id not in [None, 'None'] else None  # detranscribe
+            all_kwargs1['llamacpp_dict'] = llamacpp_dict
+            all_kwargs1['max_seq_len'] = max_seq_len1
+            try:
+                all_kwargs1['rope_scaling'] = str_to_dict(rope_scaling1)  # transcribe
+            except:
+                print("Failed to use user input for rope_scaling dict", flush=True)
+                all_kwargs1['rope_scaling'] = {}
+            model_lower = model_name.strip().lower()
+            if model_lower in inv_prompt_type_to_model_lower:
+                prompt_type1 = inv_prompt_type_to_model_lower[model_lower]
+            else:
+                prompt_type1 = prompt_type_old
+
+            # detranscribe
+            if lora_weights == no_lora_str:
+                lora_weights = ''
+            all_kwargs1['lora_weights'] = lora_weights.strip()
+            if server_name == no_server_str:
+                server_name = ''
+            all_kwargs1['inference_server'] = server_name.strip()
+
+            model1, tokenizer1, device1 = get_model(reward_type=False,
+                                                    **get_kwargs(get_model, exclude_names=['reward_type'],
+                                                                 **all_kwargs1))
+            clear_torch_cache()
+
+            tokenizer_base_model = model_name
+            prompt_dict1, error0 = get_prompt(prompt_type1, '',
+                                              chat=False, context='', reduced=False, making_context=False,
+                                              return_dict=True, system_prompt=system_prompt1)
+            model_state_new = dict(model=model1, tokenizer=tokenizer1, device=device1,
+                                   base_model=model_name, tokenizer_base_model=tokenizer_base_model,
+                                   lora_weights=lora_weights, inference_server=server_name,
+                                   prompt_type=prompt_type1, prompt_dict=prompt_dict1,
+                                   )
+
+            max_max_new_tokens1 = get_max_max_new_tokens(model_state_new, **kwargs)
+
+            if kwargs['debug']:
+                print("Post-switch GPU memory: %s" % get_torch_allocated(), flush=True)
+            return model_state_new, model_name, lora_weights, server_name, prompt_type1, \
+                gr.Slider.update(maximum=max_max_new_tokens1), \
+                gr.Slider.update(maximum=max_max_new_tokens1)
+
+        def get_prompt_str(prompt_type1, prompt_dict1, system_prompt1, which=0):
+            if prompt_type1 in ['', None]:
+                print("Got prompt_type %s: %s" % (which, prompt_type1), flush=True)
+                return str({})
+            prompt_dict1, prompt_dict_error = get_prompt(prompt_type1, prompt_dict1, chat=False, context='',
+                                                         reduced=False, making_context=False, return_dict=True,
+                                                         system_prompt=system_prompt1)
+            if prompt_dict_error:
+                return str(prompt_dict_error)
+            else:
+                # return so user can manipulate if want and use as custom
+                return str(prompt_dict1)
+
+        get_prompt_str_func1 = functools.partial(get_prompt_str, which=1)
+        get_prompt_str_func2 = functools.partial(get_prompt_str, which=2)
+        prompt_type.change(fn=get_prompt_str_func1, inputs=[prompt_type, prompt_dict, system_prompt],
+                           outputs=prompt_dict, queue=False)
+        prompt_type2.change(fn=get_prompt_str_func2, inputs=[prompt_type2, prompt_dict2, system_prompt],
+                            outputs=prompt_dict2,
+                            queue=False)
+
+        def dropdown_prompt_type_list(x):
+            return gr.Dropdown.update(value=x)
+
+        def chatbot_list(x, model_used_in):
+            return gr.Textbox.update(label=f'h2oGPT [Model: {model_used_in}]')
+
+        load_model_args = dict(fn=load_model,
+                               inputs=[model_choice, lora_choice, server_choice, model_state, prompt_type,
+                                       model_load8bit_checkbox, model_load4bit_checkbox, model_low_bit_mode,
+                                       model_load_gptq, model_load_exllama_checkbox,
+                                       model_safetensors_checkbox, model_revision,
+                                       model_use_gpu_id_checkbox, model_gpu,
+                                       max_seq_len, rope_scaling,
+                                       model_path_llama, model_name_gptj, model_name_gpt4all_llama,
+                                       n_gpu_layers, n_batch, n_gqa, llamacpp_dict_more,
+                                       system_prompt],
+                               outputs=[model_state, model_used, lora_used, server_used,
+                                        # if prompt_type changes, prompt_dict will change via change rule
+                                        prompt_type, max_new_tokens, min_new_tokens,
+                                        ])
+        prompt_update_args = dict(fn=dropdown_prompt_type_list, inputs=prompt_type, outputs=prompt_type)
+        chatbot_update_args = dict(fn=chatbot_list, inputs=[text_output, model_used], outputs=text_output)
+        nochat_update_args = dict(fn=chatbot_list, inputs=[text_output_nochat, model_used], outputs=text_output_nochat)
+        load_model_event = load_model_button.click(**load_model_args,
+                                                   api_name='load_model' if allow_api and not is_public else None) \
+            .then(**prompt_update_args) \
+            .then(**chatbot_update_args) \
+            .then(**nochat_update_args) \
+            .then(clear_torch_cache)
+
+        load_model_args2 = dict(fn=load_model,
+                                inputs=[model_choice2, lora_choice2, server_choice2, model_state2, prompt_type2,
+                                        model_load8bit_checkbox2, model_load4bit_checkbox2, model_low_bit_mode2,
+                                        model_load_gptq2, model_load_exllama_checkbox2,
+                                        model_safetensors_checkbox2, model_revision2,
+                                        model_use_gpu_id_checkbox2, model_gpu2,
+                                        max_seq_len2, rope_scaling2,
+                                        model_path_llama2, model_name_gptj2, model_name_gpt4all_llama2,
+                                        n_gpu_layers2, n_batch2, n_gqa2, llamacpp_dict_more2,
+                                        system_prompt],
+                                outputs=[model_state2, model_used2, lora_used2, server_used2,
+                                         # if prompt_type2 changes, prompt_dict2 will change via change rule
+                                         prompt_type2, max_new_tokens2, min_new_tokens2
+                                         ])
+        prompt_update_args2 = dict(fn=dropdown_prompt_type_list, inputs=prompt_type2, outputs=prompt_type2)
+        chatbot_update_args2 = dict(fn=chatbot_list, inputs=[text_output2, model_used2], outputs=text_output2)
+        load_model_event2 = load_model_button2.click(**load_model_args2,
+                                                     api_name='load_model2' if allow_api and not is_public else None) \
+            .then(**prompt_update_args2) \
+            .then(**chatbot_update_args2) \
+            .then(clear_torch_cache)
+
+        def dropdown_model_lora_server_list(model_list0, model_x,
+                                            lora_list0, lora_x,
+                                            server_list0, server_x,
+                                            model_used1, lora_used1, server_used1,
+                                            model_used2, lora_used2, server_used2,
+                                            ):
+            model_new_state = [model_list0[0] + [model_x]]
+            model_new_options = [*model_new_state[0]]
+            if no_model_str in model_new_options:
+                model_new_options.remove(no_model_str)
+            model_new_options = [no_model_str] + sorted(model_new_options)
+            x1 = model_x if model_used1 == no_model_str else model_used1
+            x2 = model_x if model_used2 == no_model_str else model_used2
+            ret1 = [gr.Dropdown.update(value=x1, choices=model_new_options),
+                    gr.Dropdown.update(value=x2, choices=model_new_options),
+                    '', model_new_state]
+
+            lora_new_state = [lora_list0[0] + [lora_x]]
+            lora_new_options = [*lora_new_state[0]]
+            if no_lora_str in lora_new_options:
+                lora_new_options.remove(no_lora_str)
+            lora_new_options = [no_lora_str] + sorted(lora_new_options)
+            # don't switch drop-down to added lora if already have model loaded
+            x1 = lora_x if model_used1 == no_model_str else lora_used1
+            x2 = lora_x if model_used2 == no_model_str else lora_used2
+            ret2 = [gr.Dropdown.update(value=x1, choices=lora_new_options),
+                    gr.Dropdown.update(value=x2, choices=lora_new_options),
+                    '', lora_new_state]
+
+            server_new_state = [server_list0[0] + [server_x]]
+            server_new_options = [*server_new_state[0]]
+            if no_server_str in server_new_options:
+                server_new_options.remove(no_server_str)
+            server_new_options = [no_server_str] + sorted(server_new_options)
+            # don't switch drop-down to added server if already have model loaded
+            x1 = server_x if model_used1 == no_model_str else server_used1
+            x2 = server_x if model_used2 == no_model_str else server_used2
+            ret3 = [gr.Dropdown.update(value=x1, choices=server_new_options),
+                    gr.Dropdown.update(value=x2, choices=server_new_options),
+                    '', server_new_state]
+
+            return tuple(ret1 + ret2 + ret3)
+
+        add_model_lora_server_event = \
+            add_model_lora_server_button.click(fn=dropdown_model_lora_server_list,
+                                               inputs=[model_options_state, new_model] +
+                                                      [lora_options_state, new_lora] +
+                                                      [server_options_state, new_server] +
+                                                      [model_used, lora_used, server_used] +
+                                                      [model_used2, lora_used2, server_used2],
+                                               outputs=[model_choice, model_choice2, new_model, model_options_state] +
+                                                       [lora_choice, lora_choice2, new_lora, lora_options_state] +
+                                                       [server_choice, server_choice2, new_server,
+                                                        server_options_state],
+                                               queue=False)
+
+        go_event = go_btn.click(lambda: gr.update(visible=False), None, go_btn, api_name="go" if allow_api else None,
+                                queue=False) \
+            .then(lambda: gr.update(visible=True), None, normal_block, queue=False) \
+            .then(**load_model_args, queue=False).then(**prompt_update_args, queue=False)
+
+        def compare_textbox_fun(x):
+            return gr.Textbox.update(visible=x)
+
+        def compare_column_fun(x):
+            return gr.Column.update(visible=x)
+
+        def compare_prompt_fun(x):
+            return gr.Dropdown.update(visible=x)
+
+        def slider_fun(x):
+            return gr.Slider.update(visible=x)
+
+        compare_checkbox.select(compare_textbox_fun, compare_checkbox, text_output2,
+                                api_name="compare_checkbox" if allow_api else None) \
+            .then(compare_column_fun, compare_checkbox, col_model2) \
+            .then(compare_prompt_fun, compare_checkbox, prompt_type2) \
+            .then(compare_textbox_fun, compare_checkbox, score_text2) \
+            .then(slider_fun, compare_checkbox, max_new_tokens2) \
+            .then(slider_fun, compare_checkbox, min_new_tokens2)
+        # FIXME: add score_res2 in condition, but do better
+
+        # callback for logging flagged input/output
+        callback.setup(inputs_list + [text_output, text_output2] + text_outputs, "flagged_data_points")
+        flag_btn.click(lambda *args: callback.flag(args), inputs_list + [text_output, text_output2] + text_outputs,
+                       None,
+                       preprocess=False,
+                       api_name='flag' if allow_api else None, queue=False)
+        flag_btn_nochat.click(lambda *args: callback.flag(args), inputs_list + [text_output_nochat], None,
+                              preprocess=False,
+                              api_name='flag_nochat' if allow_api else None, queue=False)
+
+        def get_system_info():
+            if is_public:
+                time.sleep(10)  # delay to avoid spam since queue=False
+            return gr.Textbox.update(value=system_info_print())
+
+        system_event = system_btn.click(get_system_info, outputs=system_text,
+                                        api_name='system_info' if allow_api else None, queue=False)
+
+        def get_system_info_dict(system_input1, **kwargs1):
+            if system_input1 != os.getenv("ADMIN_PASS", ""):
+                return json.dumps({})
+            exclude_list = ['admin_pass', 'examples']
+            sys_dict = {k: v for k, v in kwargs1.items() if
+                        isinstance(v, (str, int, bool, float)) and k not in exclude_list}
+            try:
+                sys_dict.update(system_info())
+            except Exception as e:
+                # protection
+                print("Exception: %s" % str(e), flush=True)
+            return json.dumps(sys_dict)
+
+        system_kwargs = all_kwargs.copy()
+        system_kwargs.update(dict(command=str(' '.join(sys.argv))))
+        get_system_info_dict_func = functools.partial(get_system_info_dict, **all_kwargs)
+
+        system_dict_event = system_btn2.click(get_system_info_dict_func,
+                                              inputs=system_input,
+                                              outputs=system_text2,
+                                              api_name='system_info_dict' if allow_api else None,
+                                              queue=False,  # queue to avoid spam
+                                              )
+
+        def get_hash():
+            return kwargs['git_hash']
+
+        system_event = system_btn3.click(get_hash,
+                                         outputs=system_text3,
+                                         api_name='system_hash' if allow_api else None,
+                                         queue=False,
+                                         )
+
+        def get_model_names():
+            key_list = ['base_model', 'prompt_type', 'prompt_dict'] + list(kwargs['other_model_state_defaults'].keys())
+            # don't want to expose backend inference server IP etc.
+            # key_list += ['inference_server']
+            return [{k: x[k] for k in key_list if k in x} for x in model_states]
+
+        models_list_event = system_btn4.click(get_model_names,
+                                              outputs=system_text4,
+                                              api_name='model_names' if allow_api else None,
+                                              queue=False,
+                                              )
+
+        def count_chat_tokens(model_state1, chat1, prompt_type1, prompt_dict1,
+                              system_prompt1, chat_conversation1,
+                              memory_restriction_level1=0,
+                              keep_sources_in_context1=False,
+                              ):
+            if model_state1 and not isinstance(model_state1['tokenizer'], str):
+                tokenizer = model_state1['tokenizer']
+            elif model_state0 and not isinstance(model_state0['tokenizer'], str):
+                tokenizer = model_state0['tokenizer']
+            else:
+                tokenizer = None
+            if tokenizer is not None:
+                langchain_mode1 = 'LLM'
+                add_chat_history_to_context1 = True
+                # fake user message to mimic bot()
+                chat1 = copy.deepcopy(chat1)
+                chat1 = chat1 + [['user_message1', None]]
+                model_max_length1 = tokenizer.model_max_length
+                context1 = history_to_context(chat1,
+                                              langchain_mode=langchain_mode1,
+                                              add_chat_history_to_context=add_chat_history_to_context1,
+                                              prompt_type=prompt_type1,
+                                              prompt_dict=prompt_dict1,
+                                              chat=True,
+                                              model_max_length=model_max_length1,
+                                              memory_restriction_level=memory_restriction_level1,
+                                              keep_sources_in_context=keep_sources_in_context1,
+                                              system_prompt=system_prompt1,
+                                              chat_conversation=chat_conversation1)
+                tokens = tokenizer(context1, return_tensors="pt")['input_ids']
+                if len(tokens.shape) == 1:
+                    return str(tokens.shape[0])
+                elif len(tokens.shape) == 2:
+                    return str(tokens.shape[1])
+                else:
+                    return "N/A"
+            else:
+                return "N/A"
+
+        count_chat_tokens_func = functools.partial(count_chat_tokens,
+                                                   memory_restriction_level1=memory_restriction_level,
+                                                   keep_sources_in_context1=kwargs['keep_sources_in_context'])
+        count_tokens_event = count_chat_tokens_btn.click(fn=count_chat_tokens_func,
+                                                         inputs=[model_state, text_output, prompt_type, prompt_dict,
+                                                                 system_prompt, chat_conversation],
+                                                         outputs=chat_token_count,
+                                                         api_name='count_tokens' if allow_api else None)
+
+        # don't pass text_output, don't want to clear output, just stop it
+        # cancel only stops outer generation, not inner generation or non-generation
+        stop_btn.click(lambda: None, None, None,
+                       cancels=submits1 + submits2 + submits3 + submits4 +
+                               [submit_event_nochat, submit_event_nochat2] +
+                               [eventdb1, eventdb2, eventdb3] +
+                               [eventdb7a, eventdb7, eventdb8a, eventdb8, eventdb9a, eventdb9, eventdb12a, eventdb12] +
+                               db_events +
+                               [eventdbloadla, eventdbloadlb] +
+                               [clear_event] +
+                               [submit_event_nochat_api, submit_event_nochat] +
+                               [load_model_event, load_model_event2] +
+                               [count_tokens_event]
+                       ,
+                       queue=False, api_name='stop' if allow_api else None).then(clear_torch_cache, queue=False)
+
+        if kwargs['auth'] is not None:
+            auth = authf
+            load_func = user_state_setup
+            load_inputs = [my_db_state, requests_state, login_btn, login_btn]
+            load_outputs = [my_db_state, requests_state, login_btn]
+        else:
+            auth = None
+            load_func, load_inputs, load_outputs = None, None, None
+
+        app_js = wrap_js_to_lambda(
+            len(load_inputs) if load_inputs else 0,
+            get_dark_js() if kwargs['dark'] else None,
+            get_heap_js(heap_app_id) if is_heap_analytics_enabled else None)
+
+        load_event = demo.load(fn=load_func, inputs=load_inputs, outputs=load_outputs, _js=app_js)
+
+        if load_func:
+            load_event2 = load_event.then(load_login_func,
+                                          inputs=login_inputs,
+                                          outputs=login_outputs)
+            if not kwargs['large_file_count_mode']:
+                load_event3 = load_event2.then(**get_sources_kwargs)
+                load_event4 = load_event3.then(fn=update_dropdown, inputs=docs_state, outputs=document_choice)
+                load_event5 = load_event4.then(**show_sources_kwargs)
+                load_event6 = load_event5.then(**get_viewable_sources_args)
+                load_event7 = load_event6.then(**viewable_kwargs)
+
+    demo.queue(concurrency_count=kwargs['concurrency_count'], api_open=kwargs['api_open'])
+    favicon_file = "h2o-logo.svg"
+    favicon_path = favicon_file
+    if not os.path.isfile(favicon_file):
+        print("favicon_path1=%s not found" % favicon_file, flush=True)
+        alt_path = os.path.dirname(os.path.abspath(__file__))
+        favicon_path = os.path.join(alt_path, favicon_file)
+        if not os.path.isfile(favicon_path):
+            print("favicon_path2: %s not found in %s" % (favicon_file, alt_path), flush=True)
+            alt_path = os.path.dirname(alt_path)
+            favicon_path = os.path.join(alt_path, favicon_file)
+            if not os.path.isfile(favicon_path):
+                print("favicon_path3: %s not found in %s" % (favicon_file, alt_path), flush=True)
+                favicon_path = None
+
+    if kwargs['prepare_offline_level'] > 0:
+        from src.prepare_offline import go_prepare_offline
+        go_prepare_offline(**locals())
+        return
+
+    scheduler = BackgroundScheduler()
+    scheduler.add_job(func=clear_torch_cache, trigger="interval", seconds=20)
+    if is_public and \
+            kwargs['base_model'] not in non_hf_types:
+        # FIXME: disable for gptj, langchain or gpt4all modify print itself
+        # FIXME: and any multi-threaded/async print will enter model output!
+        scheduler.add_job(func=ping, trigger="interval", seconds=60)
+    if is_public or os.getenv('PING_GPU'):
+        scheduler.add_job(func=ping_gpu, trigger="interval", seconds=60 * 10)
+    scheduler.start()
+
+    # import control
+    if kwargs['langchain_mode'] == 'Disabled' and \
+            os.environ.get("TEST_LANGCHAIN_IMPORT") and \
+            kwargs['base_model'] not in non_hf_types:
+        assert 'gpt_langchain' not in sys.modules, "Dev bug, import of langchain when should not have"
+        assert 'langchain' not in sys.modules, "Dev bug, import of langchain when should not have"
+
+    # set port in case GRADIO_SERVER_PORT was already set in prior main() call,
+    # gradio does not listen if change after import
+    # Keep None if not set so can find an open port above used ports
+    server_port = os.getenv('GRADIO_SERVER_PORT')
+    if server_port is not None:
+        server_port = int(server_port)
+
+    demo.launch(share=kwargs['share'],
+                server_name=kwargs['server_name'],
+                show_error=True,
+                server_port=server_port,
+                favicon_path=favicon_path,
+                prevent_thread_lock=True,
+                auth=auth,
+                auth_message=auth_message,
+                root_path=kwargs['root_path'])
+    if kwargs['verbose'] or not (kwargs['base_model'] in ['gptj', 'gpt4all_llama']):
+        print("Started Gradio Server and/or GUI: server_name: %s port: %s" % (kwargs['server_name'], server_port),
+              flush=True)
+    if kwargs['block_gradio_exit']:
+        demo.block_thread()
+
+
+def show_doc(db1s, selection_docs_state1, requests_state1,
+             langchain_mode1,
+             single_document_choice1,
+             view_raw_text_checkbox1,
+             text_context_list1,
+             dbs1=None,
+             load_db_if_exists1=None,
+             db_type1=None,
+             use_openai_embedding1=None,
+             hf_embedding_model1=None,
+             migrate_embedding_model_or_db1=None,
+             auto_migrate_db1=None,
+             verbose1=False,
+             get_userid_auth1=None,
+             max_raw_chunks=1000000,
+             api=False,
+             n_jobs=-1):
+    file = single_document_choice1
+    document_choice1 = [single_document_choice1]
+    content = None
+    db_documents = []
+    db_metadatas = []
+    if db_type1 in ['chroma', 'chroma_old']:
+        assert langchain_mode1 is not None
+        langchain_mode_paths = selection_docs_state1['langchain_mode_paths']
+        langchain_mode_types = selection_docs_state1['langchain_mode_types']
+        from src.gpt_langchain import set_userid, get_any_db, get_docs_and_meta
+        set_userid(db1s, requests_state1, get_userid_auth1)
+        top_k_docs = -1
+        db = get_any_db(db1s, langchain_mode1, langchain_mode_paths, langchain_mode_types,
+                        dbs=dbs1,
+                        load_db_if_exists=load_db_if_exists1,
+                        db_type=db_type1,
+                        use_openai_embedding=use_openai_embedding1,
+                        hf_embedding_model=hf_embedding_model1,
+                        migrate_embedding_model=migrate_embedding_model_or_db1,
+                        auto_migrate_db=auto_migrate_db1,
+                        for_sources_list=True,
+                        verbose=verbose1,
+                        n_jobs=n_jobs,
+                        )
+        query_action = False  # long chunks like would be used for summarize
+        # the below is as or filter, so will show doc or by chunk, unrestricted
+        from langchain.vectorstores import Chroma
+        if isinstance(db, Chroma):
+            # chroma >= 0.4
+            if view_raw_text_checkbox1:
+                one_filter = \
+                    [{"source": {"$eq": x}, "chunk_id": {"$gte": 0}} if query_action else {"source": {"$eq": x},
+                                                                                           "chunk_id": {
+                                                                                               "$gte": -1}}
+                     for x in document_choice1][0]
+            else:
+                one_filter = \
+                    [{"source": {"$eq": x}, "chunk_id": {"$gte": 0}} if query_action else {"source": {"$eq": x},
+                                                                                           "chunk_id": {
+                                                                                               "$eq": -1}}
+                     for x in document_choice1][0]
+            filter_kwargs = dict(filter={"$and": [dict(source=one_filter['source']),
+                                                  dict(chunk_id=one_filter['chunk_id'])]})
+        else:
+            # migration for chroma < 0.4
+            one_filter = \
+                [{"source": {"$eq": x}, "chunk_id": {"$gte": 0}} if query_action else {"source": {"$eq": x},
+                                                                                       "chunk_id": {
+                                                                                           "$eq": -1}}
+                 for x in document_choice1][0]
+            if view_raw_text_checkbox1:
+                # like or, full raw all chunk types
+                filter_kwargs = dict(filter=one_filter)
+            else:
+                filter_kwargs = dict(filter={"$and": [dict(source=one_filter['source']),
+                                                      dict(chunk_id=one_filter['chunk_id'])]})
+        db_documents, db_metadatas = get_docs_and_meta(db, top_k_docs, filter_kwargs=filter_kwargs,
+                                                       text_context_list=text_context_list1)
+        # order documents
+        from langchain.docstore.document import Document
+        docs_with_score = [(Document(page_content=result[0], metadata=result[1] or {}), 0)
+                           for result in zip(db_documents, db_metadatas)]
+        doc_chunk_ids = [x.get('chunk_id', -1) for x in db_metadatas]
+        doc_page_ids = [x.get('page', 0) for x in db_metadatas]
+        doc_hashes = [x.get('doc_hash', 'None') for x in db_metadatas]
+        docs_with_score = [x for hx, px, cx, x in
+                           sorted(zip(doc_hashes, doc_page_ids, doc_chunk_ids, docs_with_score),
+                                  key=lambda x: (x[0], x[1], x[2]))
+                           # if cx == -1
+                           ]
+        db_metadatas = [x[0].metadata for x in docs_with_score][:max_raw_chunks]
+        db_documents = [x[0].page_content for x in docs_with_score][:max_raw_chunks]
+        # done reordering
+        if view_raw_text_checkbox1:
+            content = [dict_to_html(x) + '\n' + text_to_html(y) for x, y in zip(db_metadatas, db_documents)]
+        else:
+            content = [text_to_html(y) for x, y in zip(db_metadatas, db_documents)]
+        content = '\n'.join(content)
+        content = f"""<!DOCTYPE html>
+<html>
+<head>
+<title>{file}</title>
+</head>
+<body>
+{content}
+</body>
+</html>"""
+    if api:
+        if view_raw_text_checkbox1:
+            return dict(contents=db_documents, metadatas=db_metadatas)
+        else:
+            contents = [text_to_html(y, api=api) for y in db_documents]
+            metadatas = [dict_to_html(x, api=api) for x in db_metadatas]
+            return dict(contents=contents, metadatas=metadatas)
+    else:
+        assert not api, "API mode for get_document only supported for chroma"
+
+    dummy1 = gr.update(visible=False, value=None)
+    # backup is text dump of db version
+    if content:
+        dummy_ret = dummy1, dummy1, dummy1, dummy1, gr.update(visible=True, value=content)
+        if view_raw_text_checkbox1:
+            return dummy_ret
+    else:
+        dummy_ret = dummy1, dummy1, dummy1, dummy1, dummy1
+
+    if not isinstance(file, str):
+        return dummy_ret
+
+    if file.lower().endswith('.html') or file.lower().endswith('.mhtml') or file.lower().endswith('.htm') or \
+            file.lower().endswith('.xml'):
+        try:
+            with open(file, 'rt') as f:
+                content = f.read()
+            return gr.update(visible=True, value=content), dummy1, dummy1, dummy1, dummy1
+        except:
+            return dummy_ret
+
+    if file.lower().endswith('.md'):
+        try:
+            with open(file, 'rt') as f:
+                content = f.read()
+            return dummy1, dummy1, dummy1, gr.update(visible=True, value=content), dummy1
+        except:
+            return dummy_ret
+
+    if file.lower().endswith('.py'):
+        try:
+            with open(file, 'rt') as f:
+                content = f.read()
+            content = f"```python\n{content}\n```"
+            return dummy1, dummy1, dummy1, gr.update(visible=True, value=content), dummy1
+        except:
+            return dummy_ret
+
+    if file.lower().endswith('.txt') or file.lower().endswith('.rst') or file.lower().endswith(
+            '.rtf') or file.lower().endswith('.toml'):
+        try:
+            with open(file, 'rt') as f:
+                content = f.read()
+            content = f"```text\n{content}\n```"
+            return dummy1, dummy1, dummy1, gr.update(visible=True, value=content), dummy1
+        except:
+            return dummy_ret
+
+    func = None
+    if file.lower().endswith(".csv"):
+        func = pd.read_csv
+    elif file.lower().endswith(".pickle"):
+        func = pd.read_pickle
+    elif file.lower().endswith(".xls") or file.lower().endswith("xlsx"):
+        func = pd.read_excel
+    elif file.lower().endswith('.json'):
+        func = pd.read_json
+    # pandas doesn't show full thing, even if html view shows broken things still better
+    # elif file.lower().endswith('.xml'):
+    #    func = pd.read_xml
+    if func is not None:
+        try:
+            df = func(file).head(100)
+        except:
+            return dummy_ret
+        return dummy1, gr.update(visible=True, value=df), dummy1, dummy1, dummy1
+    port = int(os.getenv('GRADIO_SERVER_PORT', '7860'))
+    import pathlib
+    absolute_path_string = os.path.abspath(file)
+    url_path = pathlib.Path(absolute_path_string).as_uri()
+    url = get_url(absolute_path_string, from_str=True)
+    img_url = url.replace("""<a href=""", """<img src=""")
+    if file.lower().endswith('.png') or file.lower().endswith('.jpg') or file.lower().endswith('.jpeg'):
+        return gr.update(visible=True, value=img_url), dummy1, dummy1, dummy1, dummy1
+    elif file.lower().endswith('.pdf') or 'arxiv.org/pdf' in file:
+
+        # account for when use `wget -b -m -k -o wget.log -e robots=off`
+        if url_alive('http://' + file):
+            file = 'http://' + file
+        if url_alive('https://' + file):
+            file = 'https://' + file
+
+        if file.lower().startswith('http') or file.lower().startswith('https'):
+            # if file is online, then might as well use google(?)
+            document1 = file
+            return gr.update(visible=True,
+                             value=f"""<iframe width="1000" height="800" src="https://docs.google.com/viewerng/viewer?url={document1}&embedded=true" frameborder="0" height="100%" width="100%">
+</iframe>
+"""), dummy1, dummy1, dummy1, dummy1
+        else:
+            # FIXME: This doesn't work yet, just return dummy result for now
+            if False:
+                ip = get_local_ip()
+                document1 = url_path.replace('file://', f'http://{ip}:{port}/')
+                # document1 = url
+                return gr.update(visible=True, value=f"""<object data="{document1}" type="application/pdf">
+<iframe src="https://docs.google.com/viewer?url={document1}&embedded=true"></iframe>
+</object>"""), dummy1, dummy1, dummy1, dummy1
+            else:
+                return dummy_ret
+    else:
+        return dummy_ret
+
+
+def get_inputs_list(inputs_dict, model_lower, model_id=1):
+    """
+    map gradio objects in locals() to inputs for evaluate().
+    :param inputs_dict:
+    :param model_lower:
+    :param model_id: Which model (1 or 2) of 2
+    :return:
+    """
+    inputs_list_names = list(inspect.signature(evaluate).parameters)
+    inputs_list = []
+    inputs_dict_out = {}
+    for k in inputs_list_names:
+        if k == 'kwargs':
+            continue
+        if k in input_args_list + inputs_kwargs_list:
+            # these are added at use time for args or partial for kwargs, not taken as input
+            continue
+        if 'mbart-' not in model_lower and k in ['src_lang', 'tgt_lang']:
+            continue
+        if model_id == 2:
+            if k == 'prompt_type':
+                k = 'prompt_type2'
+            if k == 'prompt_used':
+                k = 'prompt_used2'
+            if k == 'max_new_tokens':
+                k = 'max_new_tokens2'
+            if k == 'min_new_tokens':
+                k = 'min_new_tokens2'
+        inputs_list.append(inputs_dict[k])
+        inputs_dict_out[k] = inputs_dict[k]
+    return inputs_list, inputs_dict_out
+
+
+def update_user_db_gr(file, db1s, selection_docs_state1, requests_state1,
+                      langchain_mode, chunk, chunk_size, embed,
+
+                      image_loaders,
+                      pdf_loaders,
+                      url_loaders,
+                      jq_schema,
+                      h2ogpt_key,
+
+                      captions_model=None,
+                      caption_loader=None,
+                      doctr_loader=None,
+
+                      dbs=None,
+                      get_userid_auth=None,
+                      **kwargs):
+    valid_key = is_valid_key(kwargs.pop('enforce_h2ogpt_api_key', None),
+                             kwargs.pop('h2ogpt_api_keys', []), h2ogpt_key,
+                             requests_state1=requests_state1)
+    if not valid_key:
+        raise ValueError(invalid_key_msg)
+    loaders_dict, captions_model = gr_to_lg(image_loaders,
+                                            pdf_loaders,
+                                            url_loaders,
+                                            captions_model=captions_model,
+                                            **kwargs,
+                                            )
+    if jq_schema is None:
+        jq_schema = kwargs['jq_schema0']
+    loaders_dict.update(dict(captions_model=captions_model,
+                             caption_loader=caption_loader,
+                             doctr_loader=doctr_loader,
+                             jq_schema=jq_schema,
+                             ))
+    kwargs.pop('image_loaders_options0', None)
+    kwargs.pop('pdf_loaders_options0', None)
+    kwargs.pop('url_loaders_options0', None)
+    kwargs.pop('jq_schema0', None)
+    if not embed:
+        kwargs['use_openai_embedding'] = False
+        kwargs['hf_embedding_model'] = 'fake'
+        kwargs['migrate_embedding_model'] = False
+
+    from src.gpt_langchain import update_user_db
+    return update_user_db(file, db1s, selection_docs_state1, requests_state1,
+                          langchain_mode=langchain_mode, chunk=chunk, chunk_size=chunk_size,
+                          **loaders_dict,
+                          dbs=dbs,
+                          get_userid_auth=get_userid_auth,
+                          **kwargs)
+
+
+def get_sources_gr(db1s, selection_docs_state1, requests_state1, langchain_mode, dbs=None, docs_state0=None,
+                   load_db_if_exists=None,
+                   db_type=None,
+                   use_openai_embedding=None,
+                   hf_embedding_model=None,
+                   migrate_embedding_model=None,
+                   auto_migrate_db=None,
+                   verbose=False,
+                   get_userid_auth=None,
+                   api=False,
+                   n_jobs=-1):
+    from src.gpt_langchain import get_sources
+    sources_file, source_list, num_chunks, db = \
+        get_sources(db1s, selection_docs_state1, requests_state1, langchain_mode,
+                    dbs=dbs, docs_state0=docs_state0,
+                    load_db_if_exists=load_db_if_exists,
+                    db_type=db_type,
+                    use_openai_embedding=use_openai_embedding,
+                    hf_embedding_model=hf_embedding_model,
+                    migrate_embedding_model=migrate_embedding_model,
+                    auto_migrate_db=auto_migrate_db,
+                    verbose=verbose,
+                    get_userid_auth=get_userid_auth,
+                    n_jobs=n_jobs,
+                    )
+    if api:
+        return source_list
+    if langchain_mode in langchain_modes_non_db:
+        doc_counts_str = "LLM Mode\nNo Collection"
+    else:
+        doc_counts_str = "Collection: %s\nDocs: %d\nChunks: %d" % (langchain_mode, len(source_list), num_chunks)
+    return sources_file, source_list, doc_counts_str
+
+
+def get_source_files_given_langchain_mode_gr(db1s, selection_docs_state1, requests_state1,
+                                             langchain_mode,
+                                             dbs=None,
+                                             load_db_if_exists=None,
+                                             db_type=None,
+                                             use_openai_embedding=None,
+                                             hf_embedding_model=None,
+                                             migrate_embedding_model=None,
+                                             auto_migrate_db=None,
+                                             verbose=False,
+                                             get_userid_auth=None,
+                                             n_jobs=-1):
+    from src.gpt_langchain import get_source_files_given_langchain_mode
+    return get_source_files_given_langchain_mode(db1s, selection_docs_state1, requests_state1, None,
+                                                 langchain_mode,
+                                                 dbs=dbs,
+                                                 load_db_if_exists=load_db_if_exists,
+                                                 db_type=db_type,
+                                                 use_openai_embedding=use_openai_embedding,
+                                                 hf_embedding_model=hf_embedding_model,
+                                                 migrate_embedding_model=migrate_embedding_model,
+                                                 auto_migrate_db=auto_migrate_db,
+                                                 verbose=verbose,
+                                                 get_userid_auth=get_userid_auth,
+                                                 delete_sources=False,
+                                                 n_jobs=n_jobs)
+
+
+def del_source_files_given_langchain_mode_gr(db1s, selection_docs_state1, requests_state1, document_choice1,
+                                             langchain_mode,
+                                             dbs=None,
+                                             load_db_if_exists=None,
+                                             db_type=None,
+                                             use_openai_embedding=None,
+                                             hf_embedding_model=None,
+                                             migrate_embedding_model=None,
+                                             auto_migrate_db=None,
+                                             verbose=False,
+                                             get_userid_auth=None,
+                                             n_jobs=-1):
+    from src.gpt_langchain import get_source_files_given_langchain_mode
+    return get_source_files_given_langchain_mode(db1s, selection_docs_state1, requests_state1, document_choice1,
+                                                 langchain_mode,
+                                                 dbs=dbs,
+                                                 load_db_if_exists=load_db_if_exists,
+                                                 db_type=db_type,
+                                                 use_openai_embedding=use_openai_embedding,
+                                                 hf_embedding_model=hf_embedding_model,
+                                                 migrate_embedding_model=migrate_embedding_model,
+                                                 auto_migrate_db=auto_migrate_db,
+                                                 verbose=verbose,
+                                                 get_userid_auth=get_userid_auth,
+                                                 delete_sources=True,
+                                                 n_jobs=n_jobs)
+
+
+def update_and_get_source_files_given_langchain_mode_gr(db1s,
+                                                        selection_docs_state,
+                                                        requests_state,
+                                                        langchain_mode, chunk, chunk_size,
+
+                                                        image_loaders,
+                                                        pdf_loaders,
+                                                        url_loaders,
+                                                        jq_schema,
+
+                                                        captions_model=None,
+                                                        caption_loader=None,
+                                                        doctr_loader=None,
+
+                                                        dbs=None, first_para=None,
+                                                        hf_embedding_model=None,
+                                                        use_openai_embedding=None,
+                                                        migrate_embedding_model=None,
+                                                        auto_migrate_db=None,
+                                                        text_limit=None,
+                                                        db_type=None, load_db_if_exists=None,
+                                                        n_jobs=None, verbose=None, get_userid_auth=None,
+                                                        image_loaders_options0=None,
+                                                        pdf_loaders_options0=None,
+                                                        url_loaders_options0=None,
+                                                        jq_schema0=None):
+    from src.gpt_langchain import update_and_get_source_files_given_langchain_mode
+
+    loaders_dict, captions_model = gr_to_lg(image_loaders,
+                                            pdf_loaders,
+                                            url_loaders,
+                                            image_loaders_options0=image_loaders_options0,
+                                            pdf_loaders_options0=pdf_loaders_options0,
+                                            url_loaders_options0=url_loaders_options0,
+                                            captions_model=captions_model,
+                                            )
+    if jq_schema is None:
+        jq_schema = jq_schema0
+    loaders_dict.update(dict(captions_model=captions_model,
+                             caption_loader=caption_loader,
+                             doctr_loader=doctr_loader,
+                             jq_schema=jq_schema,
+                             ))
+
+    return update_and_get_source_files_given_langchain_mode(db1s,
+                                                            selection_docs_state,
+                                                            requests_state,
+                                                            langchain_mode, chunk, chunk_size,
+                                                            **loaders_dict,
+                                                            dbs=dbs, first_para=first_para,
+                                                            hf_embedding_model=hf_embedding_model,
+                                                            use_openai_embedding=use_openai_embedding,
+                                                            migrate_embedding_model=migrate_embedding_model,
+                                                            auto_migrate_db=auto_migrate_db,
+                                                            text_limit=text_limit,
+                                                            db_type=db_type, load_db_if_exists=load_db_if_exists,
+                                                            n_jobs=n_jobs, verbose=verbose,
+                                                            get_userid_auth=get_userid_auth)
+
+
+def set_userid_gr(db1s, requests_state1, get_userid_auth):
+    from src.gpt_langchain import set_userid
+    return set_userid(db1s, requests_state1, get_userid_auth)
+
+
+def set_dbid_gr(db1):
+    from src.gpt_langchain import set_dbid
+    return set_dbid(db1)
+
+
+def set_userid_direct_gr(db1s, userid, username):
+    from src.gpt_langchain import set_userid_direct
+    return set_userid_direct(db1s, userid, username)