Compare commits

..

5 Commits

Author SHA1 Message Date
hagen-danswer
e2aaa60e77 done 2024-11-21 17:26:18 -08:00
hagen-danswer
31f4a68bee less passing around is_cloud 2024-11-21 16:58:31 -08:00
hagen-danswer
4fc196fc39 properly escaped the user query 2024-11-21 16:37:48 -08:00
hagen-danswer
95ab63b6bc reworked it 2024-11-21 16:35:08 -08:00
hagen-danswer
6d26d0b929 replace deprecated confluence group api endpoint 2024-11-21 12:37:16 -08:00
92 changed files with 2197 additions and 3988 deletions

View File

@@ -1,59 +0,0 @@
"""display custom llm models
Revision ID: 177de57c21c9
Revises: 4ee1287bd26a
Create Date: 2024-11-21 11:49:04.488677
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from sqlalchemy import and_
revision = "177de57c21c9"
down_revision = "4ee1287bd26a"
branch_labels = None
depends_on = None
depends_on = None
def upgrade() -> None:
conn = op.get_bind()
llm_provider = sa.table(
"llm_provider",
sa.column("id", sa.Integer),
sa.column("provider", sa.String),
sa.column("model_names", postgresql.ARRAY(sa.String)),
sa.column("display_model_names", postgresql.ARRAY(sa.String)),
)
excluded_providers = ["openai", "bedrock", "anthropic", "azure"]
providers_to_update = sa.select(
llm_provider.c.id,
llm_provider.c.model_names,
llm_provider.c.display_model_names,
).where(
and_(
~llm_provider.c.provider.in_(excluded_providers),
llm_provider.c.model_names.isnot(None),
)
)
results = conn.execute(providers_to_update).fetchall()
for provider_id, model_names, display_model_names in results:
if display_model_names is None:
display_model_names = []
combined_model_names = list(set(display_model_names + model_names))
update_stmt = (
llm_provider.update()
.where(llm_provider.c.id == provider_id)
.values(display_model_names=combined_model_names)
)
conn.execute(update_stmt)
def downgrade() -> None:
pass

View File

@@ -1,29 +0,0 @@
"""add auto scroll to user model
Revision ID: a8c2065484e6
Revises: 177de57c21c9
Create Date: 2024-11-22 17:34:09.690295
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "a8c2065484e6"
down_revision = "177de57c21c9"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Add the auto_scroll column with a default value of True
op.add_column(
"user",
sa.Column("auto_scroll", sa.Boolean(), nullable=True, server_default=None),
)
def downgrade() -> None:
# Remove the auto_scroll column
op.drop_column("user", "auto_scroll")

View File

@@ -1,551 +0,0 @@
Branch,Commit Hash,Author,Date,Subject
DAN-108,548c081fd6515c2e8b912d145c135e292db4613e,pablodanswer,2024-11-20,k
DAN-108,0d4abfdc85fdb62c347d0f649744f1b7c12e8011,pablodanswer,2024-11-20,folder clarity
a,36eee45a03c3227a9b070e18a043e16fe5179cb9,pablodanswer,2024-11-21,llm provider causing re render in effect
account_for_json,b37d0b91e6a6596af91e1fa32786591b76e05a67,pablodanswer,2024-11-14,fix single quote block in llm answer
account_for_json,4e0c048acba88f4c83d7c83af52bb0932234ddad,pablodanswer,2024-11-14,nit
account_for_json,a0371a6750476fccc3b9892a7c58d72182c92507,pablodanswer,2024-11-14,minor logic update
account_for_json,4f1c4baa80f7b747633bb3d528aed6de5b11f639,pablodanswer,2024-11-14,minor cosmetic update
account_for_json,b6ef7e713a4eca3d65aa411604e8f67ad5efdd87,pablodanswer,2024-11-14,k
account_for_json,66df9b6f7dae8bce61e35615d715ddefc6406614,pablodanswer,2024-11-14,improved fallback logic
account_for_json,0473888ccdb5219cc39f275652bfeb72a420b5d9,pablodanswer,2024-11-13,silence warning
accurate_user_counting,06f3a4590c05665b04851b30860aa431ad4b7217,pablodanswer,2024-11-02,ensure we remove users in time
accurate_user_counting,6e75ba007302ce9adc4469b86695aee4b4b5c513,pablodanswer,2024-11-02,validate
accurate_user_counting,11f3729ebb9f67b8e568c01a9ce1d098560033cf,pablodanswer,2024-11-02,update register
add_csv_display,e7b044cf38cd3e25fdbe17ea8fcac3e8c17d9570,pablodanswer,2024-11-03,nit
add_csv_display,93ec944a01ec87d87a4bf2b85c1164b7625a1259,pablodanswer,2024-11-02,update requirements
add_csv_display,00f8e431ff81d7980c8d2c166bdad5f899752379,pablodanswer,2024-11-02,create portal for modal
add_csv_display,a019a812bef27a20bd2e94d558974c55ded63035,pablodanswer,2024-11-02,restructure
add_csv_display,eabc519f062b5e0fec3b2c29e89f109606e747bc,pablodanswer,2024-11-01,add downloading
add_csv_display,4dbd74cacb350ebbf5ce0554239f999503a14d8f,pablodanswer,2024-11-01,add CSV display
add_tool_formats,e7361dcb17a1d205627e46c87861f5be4dc06a03,pablodanswer,2024-11-03,add multiple formats to tools
add_tool_formats,00f8e431ff81d7980c8d2c166bdad5f899752379,pablodanswer,2024-11-02,create portal for modal
add_tool_formats,a019a812bef27a20bd2e94d558974c55ded63035,pablodanswer,2024-11-02,restructure
add_tool_formats,eabc519f062b5e0fec3b2c29e89f109606e747bc,pablodanswer,2024-11-01,add downloading
add_tool_formats,4dbd74cacb350ebbf5ce0554239f999503a14d8f,pablodanswer,2024-11-01,add CSV display
admin_wonkiness,8a7f032acb35fca9260f1f15e48a6114279a1dc0,pablodanswer,2024-11-20,valid props
api_keys_are_not_users,39c3e3f84b56f2b1d661f723fe9650503d8602ad,pablodanswer,2024-11-01,typing
api_keys_are_not_users,cab9c925cc09b636e026f36057795a775d6a8289,pablodanswer,2024-11-01,don't count api keys as users
assistant_categories,425da2250c6cade36e9dfe4aa9eaca9f60ad7c1f,pablodanswer,2024-11-18,alembic (once again)
assistant_categories,c079165c60d58d781bb399220f0041a57dd27cde,pablodanswer,2024-11-18,alembic
assistant_categories,dc5f9e5aa2fbf1a502474bc56cbe9a5eaa34ed91,pablodanswer,2024-11-11,nit
assistant_categories,7ed84cf536aa5be737f4eff25e244def9987cfb3,pablodanswer,2024-11-11,typing
assistant_categories,30a58ad86d96f841103f9bf5ef92355ba7550e72,pablodanswer,2024-11-11,finalize
assistant_categories,4c5d0a45fd07dffa42717c78f4b20025ca7c67ad,pablodanswer,2024-11-11,update typing
assistant_categories,ed7c62b450dd1b42a8e399c8abcaac8ccb006b1d,pablodanswer,2024-11-11,minor update to tests
assistant_categories,501c6afdd0a8e4c67ee8ae864392549a19f68b85,pablodanswer,2024-11-11,post rebase update
assistant_categories,8cd7e50b26d8ac5d5311c1ffc4517c35c2a9a6b6,pablodanswer,2024-11-08,add tests
assistant_categories,ca0eb6f03344cf833b2aba45c5fbe4d01a112c6f,pablodanswer,2024-11-07,nit
assistant_categories,2041484a515ebaedaf05dc0e19e3cb5095b34018,pablodanswer,2024-11-07,update assistant category display
assistant_categories,a124d4e2229bcb9a9f1caf269c444357e4749700,pablodanswer,2024-11-07,finalize
assistant_categories,59fa1d07f10b7f44010207d54547b947ca789fe1,pablodanswer,2024-11-05,functionality finalized
assistant_categories,0a226b47e55dc6767dde8f478729616d1b4870f1,pablodanswer,2024-11-05,add assistant categories v1
assistant_clarity,71c60c52dd37ccebd2d4f8862676d5f21a64acf1,pablodanswer,2024-11-12,minor update
assistant_clarity,72f05a13485dab5a8ddd0d0e5ac7d4e98aed01a2,pablodanswer,2024-11-12,delete code
assistant_clarity,0c22f8ab20c32043c9e1f5f991989a07ecbd6387,pablodanswer,2024-11-12,delete code!
assistant_clarity,e376032f14621d645fda23f058b5712c33224e82,pablodanswer,2024-11-12,update paradigm
assistant_clarity,3f2738006951ffcf58ea59473da3070e8023a9d0,pablodanswer,2024-11-12,alembic fix
assistant_clarity,233f186fecb9eba7eefd6aa493ce70b299f68ac6,pablodanswer,2024-11-12,slight rejigger
assistant_clarity,0582306d9be29f7c3daff7b7d5a2c1ef1517e033,pablodanswer,2024-11-12,k
assistant_clarity,4f699b2591fe190abf1d68fefb3f2841c0f7f68e,pablodanswer,2024-11-12,add minor clarity
assistant_clarity,bc6d47a6c5702d102cc04c16e56426a1561fe3e5,pablodanswer,2024-11-12,minor clean up
assistant_clarity,09ec137a5f6fb230a0c39a67b19e9f772d3441ca,pablodanswer,2024-11-12,update organization
auth_categories,f51d87833e591bdcb9a650aa762060387a96a292,pablodanswer,2024-11-07,nit
auth_categories,01f93bab2f698bb0dc84bddb705de40a9a18e660,pablodanswer,2024-11-07,update assistant category display
auth_categories,b162e9f4c4c9ff4b9cd718f548cc20ab0e60be0f,pablodanswer,2024-11-07,finalize
auth_categories,c7097dffbd73e1b2d9b34ad67bbd8aa6e072c3b5,pablodanswer,2024-11-05,functionality finalized
auth_categories,653bbffb3cda5cbc41f61917e5634e22d70d5e26,pablodanswer,2024-11-05,add assistant categories v1
auto_prompts,06bc8f1f92e33af2c6bb1750936407ad8e29d3c0,pablodanswer,2024-10-28,base functionality
auto_prompts,8093ceeb45088c813fbb117302738b3d225c2f8b,pablodanswer,2024-10-28,formatting
auto_prompts,3d0ace1e450ac6d7271ddedc2ec122a2647be7df,pablodanswer,2024-10-28,minor nits
auto_prompts,553aba79dc41b928c163a83481b202ad56805aae,pablodanswer,2024-10-28,update based on feedback
auto_prompts,da038b317a0b5185ccc32297b01fcaa97ffbb429,pablodanswer,2024-09-21,remove logs
auto_prompts,6769dc373faf7576c2d0ac212735b88eae755293,pablodanswer,2024-09-21,minor udpate to ui
auto_prompts,b35e05315c4c506da87524fe788a9cf5aacb7375,pablodanswer,2024-09-20,use display name + minor updates to models
auto_prompts,7cfd3d2d442255616ec5c477dc4b3eb0b2cad1ed,pablodanswer,2024-09-20,cleaner cards
auto_prompts,b2aa1c864b20274386a1bbe699a3ef7e094bd858,pablodanswer,2024-09-20,slightly cleaner animation
auto_prompts,d2f8177b8f1b9be8eebce520204018e6be59b03c,pablodanswer,2024-09-20,cleaner initial chat screen
back_to_danswer,262a405195e1b1b07c96e1ae4a39df76b690ed69,pablodanswer,2024-11-06,update redirect
beat_robustification,63959454df29709c149b71f82672c8752c646cfa,pablodanswer,2024-11-03,Remove locks (#3017)
beat_robustification,96027f1d732f26b407afd2b52641615a96d5402b,pablodanswer,2024-11-02,ensure versioned apps capture
beat_robustification,80ea6a36610775a0e57ec236f9a2bdaf419a51e5,pablodanswer,2024-11-01,typing
beat_robustification,527c409f81a7d31c8ff6ebd2be465418476eba74,pablodanswer,2024-11-01,update
beat_robustification,19ab457d926a05a0d61ada33684918a5d427e619,pablodanswer,2024-11-01,address comments
beat_robustification,f5b38cd9362b4c7b84357a6fcf2bbeb4c1e7c8a8,pablodanswer,2024-10-30,nit
beat_robustification,63d1cc56acdeba0430d5da9f8b752cd470df865f,pablodanswer,2024-10-30,reorg
beat_robustification,4436bec97019893c256ee1750e28e3061edfd771,pablodanswer,2024-10-30,validate
beat_robustification,90b7198d53ec8b383051925de16a2818653c4fe3,pablodanswer,2024-10-30,add validated + reformatted dynamic beat acquisition
better_image_assistant_prompt,e9abbcdefdf21eef2000fc61342e4129bfd1498f,pablodanswer,2024-11-03,nit
better_image_assistant_prompt,89f51078690bed44b2809aa5229f39b4d543d88e,pablodanswer,2024-11-02,k
better_image_assistant_prompt,6972874aac31dcccd4ff739484b6a5b563e62405,pablodanswer,2024-11-02,slight upgrade to prompts
bg_processing_improvements,48d24860e6f5401a265951b8e49e900ed6e40f63,pablodanswer,2024-11-03,improvements
branding_update,12bbf2ad972a1f8887e5f5eb427b88261ef5097c,pablodanswer,2024-10-28,add additional configuration options
bugfix/async,8b9e1a07d55b3f090d168768a74d09d60ba19649,pablodanswer,2024-11-11,typing
bugfix/async,b6301ffcb9bb35f6d73c28ffd502bfb01f49272a,pablodanswer,2024-11-11,spacing
bugfix/async,490ce0db18df25625446a4abe163790b96431645,pablodanswer,2024-11-11,cleaner approach
bugfix/async,b2ca13eaae905af768519a62a38d3d84c239cba8,pablodanswer,2024-11-11,treat async values differently
bugfix/curator_interface,a7312f62366cff5243e4b85c5c47e33e5da29f5c,pablodanswer,2024-11-21,remove values
bugfix/curator_interface,85e08df5219f0e2e793beb65a1ce4dc36f2481d4,pablodanswer,2024-11-21,update user role
bugfix/curator_interface,937a07d705a8620f47336c1c6c125ae6b025a950,pablodanswer,2024-11-21,update
bugfix/curator_interface,1130d456aaa6ea38aeeacd234ab82504e3c5fc68,pablodanswer,2024-11-21,update
bugfix/curator_interface,cf4cda235ce02bfdea1f1cd17ad4f6a2e0f7f9f7,pablodanswer,2024-11-21,update config
bugfix/curator_interface,5a07f727c0563061398f50ed253f1efc2f83c176,pablodanswer,2024-11-21,mystery solved
bugfix/index_attempt_logging_2,209514815547074a31b3121bf47e7b1e350e817d,Richard Kuo (Danswer),2024-11-21,Move unfenced check to check_for_indexing. implement a double check pattern for all indexing error checks
bugfix/indexing_redux,0c068c47c2cb729a0450910f0f6b6d04b340b131,Richard Kuo (Danswer),2024-11-17,Merge branch 'main' of https://github.com/danswer-ai/danswer into bugfix/indexing_redux
bugfix/indexing_redux,1dfde97a5a52a8c4c3996d14348e9fffe6073743,Richard Kuo (Danswer),2024-11-14,refactor unknown index attempts and redis lock
bugfix/indexing_redux,5d95976bf1bc13caaa21655777e8e84efb682cd2,Richard Kuo (Danswer),2024-11-14,raise indexing lock timeout
bugfix/pagination,1a009c6b6a3d52302e5bbdec20c75ce15a678f5c,pablodanswer,2024-11-07,minor update
bugfix/pagination,e8cd2630e2bee96496b30f637a169df863e11495,pablodanswer,2024-11-06,minor update
bugfix/pagination,d835de1f5219248f164221464b257b5a44c6ed8f,pablodanswer,2024-11-06,fixed query history
bugfix/pagination,c6d35a8ad6be86c28ba8d3645d171d22390cc9fa,pablodanswer,2024-11-06,update side
bugfix/pagination,a5641e5a5e001dc3a4740bfcdd53c9fafb64c20a,pablodanswer,2024-11-06,fix pagination
bugfix/pruning,c27308c812f536a5e7410a73b0940f63330fb3fb,pablodanswer,2024-10-30,clarity
calendar_clarity,7edb205a6837d0328062ecbb9a9318dd6e27f9d5,pablodanswer,2024-11-22,minor calendar cleanup
callout_clarity,a8787b7be8e66d06edeaa997390ca118d1abaaac,pablodanswer,2024-11-04,k
callout_clarity,585e6b7b2fec35e17f91d55354c48631cb773ca7,pablodanswer,2024-11-04,k
callout_clarity,bdbfb62946b644ddf011a2e03a1a9b2158899f36,pablodanswer,2024-11-04,ensure props aligned
cascade_search,9c975d829d0b67d245da18e905781c22578f413f,pablodanswer,2024-10-30,minor foreign key update
clean-jira-pr,1eec84a6693add96e571eca96cf181bd32ab42f4,hagen-danswer,2024-11-20,cleanup
clean-jira-pr,658951f66dfe2cb97e20f590f71f46bcb8b1f1ef,hagen-danswer,2024-11-20,more cleanup of Jira connector
clean-jira-pr,da153ef5179592cfa11f9ce271c187739e242432,hagen-danswer,2024-11-20,fixed testing
clean-jira-pr,82118e0837d486e8d66fb7eb26d523c4fa79f8a2,hagen-danswer,2024-11-20,Added Slim connector for Jira
cloud_auth,bcce7733aa5bb2f3af2842d8e9938af6c5597c9c,pablodanswer,2024-11-11,typing
cloud_auth,eeeb84c66bf1d5aefd16ad20f9727a61b2ddc5f3,pablodanswer,2024-11-11,minor modification to be best practice
cloud_auth,a7b13762264b67ac720db21552c3a6c0f42e7c9d,pablodanswer,2024-11-11,k
cloud_auth,1c020d11c4d4257732a7fca17eecbde979e42804,pablodanswer,2024-11-11,minor clarity
cloud_auth,cb6fad26b8ec9f77a7bc82a94da8e6748bbc20f0,pablodanswer,2024-11-11,cloud auth referral source
cohere,444ad36c0801810fadfcc4a0c1f355004f59e317,pablodanswer,2024-11-13,config
cohere,227faf87c690ef9b30fbe79b1582ad36a4ec95b2,pablodanswer,2024-11-11,update config
cohere,1bf33a6b7ae5fc84a779c3c6d9d8c514523b5af9,pablodanswer,2024-11-11,ensure we properly expose name(space) for slackbot
cohere,15bd1d0ca6461ba7a9a1d2f468aea5f981e8750e,pablodanswer,2024-11-11,update configs
cohere,ce48d189aa6f9f83a6a62b353ea04bd16659d0e2,pablodanswer,2024-11-11,update
cohere,43b82e50cfdf9a1a260bde312a7e7e4f2929425b,pablodanswer,2024-11-11,update
cohere,1d06787e1d5734c25e703ba4f4b2d7df6c8bac01,pablodanswer,2024-11-11,minor improvement
cohere,8386d30f9230565136d2133b7c5cbcb623980761,pablodanswer,2024-11-11,finalize
cohere,374e51221881fcd722876efa9f53080342f3dcbd,pablodanswer,2024-11-10,add cohere default
cohere_default,8f67dc310fa1177430b8a47cfa685b4de4af105c,pablodanswer,2024-11-11,update
cohere_default,ad7d18968075a932a4539ac37d5432fa99fe99f4,pablodanswer,2024-11-11,minor improvement
cohere_default,72730a5ba3cef93523bfba9ee63994e5a1c0d63f,pablodanswer,2024-11-11,finalize
cohere_default,df8bd6daf46c1fce951efb50aaeff5e7cbc4b74a,pablodanswer,2024-11-10,add cohere default
cohere_default,6b78ab0a99bb5727df35c1dfc23c5e39008211ae,pablodanswer,2024-11-11,Cleaner EE fallback for no op (#3106)
cohere_default,e97bf1d4e28bcbf32080c3a339d0e2ac3d6d0253,Chris Weaver,2024-11-11,New assistants api (#3097)
cohere_default,293dbfb8eb7b3ac4d2878b7a72068b829b9e3469,rkuo-danswer,2024-11-09,re-enable helm (#3053)
cohere_default,f4a61202a7b6de8a011d67896b16e14f94eb981a,pablodanswer,2024-11-09,Silence auth logs (#3098)
cohere_default,53f9d94ceb7a6a8da2a0c2d94fee6971adb29bbf,pablodanswer,2024-11-11,revert
cohere_default,5058d898b8532881c517e14c22ca5c32784288fe,pablodanswer,2024-11-11,update some configs
cohere_default,bc7de4ec1b9832059426ed74f2755c9548852459,pablodanswer,2024-11-11,moderate slackbot switch
cohere_default,3ad98078f5205c2df5a3ea96cc165b982256a975,pablodanswer,2024-11-10,finalized keda
cohere_default,0fb12b42f10bae3d8633717f763fa42271349442,pablodanswer,2024-11-10,minor update
cohere_default,158329a3cc659d666328dac36bac7c5ffa87e084,pablodanswer,2024-11-10,finalize slackbot improvements
cohere_default,7f1a50823baf0f5bbab89587e7df6f03fe552e27,pablodanswer,2024-11-10,fix typing
cohere_default,0e76bcef454e0c09cb83ce91834730fdd084d930,pablodanswer,2024-11-10,add improved cloud configuration
csv_limits,45be7156c52d3b32799d67139998de7892c3490e,pablodanswer,2024-11-11,minor enforcement of CSV length for internal processing
custom_llm_display_fix,01efa818bcc82eef92457cbe4acd6c3c2fab60f0,pablodanswer,2024-11-21,Revert "clean horizontal scrollbar"
custom_llm_display_fix,dec279a9602825243ed7df4b7a5592ccd267bddd,pablodanswer,2024-11-21,update migration
custom_llm_display_fix,4b03c0e6e24b36725f4501edb81f46dc2812ff4f,pablodanswer,2024-11-21,k
custom_llm_display_fix,17eb0d3086b6249c806f51a0a45c78c927249bcd,pablodanswer,2024-11-21,ensure proper migration
custom_llm_display_fix,0f638229f56966e480d3479de5f9a3108750afc8,pablodanswer,2024-11-20,provider fix
custom_llm_display_fix,fa592a1b7a69897110a928a222b19eaef3b7267a,pablodanswer,2024-11-21,clean horizontal scrollbar
danswer_authorization_header,856c2debd98187b28e341940dafeb97eed81cad9,pablodanswer,2024-10-29,add danswer api key header
default_keys,4907d2271950fb2f45c56c21e6d641b616c02ad7,pablodanswer,2024-11-03,naming
default_keys,8766502f6dd125a43ef6cc9e9a20cec1c8f3ae8a,pablodanswer,2024-11-03,add cohere as well
default_keys,589e141bc9d2ed30c467257596f346c4824934a7,pablodanswer,2024-11-03,add default api keys for cloud users
default_prompts,d1926d47b5b65aeb01c103d7c44fa5bb63e4fb1c,pablodanswer,2024-11-06,update default live assistant logic
default_prompts,f457bdb49128b010da04612f598ef0e0810dcf7c,pablodanswer,2024-11-06,update starter message
default_prompts,00adc2d0e0cd23d7c9664b68f4caa7859bdb4eeb,Yuhong Sun,2024-11-06,touchup
default_prompts,f56b139d8dbcc44248080719fa9f3c81afdf1e81,pablodanswer,2024-11-06,nit
default_prompts,09cd3c6c2792b94e7db220a921095f0af8054e0c,pablodanswer,2024-11-06,minor update to refresh
default_prompts,32a688b6277b918afd7497f483ef457b85dc9d05,pablodanswer,2024-11-06,udpate refresh logic
default_prompts,719fb914f5094f3a35095cbb8e0c75aa4f0d0c45,pablodanswer,2024-11-06,update ux + spacing
default_prompts,7c5df1cf69e8c890cc02e27b2ba2edeac9c3c22a,pablodanswer,2024-11-05,fallback to all assistants
default_prompts,8a900b732dd67215718e07273cc62c881b6786e4,pablodanswer,2024-11-03,formating nits
default_prompts,eab00d7247cf0853b6a83888ae581c63c8c59981,pablodanswer,2024-11-03,nit
default_prompts,9460009ed306a135110bc88cc6b75f3779df96d0,pablodanswer,2024-11-03,update typing
default_prompts,4f1aa7f1ff04debb39b6ea8ea79de3d01254f4a5,pablodanswer,2024-11-03,validate
default_prompts,c97b8938920b4406477f252b01a1e561b3b24f31,pablodanswer,2024-11-03,k
default_prompts,074334e20d2208f52bbf00bda76e3e79494977c2,pablodanswer,2024-11-03,update user preferences
default_prompts,85b50855c0778fb34fc32441e7c3791b905485fa,pablodanswer,2024-11-03,update persona defaults
default_schema_slack,87931b759feb1431ce96090bd390e3e28cb30208,pablodanswer,2024-11-08,adjust default postgres schema for slack listener
detailed_filters,bde4b4029af5334699e226afbd77ba0753a04797,pablodanswer,2024-11-18,update date range filter
detailed_filters,d77629fc318db896c5b9f53c45c33dfad5038e6b,pablodanswer,2024-11-05,clarity updates
detailed_filters,0038c32213681db3dab29dee2f21324743fc6d94,pablodanswer,2024-11-05,add new complicated filters
double_auth,a7173eb689100c9abd1b68aeab890a992da32cbc,pablodanswer,2024-10-27,ports
double_auth,45170a28fc8417b6f0de7ac97c643a36e4c03284,pablodanswer,2024-10-27,fix nagging double auth issue
dropdown,c29beaf403a7722e1ee638cc50c8551931f8c5d9,pablodanswer,2024-11-13,combobox
dropdown,46f84d15f8af635123557056542829a14d5fca60,pablodanswer,2024-11-13,content scroll differences
dropdown,e8c93199f24cac94b73e8ac923b43b3159af74c9,pablodanswer,2024-11-13,minor dropdown fix
fallback_context,3734e683e1719d9f6abe9e80e475a4c2c275cdaf,pablodanswer,2024-11-07,ensure proper attribution
fallback_context,886e8c7b6e30328c1d95277f22dde48af2cb1a99,pablodanswer,2024-11-07,update comments
fallback_context,4916d66df0ec3d348caafe6c40c5e16fb28381b1,pablodanswer,2024-11-07,clearer
fallback_context,6ae512fc4e909a52e90c548f9674b60d536bdc54,pablodanswer,2024-11-06,update typing
fallback_context,159c8ee22df75036d3db59c292fa13632982b427,pablodanswer,2024-11-06,add sentinel value
feat/cert_clarity,35307d4f384039ef0df8f979e34912ab1cd4e201,pablodanswer,2024-10-30,first pass
feat/cert_clarity,e6b9ebc198973a84dc9412302e6b98a24b0a2ce3,pablodanswer,2024-10-29,ensure functionality
feat/cert_mount,a32e34b5571d60a4b8b8a1d62328b9a77fb0ad27,pablodanswer,2024-10-30,simplify
feat/cert_mount,2dc7b08a9cb73164479c03dfd4b4fed162029399,pablodanswer,2024-10-30,first pass
feat/cert_mount,e6b9ebc198973a84dc9412302e6b98a24b0a2ce3,pablodanswer,2024-10-29,ensure functionality
feat/certificate,152e8c422bb9c6bf7b08221dcfe44a60d7a2de22,pablodanswer,2024-11-01,nit
feat/certificate,45498a5f51a8efa9955c18fe5cb53b2d0f41ebd3,pablodanswer,2024-10-31,k
feat/certificate,9ecf237435cd8a5b0ac60ebaca8d26840ab0abed,pablodanswer,2024-10-31,minor clean up
feat/certificate,fed2c5666cb54d3edcfe14319e3f7d7befbed78e,pablodanswer,2024-10-30,remove now unneeded COPY command
feat/certificate,56b3f2fa999db64aec3fd069b1de2bc77d00a6b6,pablodanswer,2024-10-30,simplify
feat/certificate,7d03f3aa8cb8a4ada9af8551db62364eb8e2c217,pablodanswer,2024-10-30,first pass
feat/silence_unauth_logs,d2ba35ca45ca77701075813fd64858b04c4e9eb2,pablodanswer,2024-11-09,k
feat/silence_unauth_logs,923176ef6e1e1941f8dc461d1d7b1d76f88c4e1b,pablodanswer,2024-11-09,remove unnecessary line
feat/silence_unauth_logs,888ce3e0ced3a63c57f7ec2221059d0012e772c2,pablodanswer,2024-11-09,silence auth logs
feat/tenant_posthog,35ed1d2108dd1a28cf63ba45f776d8a25b91b5d7,pablodanswer,2024-10-27,nit
feat/tenant_posthog,d1a9e0f6c4618aa4a7e5029dbbeb6179a40ff5c7,pablodanswer,2024-10-27,distinguish tenants in posthog
fix-answer-with-specified-doc-ids,5fbcc70518bd5d1be00d6595f3fc690f81c52f21,pablodanswer,2024-11-01,minor logging updates for clarity
fix-answer-with-specified-doc-ids,7db0de9505c3510a4db76e98a47d5b079056dc93,pablodanswer,2024-10-31,minor typo
fix-answer-with-specified-doc-ids,18b4a8a26331bc013b49e486e2bf82c5ce4bfe73,pablodanswer,2024-10-31,fix stop generating
fix-answer-with-specified-doc-ids,98660be16459038b438d12616bd6f00dde418b95,Weves,2024-10-31,Fix UT
fix-answer-with-specified-doc-ids,3620266bddfbf1fca309ff2fe97f72bda7462979,Weves,2024-10-31,Remove unused exception
fix-answer-with-specified-doc-ids,2132a430cc64abd869632c0f55a35bdc42b30be9,Weves,2024-10-31,Fix image generation slowness
fix-answer-with-specified-doc-ids,24e34019ce25314c5e749d38dd0895a1c3d5141e,Weves,2024-10-31,More testing
fix-answer-with-specified-doc-ids,3cd4ed5052277428dc06343f53e0e6486af26208,Weves,2024-10-31,Testing
fix-answer-with-specified-doc-ids,200bb96853d6d96a99093f6e915fe9721ab5c6b3,Weves,2024-10-31,Add quote support
fix-answer-with-specified-doc-ids,5a0c6d003607dfb9a7445a6a87df9a6062b73bc6,Weves,2024-10-02,Fix
fix-openai-tokenizer,566e4cfd0f39db0a1fbc7c7fae040bcf98482f62,pablodanswer,2024-11-08,minor updates
fix-openai-tokenizer,3b09f3e53e7a8f948cd36255fd53423d7b5827d0,pablodanswer,2024-11-07,minor organizational update
fix-openai-tokenizer,75d5e6b8b6e81c77063fd79b4cfe532366da723a,pablodanswer,2024-11-07,minor update to ensure consistency
fix-openai-tokenizer,362bb3557246e86de131c223acdf2adf17fb14e4,pablodanswer,2024-11-06,nit
fix-openai-tokenizer,6d100d81d284dc98143bb8c94c16c25d64c56633,pablodanswer,2024-11-06,clean up test embeddings
fix-openai-tokenizer,c5be5dc4c9710b684d0954a5224a75c090befe94,Yuhong Sun,2024-11-05,k
fix_missing_json,1f6cc578c425f8bbe3b320f65f191f09c8fcfa0b,pablodanswer,2024-11-20,k
fix_missing_json,d95b7d6695ba087f0b9da9bdf245f7c34e503499,pablodanswer,2024-11-20,k
fix_missing_json,b75d4af102739a2b9e3ec2dff301f4affd08b3e5,pablodanswer,2024-11-20,remove logs
fix_missing_json,559d9ed6d4fd27de8941a104c9c83322a75abea6,pablodanswer,2024-11-20,k
fix_missing_json,9c900d658979341ce0d8c3c2eb87e7cfafd8ccf9,pablodanswer,2024-11-20,initial steps
formatting_niceties,e2b47fa84c828e1c9f6ab0dd510e2eb83faeb877,pablodanswer,2024-11-20,update styling
formatting_niceties,e4916209d6c9f4ed5765d7ae20f77903ffd93e9b,pablodanswer,2024-11-20,search bar formatting
graceful_failure,03245a4366adeb1668a337b37d070d09922f5531,pablodanswer,2024-10-28,fail gracefully on provider fetch
gtm,acff050f6b2bec0368571e0936f9342b7bcd3919,pablodanswer,2024-11-20,update github workflow
gtm,b96260442d02c9298ed110ba97f5e9eff1ed9100,pablodanswer,2024-11-20,add gtm for cloud build
gtm_v2,4f96ddf9e69923ef1209c5586c73eb40b0418aaa,pablodanswer,2024-11-21,quick fix
horizontal_scrollbar,fa82e8c74cac273563badadec0c04176575ffbbb,pablodanswer,2024-11-21,account for additional edge case
horizontal_scrollbar,fa592a1b7a69897110a928a222b19eaef3b7267a,pablodanswer,2024-11-21,clean horizontal scrollbar
improved_cert,3b19c075ad6e8930d785943b24e46b2c08555c3a,pablodanswer,2024-11-07,minor improvements
improved_cloud,379d569c61801f0c093b7474f888392aa2cb1249,pablodanswer,2024-11-11,include reset engine!
improved_cloud,53f9d94ceb7a6a8da2a0c2d94fee6971adb29bbf,pablodanswer,2024-11-11,revert
improved_cloud,5058d898b8532881c517e14c22ca5c32784288fe,pablodanswer,2024-11-11,update some configs
improved_cloud,bc7de4ec1b9832059426ed74f2755c9548852459,pablodanswer,2024-11-11,moderate slackbot switch
improved_cloud,3ad98078f5205c2df5a3ea96cc165b982256a975,pablodanswer,2024-11-10,finalized keda
improved_cloud,0fb12b42f10bae3d8633717f763fa42271349442,pablodanswer,2024-11-10,minor update
improved_cloud,158329a3cc659d666328dac36bac7c5ffa87e084,pablodanswer,2024-11-10,finalize slackbot improvements
improved_cloud,7f1a50823baf0f5bbab89587e7df6f03fe552e27,pablodanswer,2024-11-10,fix typing
improved_cloud,0e76bcef454e0c09cb83ce91834730fdd084d930,pablodanswer,2024-11-10,add improved cloud configuration
indent,95ded1611c7d2199438b863c54f327eba632a5b0,pablodanswer,2024-10-27,add indent to scan_iter
indexing_improvements,ff8e5612c9cd67a642314632658f5a55814f7c5e,pablodanswer,2024-11-05,minor
individual_deployments,fe83d549a356d802ee1e693c8739db7563ed5ddc,pablodanswer,2024-11-02,add k8s configs
individual_deployments,0e42bb64579328d18ff01049a7aaa2a0b49be142,pablodanswer,2024-10-31,remove unecessary locks
individual_deployments,41ec9b23309a3bbfe598018832fbf5d3fe91c5e1,pablodanswer,2024-10-31,minor
individual_deployments,9e4e848b98f35056dcf3df6f0815651e9fe56eba,pablodanswer,2024-10-30,initial removal of locks!
individual_deployments,1407652e3b5825fae7a90a0d5818ef67ec44f50d,pablodanswer,2024-10-30,nit
individual_deployments,2758ff7efd4dd47e891ef77c05985d6407e4cbd7,pablodanswer,2024-10-30,reorg
individual_deployments,0718d5740b714a0222eb2520c6c2f0e70c095aa1,pablodanswer,2024-10-30,validate
individual_deployments,922f3487fbd7585ce6a7251ff0644cbeca921133,pablodanswer,2024-10-30,add validated + reformatted dynamic beat acquisition
json_account,f4b3f8356a5911cb4a0610773b824bc6e6eb8c73,pablodanswer,2024-11-14,fix single quote block in llm answer
k8s_jobs,7124ce0b9a56f0b5dc45a733fe95cd581f9894a4,pablodanswer,2024-11-02,improve workers
k8s_jobs,10ab08420479ab056d807cbf0942c67a1dd6e7c7,pablodanswer,2024-11-02,improved timeouts + worker configs
k8s_jobs,9bc478fa1b7f1418fadfbd067383d67b417472aa,pablodanswer,2024-11-02,k
k8s_jobs,930e392d69ecd1058a73c0dfb0e2e021232921fc,pablodanswer,2024-11-02,update config
k8s_jobs,6d14ceeadf958cd1e7600b667b69ce0f3bf86830,pablodanswer,2024-11-02,k
k8s_jobs,efdf95eb232870f83677b2b424ffaa117463649a,pablodanswer,2024-11-02,add k8s configs
k8s_jobs,f687d3987cd9514f9fe587e563729ce27b8ff224,pablodanswer,2024-11-02,k
k8s_jobs,af4c9361a926867a992239daa283900300d7247e,pablodanswer,2024-11-02,nit
k8s_jobs,f74366bbd8699f9987ed8229e3368a5d7be71a53,pablodanswer,2024-11-01,update
k8s_jobs,734fcdca98aa5eeaa99d9936fa8db716eda93ad7,pablodanswer,2024-10-31,remove unecessary locks
k8s_jobs,dbc44315ad3cbf79509bd14a4025c2ecc4a6f86e,pablodanswer,2024-10-31,minor
k8s_jobs,d80049262406a0c30e9ad0fc647bddb23cbfbad9,pablodanswer,2024-10-30,initial removal of locks!
k8s_jobs,5646675ae094f39f3e7ead937cbcfd3fb7c7f24f,pablodanswer,2024-10-30,add validated + reformatted dynamic beat acquisition
k8s_jobs,01bdcad4f038c5d4c642ca14680593988c28bf96,pablodanswer,2024-11-02,ensure versioned apps capture
k8s_jobs,0994ac396612855ecac9afbce6ef9b8bd7e54742,pablodanswer,2024-11-01,typing
k8s_jobs,8ff8a88d5b6ad2d02a653f959c39cfeeda9ef54c,pablodanswer,2024-11-01,update
k8s_jobs,e11aee38ba5946a1453693fdc3bbd20d703d9e10,pablodanswer,2024-11-01,address comments
k8s_jobs,53c6d16c3cdc7ffb3eebd3e7b73474025ef6cafc,pablodanswer,2024-10-30,nit
k8s_jobs,a85b2a9745587c4e783e040496dee1ac83e492c9,pablodanswer,2024-10-30,reorg
k8s_jobs,4ace16c905b47b97990de0ab0ef3c029870f9be0,pablodanswer,2024-10-30,validate
k8s_jobs,89293ecc730387a864be6efc01230fedffdc7b82,pablodanswer,2024-10-30,add validated + reformatted dynamic beat acquisition
lenient_counting,4836a74e1e2789051b6d1454b7f2bd22daced61a,pablodanswer,2024-11-13,nit
lenient_counting,f7514011ef4cf62d80ab9afe170320b2e4135da2,pablodanswer,2024-11-13,lenient counting
max_height_scroll,c354912c704b0aa31737bfd41d4bd8f0c7d85769,pablodanswer,2024-11-20,ensure everythigng has a default max height in selectorformfield
migrate_tenant_upgrades_to_data_plane,572298aa8920d51320db5fff518f66fee6e42117,pablodanswer,2024-11-05,nit
migrate_tenant_upgrades_to_data_plane,40b55197ac8336e6ef081074ea65fc4b0cbeb27c,pablodanswer,2024-11-05,minor config update
migrate_tenant_upgrades_to_data_plane,4b9d868ecb78dedd3816ae7bc28e8f856881c6f4,pablodanswer,2024-11-04,minor pydantic update
migrate_tenant_upgrades_to_data_plane,1295c3a38e827024d89ba56fe3c846fcbe204bc0,pablodanswer,2024-11-04,ensure proper conditional
migrate_tenant_upgrades_to_data_plane,f2ac56d80213125f1f5d465b21a6a2e4b47566a2,pablodanswer,2024-11-04,improve import logic
migrate_tenant_upgrades_to_data_plane,fcdb3891bf196ef7e1f10e9d7a0a77512c752710,pablodanswer,2024-11-04,update provisioning
migrate_tenant_upgrades_to_data_plane,9a5d60c9a3df0891a769615e540af8332c0b416c,pablodanswer,2024-11-04,simplify
migrate_tenant_upgrades_to_data_plane,b512f35521bcb8c8ee9e748dae493028093f05bb,pablodanswer,2024-11-04,k
migrate_tenant_upgrades_to_data_plane,b872b7e778f7e0bd92e6eac9317e74e3157c12e1,pablodanswer,2024-11-04,minor clean up
migrate_tenant_upgrades_to_data_plane,b7847d16686419fe024d361cfaf2212a4decc397,pablodanswer,2024-11-04,minor cleanup
migrate_tenant_upgrades_to_data_plane,2f03ddb1bedada32576cb52bfa2cf36074fbb9fe,pablodanswer,2024-11-04,functional but scrappy
migrate_tenant_upgrades_to_data_plane,dc001a3b7b48df659bc64c2486ceded5eea3ed0f,pablodanswer,2024-11-04,add provisioning on data plane
minor,c7d58616b5943768e2e581751f4ede7a4f3292da,pablodanswer,2024-11-22,k
minor,351ee543a0773ecb6acf99f3888dd648091d7f85,pablodanswer,2024-11-22,k
minor_fixes,ea58c3259505aaa53c66343243667959ca79ecb8,pablodanswer,2024-11-05,minor changes
minor_fixes,cbf577cf4623c8352664058d21b1a80ae7ab4299,pablodanswer,2024-11-05,nit
minor_fixes,20d2301a7e594ad803c0486d63d056653c5b8c83,pablodanswer,2024-11-05,minor config update
minor_fixes,fdf9601375464f3e7f49d4472dbc3eeacd1eab8f,pablodanswer,2024-11-05,form
minor_fixes,7421328695641e943c7083639483fa36e4e9cfdb,pablodanswer,2024-11-04,minor pydantic update
minor_fixes,d600d63876e7100894c47a7dc9120b689a55521f,pablodanswer,2024-11-04,ensure proper conditional
minor_fixes,e7cae46867207789088df6611dbafc78650c8ace,pablodanswer,2024-11-04,improve import logic
minor_fixes,b0894320f99fea9cb13a94a5fbb5a1e9523ef460,pablodanswer,2024-11-04,update provisioning
minor_fixes,e623b494568d0bcc74937628984b6cc574aed9a6,pablodanswer,2024-11-04,simplify
minor_fixes,99d91bd658e812996bcc03d0be29e57277b8fb67,pablodanswer,2024-11-04,k
minor_fixes,77c180be0f8e91b9f997b90f631e18d41ba8fde2,pablodanswer,2024-11-04,minor clean up
minor_fixes,baaed72297ef248dc5dc422f0e5adcdff7599416,pablodanswer,2024-11-04,minor cleanup
minor_fixes,ab7fa7f6d0c3f1a59d97b5450262cb4ef6f8481d,pablodanswer,2024-11-04,functional but scrappy
minor_fixes,acf3ede8b4baf044391176aacd3bba6f80bb4b3f,pablodanswer,2024-11-04,add provisioning on data plane
minor_nits,bfcd418ecd9523376c605263565a9714ceeb3a18,pablodanswer,2024-11-09,k
minor_nits,5dfcb94964f977bb603865858e1e6aa6582454fd,pablodanswer,2024-11-09,update colors
minor_nits,a287cd94cd8090fefee7c1d20cc494b894bf39c1,pablodanswer,2024-11-09,nit
minor_nits,2d9586b059cfb1cb8e1f6c0fccc696af6ba8873d,pablodanswer,2024-11-08,nit
minor_nits,5dcc3692a7748ed20d49adef5f7672d45f600a4a,pablodanswer,2024-11-08,moderate component fixes
minor_slack_fixes,425a678a5350ad5716c3efd6a60c78f6a9c2738e,pablodanswer,2024-11-20,reset time
minor_slack_fixes,14adbcb497365f9e93c21aeb0476cffc72cab643,pablodanswer,2024-11-20,update slack redirect + token missing check
misc_color_cleanup,83c8f04e5a183a289f76b809d9aabdd4ea0e664b,pablodanswer,2024-11-03,formatting
misc_color_cleanup,334ff6fb5ab2e450e1e0709be16870b1ed07dae3,pablodanswer,2024-11-03,ensure tool call renders
misc_color_cleanup,94262264e768cdc28ffe4fc31b2947c0cf3774a3,pablodanswer,2024-11-03,ensure tailwind config evaluates properly + update textarea -> input
misc_color_cleanup,40cb9e9cdb4561eac777ede08ace88219d12ad96,pablodanswer,2024-11-02,additional minor nits
misc_color_cleanup,2e81962a74567c0c510d911a22aee385c56b3207,pablodanswer,2024-11-02,nit
misc_color_cleanup,76ca7eb3f2cf2408fee330f540987e6238cd632e,pablodanswer,2024-11-01,nit
misc_color_cleanup,7269b7a4aa986dbba654be4b375bea1d9334fe01,pablodanswer,2024-11-01,additional nits
misc_color_cleanup,4726a10fd7503882554d1dfaf1541657ffb45a04,pablodanswer,2024-11-01,misc color clean up
mobile_scroll,eca41cc514446a2c0b2c756add3164462fb2c49d,pablodanswer,2024-11-11,improved mobile scroll
modals,8093ceeb45088c813fbb117302738b3d225c2f8b,pablodanswer,2024-10-28,formatting
modals,3d0ace1e450ac6d7271ddedc2ec122a2647be7df,pablodanswer,2024-10-28,minor nits
modals,553aba79dc41b928c163a83481b202ad56805aae,pablodanswer,2024-10-28,update based on feedback
modals,da038b317a0b5185ccc32297b01fcaa97ffbb429,pablodanswer,2024-09-21,remove logs
modals,6769dc373faf7576c2d0ac212735b88eae755293,pablodanswer,2024-09-21,minor udpate to ui
modals,b35e05315c4c506da87524fe788a9cf5aacb7375,pablodanswer,2024-09-20,use display name + minor updates to models
modals,7cfd3d2d442255616ec5c477dc4b3eb0b2cad1ed,pablodanswer,2024-09-20,cleaner cards
modals,b2aa1c864b20274386a1bbe699a3ef7e094bd858,pablodanswer,2024-09-20,slightly cleaner animation
modals,d2f8177b8f1b9be8eebce520204018e6be59b03c,pablodanswer,2024-09-20,cleaner initial chat screen
more_theming,1744d29bd6f6740fb20bbbf8b5651cd60edbf127,pablodanswer,2024-11-21,k
more_theming,fa592a1b7a69897110a928a222b19eaef3b7267a,pablodanswer,2024-11-21,clean horizontal scrollbar
multi_api_key,67e347a47fd2e4aa9efe7b17c7b177166c893d10,pablodanswer,2024-10-31,clean
multi_api_key,3fb6e9bef96da888fa366a16f102358eb8e990e0,pablodanswer,2024-10-31,nit
multi_api_key,c4514fe68f58a03da0c3c3efae78ad23e2eb88c9,pablodanswer,2024-10-30,organization
multi_api_key,5b19209129542b885e123a51ce3da93b741d49d2,pablodanswer,2024-10-30,basic multi tenant api key
new_seq_tool_calling,59e9a33b30ece8d41340787d9d9a82e9a07a8f24,pablodanswer,2024-11-18,k
new_seq_tool_calling,6e60437c565a185475c715efbbef6caca1cfc2fb,pablodanswer,2024-11-17,quick nits
new_seq_tool_calling,9cde51f1a2ca1df2f753c9b6d7910b8f9623d8a4,pablodanswer,2024-11-07,scalable but not formalized
new_seq_tool_calling,8b8952f117e4d05bb484bc5dec1c12d4fbbafcca,pablodanswer,2024-11-07,k
new_seq_tool_calling,dc01eea610817ab821ded6e5ce584f81fe1ba065,pablodanswer,2024-11-07,add logs
new_seq_tool_calling,c89d8318c093c860037a839494876eff649f5d26,pablodanswer,2024-11-07,add image prompt citations
new_seq_tool_calling,3f2d6557dcb5964dbb9ed88ade743f74a4285411,pablodanswer,2024-11-07,functioning albeit janky
new_seq_tool_calling,b3818877afc406f9500e7bef1f2b7e233faf76fa,pablodanswer,2024-11-07,initial functioning update
new_theming_updates,102c264fd06232bbc4c7a23615add5cf7c0618be,pablodanswer,2024-11-21,minor updates
new_theming_updates,1744d29bd6f6740fb20bbbf8b5651cd60edbf127,pablodanswer,2024-11-21,k
new_theming_updates,fa592a1b7a69897110a928a222b19eaef3b7267a,pablodanswer,2024-11-21,clean horizontal scrollbar
nit,c68602f456c66279e760bd25067cfdfe03841f8a,pablodanswer,2024-11-10,specifically apply flex none to in progress!
nit_mx,c5147db1ae5387e8fd5672779689485142fb1b1d,pablodanswer,2024-11-20,formatting
nit_mx,3a6a74569544ee7d74c6b62a5a56730331838095,pablodanswer,2024-11-20,ensure margin properly applied
nit_redis,85843632c5fe61a425d425feef6480c639471af7,pablodanswer,2024-10-28,add srem and sadd to tenant wrapper
no_locks!,f687d3987cd9514f9fe587e563729ce27b8ff224,pablodanswer,2024-11-02,k
no_locks!,af4c9361a926867a992239daa283900300d7247e,pablodanswer,2024-11-02,nit
no_locks!,f74366bbd8699f9987ed8229e3368a5d7be71a53,pablodanswer,2024-11-01,update
no_locks!,734fcdca98aa5eeaa99d9936fa8db716eda93ad7,pablodanswer,2024-10-31,remove unecessary locks
no_locks!,dbc44315ad3cbf79509bd14a4025c2ecc4a6f86e,pablodanswer,2024-10-31,minor
no_locks!,d80049262406a0c30e9ad0fc647bddb23cbfbad9,pablodanswer,2024-10-30,initial removal of locks!
no_locks!,5646675ae094f39f3e7ead937cbcfd3fb7c7f24f,pablodanswer,2024-10-30,add validated + reformatted dynamic beat acquisition
no_locks!,01bdcad4f038c5d4c642ca14680593988c28bf96,pablodanswer,2024-11-02,ensure versioned apps capture
no_locks!,0994ac396612855ecac9afbce6ef9b8bd7e54742,pablodanswer,2024-11-01,typing
no_locks!,8ff8a88d5b6ad2d02a653f959c39cfeeda9ef54c,pablodanswer,2024-11-01,update
no_locks!,e11aee38ba5946a1453693fdc3bbd20d703d9e10,pablodanswer,2024-11-01,address comments
no_locks!,53c6d16c3cdc7ffb3eebd3e7b73474025ef6cafc,pablodanswer,2024-10-30,nit
no_locks!,a85b2a9745587c4e783e040496dee1ac83e492c9,pablodanswer,2024-10-30,reorg
no_locks!,4ace16c905b47b97990de0ab0ef3c029870f9be0,pablodanswer,2024-10-30,validate
no_locks!,89293ecc730387a864be6efc01230fedffdc7b82,pablodanswer,2024-10-30,add validated + reformatted dynamic beat acquisition
pinned,233713cde3516c05b857f878ff452c7714a91c48,pablodanswer,2024-11-20,hide animations
pinned,c0b17b4c51376d99685976430b9c4153c35e2ffa,Yuhong Sun,2024-11-20,k
pinned,15f30b00507e337ec9ee85624fc0cc574eb7b952,Yuhong Sun,2024-11-20,k
pinned,39d9df9b1b58dd2621bd575fa6c7ec720864d3bb,pablodanswer,2024-11-18,k
point_to_proper_docker_repository,9893301f113691111669bc2ab05a7c3abf19ae32,pablodanswer,2024-11-09,raise exits
point_to_proper_docker_repository,2344327112c01db8b2226dea0e02b2a8aa9ca875,pablodanswer,2024-11-09,ensure .github changes are passed
point_to_proper_docker_repository,caa2966ebc607fb8d2899ee78573ed2454983efb,pablodanswer,2024-11-09,robustify cloud deployment + include initial KEDA configuration
prev_doc,44f82fa928b79e7f51b41a0ee67cc93067880be3,pablodanswer,2024-11-22,k
prev_doc,2c7c9fbc130b8f0c717fa9fa4e5d2f6073f92be5,pablodanswer,2024-11-22,revert to previous doc select logic
prompting,4d8edad71ace767917a612dc628e266bd267d7d5,pablodanswer,2024-11-17,k
prompting,b1265619a27a849f2fbb9ba85b440a8b1b698d7d,pablodanswer,2024-11-16,add proper category delineation
prompting,dfe2c305866ad414143ce479b0601f8a61e615ea,pablodanswer,2024-11-05,post rebase cleanup
prompting,236c19230f5165e24ef557db53d863953faa714a,pablodanswer,2024-11-05,add auto-generated starter messages
proper_tenant_reset,4376bf773a81278ab92846673f193207be96052a,pablodanswer,2024-10-31,minor formatting
proper_tenant_reset,95f660db67b1327208fde82ae043511f2187452f,pablodanswer,2024-10-31,clear comment
proper_tenant_reset,1cdb5af9a1519ef8d63c94bf39256b00d4a8bdd2,pablodanswer,2024-10-31,add proper tenant reset
proper_token_default,4e0c048acba88f4c83d7c83af52bb0932234ddad,pablodanswer,2024-11-14,nit
proper_token_default,a0371a6750476fccc3b9892a7c58d72182c92507,pablodanswer,2024-11-14,minor logic update
proper_token_default,4f1c4baa80f7b747633bb3d528aed6de5b11f639,pablodanswer,2024-11-14,minor cosmetic update
proper_token_default,b6ef7e713a4eca3d65aa411604e8f67ad5efdd87,pablodanswer,2024-11-14,k
proper_token_default,66df9b6f7dae8bce61e35615d715ddefc6406614,pablodanswer,2024-11-14,improved fallback logic
proper_token_default,0473888ccdb5219cc39f275652bfeb72a420b5d9,pablodanswer,2024-11-13,silence warning
regenerate_clarity,3e232c39193b1c67bda9d732c1c2ee77ee14c721,pablodanswer,2024-10-29,minor udpate
regenerate_clarity,49e2da1c5c4fa34a8568ba0b3f08e79cd17cec93,pablodanswer,2024-10-29,add regeneration clarity
remove_ee,132802b295b805292f427039617a00e04dca2ae9,pablodanswer,2024-11-09,k
remove_ee,23883441f87ac3cd4e2ee717d2b033c3e7da9398,pablodanswer,2024-11-09,ensure callable
remove_ee,f43ed0b6b9391e66e210c5d90acf7a2409c3300b,pablodanswer,2024-11-09,finalize
remove_ee,fa42e5fa470e340e9b17fed5a3bd0e7976c6255e,pablodanswer,2024-11-08,finalize
remove_ee,625b5c52a044027b3d469286910a3cdd1c6bee02,pablodanswer,2024-11-08,update
remove_ee,239200dfc46f6cf18d7e689341b56a8baecdc0f6,pablodanswer,2024-11-08,update
remove_ee,5b70a8fa6f65d8513670c3bbbfd6cec13c76d530,pablodanswer,2024-11-08,general cleanup
remove_ee,14dfd6d29e178af9cfeb79ae20b7a846c5958966,pablodanswer,2024-11-08,move token rate limit to non-ee
remove_ee,dc4fdbb312881585fbc860b7aaff5adb9af4d8c5,pablodanswer,2024-11-08,finalize previous migration
remove_ee,cfd3d90493fad0af75569c98b6cfc9effa37b471,pablodanswer,2024-11-08,move api key to non-ee
remove_empty_directory,81e1ac918364467e3009eae376930199e3e2943f,pablodanswer,2024-10-28,remove empty directory
remove_endpoint,14f57d6475d835da6dfacc4ebd254e25618b3100,pablodanswer,2024-10-31,remove endpoint
rerender,1392f2454061914ac8c5f6302318a24064034a5b,pablodanswer,2024-11-21,k
rerender,617e6d905363cc91ca154bba0f6f2a11888b35e6,pablodanswer,2024-11-21,unused
rerender,da36e208cd53ae25a2c89a4cf0c598333898387a,pablodanswer,2024-11-21,clean
rerender,36eee45a03c3227a9b070e18a043e16fe5179cb9,pablodanswer,2024-11-21,llm provider causing re render in effect
reset_all,bde1510923d69ca0eb57340da6b59f9035e3de0a,pablodanswer,2024-11-04,ensure we reset all
search_chat_rework,931461bc8404fc51f15f0b75ae77e3a772a05989,pablodanswer,2024-11-21,v1
sequential_messages,5fbcc70518bd5d1be00d6595f3fc690f81c52f21,pablodanswer,2024-11-01,minor logging updates for clarity
sequential_messages,7db0de9505c3510a4db76e98a47d5b079056dc93,pablodanswer,2024-10-31,minor typo
sequential_messages,18b4a8a26331bc013b49e486e2bf82c5ce4bfe73,pablodanswer,2024-10-31,fix stop generating
sequential_messages,98660be16459038b438d12616bd6f00dde418b95,Weves,2024-10-31,Fix UT
sequential_messages,3620266bddfbf1fca309ff2fe97f72bda7462979,Weves,2024-10-31,Remove unused exception
sequential_messages,2132a430cc64abd869632c0f55a35bdc42b30be9,Weves,2024-10-31,Fix image generation slowness
sequential_messages,24e34019ce25314c5e749d38dd0895a1c3d5141e,Weves,2024-10-31,More testing
sequential_messages,3cd4ed5052277428dc06343f53e0e6486af26208,Weves,2024-10-31,Testing
sequential_messages,200bb96853d6d96a99093f6e915fe9721ab5c6b3,Weves,2024-10-31,Add quote support
sequential_messages,5a0c6d003607dfb9a7445a6a87df9a6062b73bc6,Weves,2024-10-02,Fix
shadcn,fe9be6669538db406a0c67959dcf4c91e8d4858b,pablodanswer,2024-10-28,button + input updates
shadcn,7cccb775c1f1385bc50131f7d548519d95ac64cd,pablodanswer,2024-10-28,initialization
sheet_update,98aa32055203d32a6d25eb1266deab6c58a176fb,pablodanswer,2024-11-21,update configuration
sheet_update,026134805a1418f32b61973f55571756ba102c09,pablodanswer,2024-11-21,finalized
sheet_update,36c1fc23d087f41db06e2680233a1ade7e65e594,pablodanswer,2024-11-21,k
sheet_update,3a4804b4b7d54fd3db576b698b5187d8dc0aa5ca,pablodanswer,2024-11-20,add multiple sheet stuff
sheet_update,5e326bcd08d019103f78da1c8a4a45ba4e401353,pablodanswer,2024-11-20,update sheet
sheet_update,d7f2a3e112c00bda2813933d673fb18080d6de6d,pablodanswer,2024-11-20,k
sheet_update,3eaf2a883a5fb52169af2ba2e0571189fb3712eb,pablodanswer,2024-11-20,quick pass
show_logs,189d62b72e0a2183ac3b25ea62eaea1b4db4366b,pablodanswer,2024-11-08,k
show_logs,89cb3b503cf219d90338110cec34d288892c27ed,pablodanswer,2024-11-08,minor updates
show_logs,cdda24f9ea4bc54f6a6c49d7848b63b2b5dacc9e,pablodanswer,2024-11-08,remove log
show_logs,6dc4ca344c927b5e9c02b28662252a4067a2f7da,pablodanswer,2024-11-08,k
show_logs,f91bac1cd90da5070247e70682e38adbe2722ce2,pablodanswer,2024-11-08,improved logging
show_logs,5e25488d0af1e1939a366fe12ab42949daaa77f1,pablodanswer,2024-11-08,add additional logs
silence_log,7400652fe70f86da3c8aab2a41f26103e395d739,pablodanswer,2024-11-20,silence small error
single_tool_call,0230920240fa46e06e1cc66fb67fa42f5caf81b3,pablodanswer,2024-11-01,finalize migration
single_tool_call,e7859e8bb4ea8409657cf0a7464724a5192e953e,pablodanswer,2024-11-01,single tool call per message
single_tool_call,fd3937179f14968b4103c634a83430f7ae9303bc,pablodanswer,2024-11-01,minor logging updates for clarity
single_tool_call,7a5a8f68a6e663d2b91badd47847193c92b523d0,pablodanswer,2024-10-31,minor typo
single_tool_call,122cd2082e4ddd4a56992f5f8c36b9853057581a,pablodanswer,2024-10-31,fix stop generating
single_tool_call,7384874e54a8ebc136b41efbe0842a327262b738,Weves,2024-10-31,Fix UT
single_tool_call,2b06789d5133029d99763037ded18766e8d04d74,Weves,2024-10-31,Remove unused exception
single_tool_call,4bdfd117370ac126e1bdc6e32f0192d59c51dd57,Weves,2024-10-31,Fix image generation slowness
single_tool_call,6d4ccc354514ff328473a1c35974521c465aa2f5,Weves,2024-10-31,More testing
single_tool_call,ef0ad8f8fce4eebc38cc9291047b84e5162572f3,Weves,2024-10-31,Testing
single_tool_call,99b076412aa3501cbff75d7521c4cedb8f793c34,Weves,2024-10-31,Add quote support
single_tool_call,499272ef25961ddb0861ee2a6ff6d978ea1e7772,Weves,2024-10-02,Fix
slack_scaling,dd958cff6b0999190c5116e0354497207231d5d6,pablodanswer,2024-10-30,minor foreign key update
super_user,0cc09c8b4d9ba0dca350a799ddc265fca38f4b90,pablodanswer,2024-11-02,nits
super_user,ec8ae2b5f4491e3de0701ba31ae3124d8f549e66,pablodanswer,2024-11-02,add super user
swap_buttons_cards,e6ce503bbbbed4d70734d11ebccc0db4994f69e0,pablodanswer,2024-11-01,nits
swap_buttons_cards,680a160b2560594c3c99d4f1e8cffc3bfea66064,pablodanswer,2024-11-01,update colors
swap_buttons_cards,748c99d655739c1bb7da0a25e2829c0d706ff810,pablodanswer,2024-10-31,clean build
swap_buttons_cards,a222b9d3e7819e9a7e525b6994248caa167c8ac1,pablodanswer,2024-10-30,list item + configuration updates
swap_buttons_cards,df38bde21a0f457fb6be4c1b66fae196ae32ec20,pablodanswer,2024-10-30,nits
swap_buttons_cards,ddb22e659d1fb4cd8f30ec952e68db683f5a746e,pablodanswer,2024-10-29,fully swapped
swap_buttons_cards,d91e54759a022acf478467b0906ee1a2867aa2ca,pablodanswer,2024-10-29,remove tremor
swap_buttons_cards,f6117b0f16581bac8fbd181e13a5dbc061c5debb,pablodanswer,2024-10-29,begin date picker + badge transfer
swap_buttons_cards,a8a73590bb24a59371c985931ac5dde96674f5b0,pablodanswer,2024-10-29,fix compiling
swap_buttons_cards,5f4f0c0ebb3f12e9de996661eb722561a048311b,pablodanswer,2024-10-29,migrate cards
swap_buttons_cards,8b8173bef0f05997c04ef9899d557d0f0a205767,pablodanswer,2024-10-29,minor updates
swap_buttons_cards,92b7fe45b1bd1ea39252cd8a4ac6a323a548f518,pablodanswer,2024-10-28,migrate badges
swap_buttons_cards,74091415c43c39080bd07c1ef9fc683ecc9742e2,pablodanswer,2024-10-28,migrate dividers + buttons
swap_buttons_cards,80f9af73d0adcb06c8228b868632bdecc362d616,pablodanswer,2024-10-28,button + input updates
swap_buttons_cards,efbeb2716536ea6b08fac40c1e074698a534ea11,pablodanswer,2024-10-28,initialization
switch-to-turbopack,09f5fea799633152f59fb9a54451d922eb4914e0,pablodanswer,2024-11-02,slight modification
switch-to-turbopack,f7ac9ae034605ac59a9c97650ebd6956d5628ed6,Weves,2024-11-02,Fix prettier
switch-to-turbopack,e42f4c98c487f671887de0c43680a659a9132753,Weves,2024-11-01,Style
switch-to-turbopack,f800017b21c2618ae51f16ef4f5d9b5e930f01fc,Weves,2024-11-01,Style
switch-to-turbopack,7f5744974644d6cbbcf41815e27f9017de76d738,Weves,2024-11-01,Fix charts
switch-to-turbopack,2b6514e75489842c8de0aae99d705e22daee9461,Weves,2024-11-01,Upgrade react
switch-to-turbopack,85d5857dbcbbf353a883abf7681c85a48dc4f724,Weves,2024-11-01,Remove override
switch-to-turbopack,7760230bf771cb6d3b0fca46b6e0bb35677ad5ee,Weves,2024-11-01,Update nextjs version
switch-to-turbopack,a3be5be8c6c2bf653de9df48e6a3dfc01144f849,Weves,2024-11-01,Remove unintended change
switch-to-turbopack,4d3fdba81ee2ccace76380b0b7318a5a5ed0ab79,Chris Weaver,2024-10-26,Upgrade to NextJS 15 + use turbopacK
temp/include_file61,20d29eb51cca799b9cc04552dd083bf202c760bc,pablodanswer,2024-11-03,temporary update
tenant_task_logger,02251aab75bad74647ba526654950b131748eb45,pablodanswer,2024-11-21,update
tenant_task_logger,805575ef183348ce55a7d8749db477422d0b30de,pablodanswer,2024-11-09,don't prevent seeding
tenant_task_logger,7146d02d553c568d99e7efd97a3b185f783a219a,pablodanswer,2024-11-06,update app base
tenant_task_logger,6c360ccc483de4ce42fc88724a55f793398a1445,pablodanswer,2024-11-05,remove logs from beat
tenant_task_logger,8773f215688e6775ebdf65bb5edda0f1e6080787,pablodanswer,2024-11-05,append
tenant_task_logger,d715c8be8a0465551e4d5670a43bf52d1d4635de,pablodanswer,2024-11-05,remove tenant id logs
tenant_task_logger,fa592a1b7a69897110a928a222b19eaef3b7267a,pablodanswer,2024-11-21,clean horizontal scrollbar
text_view,5d1a664fdc8c712aa644452b061e76b3302f714a,pablodanswer,2024-11-20,nit
text_view,b13a1d1d851b924f7b8f402894526d92712b09fa,pablodanswer,2024-11-18,k
text_view,77ab27f982af152818dcb9b4390da80113f17e72,pablodanswer,2024-11-15,update
text_view,61135ed7db5168d5517b8f11aed05e14b1aba471,pablodanswer,2024-11-14,basic log
text_view,7c13ca547fc42988ef9ca10bd4a354a0fd4473cc,pablodanswer,2024-11-14,minor testing update
text_view,46f9f0dc947da29271b16e893152402421cc1c85,pablodanswer,2024-11-14,update tests
text_view,756b56d2cd63b7792de532d05a03bbaac2c80960,pablodanswer,2024-11-13,wip tests
text_view,180c176136b46424021d4f0ca84052afae4946dd,pablodanswer,2024-11-13,minor docker file update
text_view,fa8a92875bc8c3637c7aa0eac937bc3a0818e66a,pablodanswer,2024-11-13,remove left over string
text_view,c6907ebebe9391140e272ebe0e89b6b6d207f8f5,pablodanswer,2024-11-13,finalize
text_view,709b87d56d0e770c1ee6240cfbd4bc76743eb521,pablodanswer,2024-11-13,finalized
text_view,b8df6e22d2d15a099aea2bc3b2e7d4c67b446ae8,pablodanswer,2024-11-13,k
text_view,ba977e3f5dae439f4ec6b62edc717ada5f49e1f5,pablodanswer,2024-11-12,minor typing update
text_view,ed5ed616efd0dceee374b2de5bec69adb4553a62,pablodanswer,2024-11-12,typing
text_view,ff4f3bb211485274250eed299247631cc2f1d9a3,pablodanswer,2024-11-12,update text view
text_view,e38fd6f7c76f3133fc407d99428a7286328843b6,pablodanswer,2024-11-12,update text view
text_view,c76602b7be9968643726f2a8818d27d290d400dd,pablodanswer,2024-11-12,k
text_view,62abe2511b8975ce050c4712a095372bf1d1ddc7,pablodanswer,2024-11-11,initial display
theming,e1eff26216e42897db4e49a02cb7bb13e9425422,pablodanswer,2024-11-18,nit
theming,4b1d428f71fd8993c516f35d8c4fa502c40baaae,pablodanswer,2024-11-18,add additional theming options
theming_updated,f95813e381acf7590e094f774c0811f375cde670,pablodanswer,2024-11-21,update neutral
theming_updated,804887fd311a783306f160591bc273866388a9f0,pablodanswer,2024-11-21,update
theming_updates,c6556857cceacce98b8a90f9a42c4ddfac3b7884,pablodanswer,2024-10-30,update our tailwind config
theming_updates,592394caeae4414bd87108ef9f8de65b77226e37,pablodanswer,2024-10-30,enforce colors
theming_updates,8f2b0eb72d55347091339c9ba39e2c12f238a776,pablodanswer,2024-10-30,remove pr
theming_updates,f92f8e7a73c238fc44ccca746d6fb597c5ad5cb8,pablodanswer,2024-10-30,nit
theming_updates,5c6fc34d6316e033b5e258b9a469fa1bd8ea3167,pablodanswer,2024-10-30,add comments
theming_updates,3472fb27371f59b454a4b27a699e2160b801ab46,pablodanswer,2024-10-30,ensure tailwind theme updated
theming_updates,8210c8930b005cfe6248618373a708b150e412f2,pablodanswer,2024-10-29,naming
theming_updates,e6b9ebc198973a84dc9412302e6b98a24b0a2ce3,pablodanswer,2024-10-29,ensure functionality
tool_call_per_message,bd0259c05ff9364a99670582ff1cd804fc1b12b7,pablodanswer,2024-11-03,validated
tool_call_per_message,381aadd24e897e28215964404048c84d7aeaa1df,pablodanswer,2024-11-03,remove print
tool_call_per_message,90c711322dc19a6c4092a60beb5905ded89079d6,pablodanswer,2024-11-01,k
tool_call_per_message,20a36e5f46755a55c022dd422c4d31e9abc24d46,pablodanswer,2024-11-01,validate simplify
tool_call_per_message,9b3a008ef42d31227290f0ddfbc5b37daa82f360,pablodanswer,2024-11-01,minor image generation fix
tool_call_per_message,a958903bd74c78457ef487debfb6084cd8ab6b2b,pablodanswer,2024-11-01,finalize migration
tool_call_per_message,4ea0aceca97734ddca8d1f60da930668e0561694,pablodanswer,2024-11-01,single tool call per message
tool_csv_image,8015e84531263cda72d7ca281ed0f790c0d0bb3f,pablodanswer,2024-11-03,add multiple formats to tools
tool_search,04be3fcbf7e128136f38760845f5d39197c94a5e,pablodanswer,2024-11-15,k
tool_search,601d497ed7acd05709384098a3132e1240d32932,pablodanswer,2024-11-15,add tests
tool_search,4de18b2e23222fc2c628982db8659d17c136adfa,pablodanswer,2024-11-07,update
tool_search,30e6e9b6dc8bebcc98fcf430fbd77af62faffd1a,pablodanswer,2024-11-07,somewhat cleaner
tool_search,ac64d4aa71cca26898a0eeb8d849a15a60945e69,pablodanswer,2024-11-06,remove logs
tool_search,1fd949ccfc6984904020ee50a845b119acd1f0be,pablodanswer,2024-11-06,finish functionality
tool_search,1253eb27f62c81780def9e37e5498b42321d6f49,pablodanswer,2024-11-06,k
tool_search,7dafd72d8c37ab505b35596fb3630c738b58688b,pablodanswer,2024-11-06,first pass
tooltips,5fe453e18565a9c2f3b8f20520fb7868b5e08675,pablodanswer,2024-11-04,nit: fix delay duration
tooltips,4bb9c461ef4c81543690f51c29c6c39949d3e882,pablodanswer,2024-11-04,clean up tooltips
typo,4f2f4e6534605287678fa046524a3ffd705e8ab4,pablodanswer,2024-11-18,(minor) typo
uf_theming,fe49e35ca476c494d0a9f36eb6cfea3e99ed0427,pablodanswer,2024-11-22,ensure added
uf_theming,804887fd311a783306f160591bc273866388a9f0,pablodanswer,2024-11-21,update
undo_temporary_fix,59fcdbaf5a096cc1bcd4599a1c0d7a256ca744f0,pablodanswer,2024-11-03,nit
undo_temporary_fix,c3118f91b9958e736704277b5d3f98a10e3943c2,pablodanswer,2024-11-03,Revert temporary modifications
update-confluence-behaviour,cc769b8bb9b47da9c955e70174bd498fb0b3231a,hagen-danswer,2024-11-15,has issue with boolean form
update-confluence-behaviour,e44646dd799c7f95db1df9616e83241344ef0035,hagen-danswer,2024-11-15,fixed mnore treljsertjoslijt
update-confluence-behaviour,b623630934171868c815b62e30be055fc6f06ec8,hagen-danswer,2024-11-15,whoops!
update-confluence-behaviour,790db4f8ea6bcb02df170d2892c57ccb50aaa119,hagen-danswer,2024-11-15,so good
update-confluence-behaviour,ccd6b8f38113b70ba3acf3beda199fa8ee6e3bab,hagen-danswer,2024-11-15,added key
update-confluence-behaviour,4beffa4be3ed029fe23c95ce08c5d18c9314e54e,hagen-danswer,2024-11-15,details!
update-confluence-behaviour,dacb1870dc98c986e1105fc797603957a2de4b5a,hagen-danswer,2024-11-15,copy change
update-confluence-behaviour,008d6cac8e86429884bd38bbe21a23dac96be123,hagen-danswer,2024-11-15,frontend cleanup
update-confluence-behaviour,f3310fbc73c45773dc19c2ef8da9f2fe4336b559,hagen-danswer,2024-11-15,fixed service account tests
update-confluence-behaviour,c7819a2c5735f812e150718a3620e4bf90ca6a1e,hagen-danswer,2024-11-15,fixed oauth admin tests
update-confluence-behaviour,f3fa6f1442910969f24ec4193b8cea3744f5847d,hagen-danswer,2024-11-15,reworked drive+confluence frontend and implied backend changes
user_defaults,fff98ddc15d8a94b44ffbaf2225545bc2c4c01b6,pablodanswer,2024-11-12,minor clarity
heads/v0.13.0-cloud.beta.0,102c264fd06232bbc4c7a23615add5cf7c0618be,pablodanswer,2024-11-21,minor updates
heads/v0.13.0-cloud.beta.0,1744d29bd6f6740fb20bbbf8b5651cd60edbf127,pablodanswer,2024-11-21,k
heads/v0.13.0-cloud.beta.0,fa592a1b7a69897110a928a222b19eaef3b7267a,pablodanswer,2024-11-21,clean horizontal scrollbar
validate,afc8075cc3076261c8b98a4fe30822641fb9d2cf,pablodanswer,2024-11-22,add filters to chat
validate,71123f54a753f243015f7f6bac62c3b8d1e6d05b,pablodanswer,2024-11-22,several steps
validate,6061adb114ef20c4bf6567c9450ae51a2938c927,pablodanswer,2024-11-22,remove chat / search toggle
validate,35300f65699862f982016284567ef12974ae05c2,pablodanswer,2024-11-22,update
validate,fe49e35ca476c494d0a9f36eb6cfea3e99ed0427,pablodanswer,2024-11-22,ensure added
validate,804887fd311a783306f160591bc273866388a9f0,pablodanswer,2024-11-21,update
vespa_improvements,7c27de6fdcc6172bc1ff4e9522711210f2113e86,pablodanswer,2024-11-14,minor configuration updates
Can't render this file because it contains an unexpected character in line 143 and column 96.

View File

@@ -17,21 +17,16 @@ def set_no_auth_user_preferences(
def load_no_auth_user_preferences(store: KeyValueStore) -> UserPreferences:
print("LOADING NO AUTH USER PREFERENCES")
try:
preferences_data = cast(
Mapping[str, Any], store.load(KV_NO_AUTH_USER_PREFERENCES_KEY)
)
print("PREFERENCES DATA", preferences_data)
return UserPreferences(**preferences_data)
except KvKeyNotFoundError:
return UserPreferences(
chosen_assistants=None, default_model=None, auto_scroll=True
)
return UserPreferences(chosen_assistants=None, default_model=None)
def fetch_no_auth_user(store: KeyValueStore) -> UserInfo:
print("FETCHING NO AUTH USER")
return UserInfo(
id="__no_auth_user__",
email="anonymous@danswer.ai",

View File

@@ -1,6 +1,5 @@
import multiprocessing
from typing import Any
from typing import cast
from celery import bootsteps # type: ignore
from celery import Celery
@@ -96,15 +95,6 @@ def on_worker_init(sender: Any, **kwargs: Any) -> None:
# by the primary worker. This is unnecessary in the multi tenant scenario
r = get_redis_client(tenant_id=None)
# Log the role and slave count - being connected to a slave or slave count > 0 could be problematic
info: dict[str, Any] = cast(dict, r.info("replication"))
role: str = cast(str, info.get("role"))
connected_slaves: int = info.get("connected_slaves", 0)
logger.info(
f"Redis INFO REPLICATION: role={role} connected_slaves={connected_slaves}"
)
# For the moment, we're assuming that we are the only primary worker
# that should be running.
# TODO: maybe check for or clean up another zombie primary worker if we detect it

View File

@@ -4,6 +4,7 @@ from typing import Any
from sqlalchemy.orm import Session
from danswer.background.indexing.run_indexing import RunIndexingCallbackInterface
from danswer.configs.app_configs import MAX_PRUNING_DOCUMENT_RETRIEVAL_PER_MINUTE
from danswer.connectors.cross_connector_utils.rate_limit_wrapper import (
rate_limit_builder,
@@ -16,7 +17,6 @@ from danswer.connectors.models import Document
from danswer.db.connector_credential_pair import get_connector_credential_pair
from danswer.db.enums import TaskStatus
from danswer.db.models import TaskQueueState
from danswer.indexing.indexing_heartbeat import IndexingHeartbeatInterface
from danswer.redis.redis_connector import RedisConnector
from danswer.server.documents.models import DeletionAttemptSnapshot
from danswer.utils.logger import setup_logger
@@ -78,7 +78,7 @@ def document_batch_to_ids(
def extract_ids_from_runnable_connector(
runnable_connector: BaseConnector,
callback: IndexingHeartbeatInterface | None = None,
callback: RunIndexingCallbackInterface | None = None,
) -> set[str]:
"""
If the SlimConnector hasnt been implemented for the given connector, just pull
@@ -111,15 +111,10 @@ def extract_ids_from_runnable_connector(
for doc_batch in doc_batch_generator:
if callback:
if callback.should_stop():
raise RuntimeError(
"extract_ids_from_runnable_connector: Stop signal detected"
)
raise RuntimeError("Stop signal received")
callback.progress(len(doc_batch))
all_connector_doc_ids.update(doc_batch_processing_func(doc_batch))
if callback:
callback.progress("extract_ids_from_runnable_connector", len(doc_batch))
return all_connector_doc_ids

View File

@@ -19,7 +19,7 @@ from danswer.db.engine import get_session_with_tenant
from danswer.db.enums import ConnectorCredentialPairStatus
from danswer.db.search_settings import get_all_search_settings
from danswer.redis.redis_connector import RedisConnector
from danswer.redis.redis_connector_delete import RedisConnectorDeletePayload
from danswer.redis.redis_connector_delete import RedisConnectorDeletionFenceData
from danswer.redis.redis_pool import get_redis_client
@@ -118,7 +118,7 @@ def try_generate_document_cc_pair_cleanup_tasks(
return None
# set a basic fence to start
fence_payload = RedisConnectorDeletePayload(
fence_payload = RedisConnectorDeletionFenceData(
num_tasks=None,
submitted=datetime.now(timezone.utc),
)

View File

@@ -16,6 +16,7 @@ from sqlalchemy.orm import Session
from danswer.background.celery.apps.app_base import task_logger
from danswer.background.indexing.job_client import SimpleJobClient
from danswer.background.indexing.run_indexing import run_indexing_entrypoint
from danswer.background.indexing.run_indexing import RunIndexingCallbackInterface
from danswer.configs.app_configs import DISABLE_INDEX_UPDATE_ON_SWAP
from danswer.configs.constants import CELERY_INDEXING_LOCK_TIMEOUT
from danswer.configs.constants import CELERY_VESPA_SYNC_BEAT_LOCK_TIMEOUT
@@ -41,7 +42,6 @@ from danswer.db.models import SearchSettings
from danswer.db.search_settings import get_current_search_settings
from danswer.db.search_settings import get_secondary_search_settings
from danswer.db.swap_index import check_index_swap
from danswer.indexing.indexing_heartbeat import IndexingHeartbeatInterface
from danswer.natural_language_processing.search_nlp_models import EmbeddingModel
from danswer.natural_language_processing.search_nlp_models import warm_up_bi_encoder
from danswer.redis.redis_connector import RedisConnector
@@ -57,7 +57,7 @@ from shared_configs.configs import SENTRY_DSN
logger = setup_logger()
class IndexingCallback(IndexingHeartbeatInterface):
class RunIndexingCallback(RunIndexingCallbackInterface):
def __init__(
self,
stop_key: str,
@@ -73,7 +73,6 @@ class IndexingCallback(IndexingHeartbeatInterface):
self.started: datetime = datetime.now(timezone.utc)
self.redis_lock.reacquire()
self.last_tag: str = ""
self.last_lock_reacquire: datetime = datetime.now(timezone.utc)
def should_stop(self) -> bool:
@@ -81,17 +80,15 @@ class IndexingCallback(IndexingHeartbeatInterface):
return True
return False
def progress(self, tag: str, amount: int) -> None:
def progress(self, amount: int) -> None:
try:
self.redis_lock.reacquire()
self.last_tag = tag
self.last_lock_reacquire = datetime.now(timezone.utc)
except LockError:
logger.exception(
f"IndexingCallback - lock.reacquire exceptioned. "
f"RunIndexingCallback - lock.reacquire exceptioned. "
f"lock_timeout={self.redis_lock.timeout} "
f"start={self.started} "
f"last_tag={self.last_tag} "
f"last_reacquired={self.last_lock_reacquire} "
f"now={datetime.now(timezone.utc)}"
)
@@ -622,7 +619,7 @@ def connector_indexing_task(
)
# define a callback class
callback = IndexingCallback(
callback = RunIndexingCallback(
redis_connector.stop.fence_key,
redis_connector_index.generator_progress_key,
lock,

View File

@@ -12,7 +12,7 @@ from sqlalchemy.orm import Session
from danswer.background.celery.apps.app_base import task_logger
from danswer.background.celery.celery_utils import extract_ids_from_runnable_connector
from danswer.background.celery.tasks.indexing.tasks import IndexingCallback
from danswer.background.celery.tasks.indexing.tasks import RunIndexingCallback
from danswer.configs.app_configs import ALLOW_SIMULTANEOUS_PRUNING
from danswer.configs.app_configs import JOB_TIMEOUT
from danswer.configs.constants import CELERY_PRUNING_LOCK_TIMEOUT
@@ -277,7 +277,7 @@ def connector_pruning_generator_task(
cc_pair.credential,
)
callback = IndexingCallback(
callback = RunIndexingCallback(
redis_connector.stop.fence_key,
redis_connector.prune.generator_progress_key,
lock,

View File

@@ -1,5 +1,7 @@
import time
import traceback
from abc import ABC
from abc import abstractmethod
from datetime import datetime
from datetime import timedelta
from datetime import timezone
@@ -29,7 +31,7 @@ from danswer.db.models import IndexingStatus
from danswer.db.models import IndexModelStatus
from danswer.document_index.factory import get_default_document_index
from danswer.indexing.embedder import DefaultIndexingEmbedder
from danswer.indexing.indexing_heartbeat import IndexingHeartbeatInterface
from danswer.indexing.indexing_heartbeat import IndexingHeartbeat
from danswer.indexing.indexing_pipeline import build_indexing_pipeline
from danswer.utils.logger import setup_logger
from danswer.utils.logger import TaskAttemptSingleton
@@ -40,6 +42,19 @@ logger = setup_logger()
INDEXING_TRACER_NUM_PRINT_ENTRIES = 5
class RunIndexingCallbackInterface(ABC):
"""Defines a callback interface to be passed to
to run_indexing_entrypoint."""
@abstractmethod
def should_stop(self) -> bool:
"""Signal to stop the looping function in flight."""
@abstractmethod
def progress(self, amount: int) -> None:
"""Send progress updates to the caller."""
def _get_connector_runner(
db_session: Session,
attempt: IndexAttempt,
@@ -91,7 +106,7 @@ def _run_indexing(
db_session: Session,
index_attempt: IndexAttempt,
tenant_id: str | None,
callback: IndexingHeartbeatInterface | None = None,
callback: RunIndexingCallbackInterface | None = None,
) -> None:
"""
1. Get documents which are either new or updated from specified application
@@ -123,7 +138,13 @@ def _run_indexing(
embedding_model = DefaultIndexingEmbedder.from_db_search_settings(
search_settings=search_settings,
callback=callback,
heartbeat=IndexingHeartbeat(
index_attempt_id=index_attempt.id,
db_session=db_session,
# let the world know we're still making progress after
# every 10 batches
freq=10,
),
)
indexing_pipeline = build_indexing_pipeline(
@@ -136,7 +157,6 @@ def _run_indexing(
),
db_session=db_session,
tenant_id=tenant_id,
callback=callback,
)
db_cc_pair = index_attempt.connector_credential_pair
@@ -208,9 +228,7 @@ def _run_indexing(
# contents still need to be initially pulled.
if callback:
if callback.should_stop():
raise RuntimeError(
"_run_indexing: Connector stop signal detected"
)
raise RuntimeError("Connector stop signal detected")
# TODO: should we move this into the above callback instead?
db_session.refresh(db_cc_pair)
@@ -271,7 +289,7 @@ def _run_indexing(
db_session.commit()
if callback:
callback.progress("_run_indexing", len(doc_batch))
callback.progress(len(doc_batch))
# This new value is updated every batch, so UI can refresh per batch update
update_docs_indexed(
@@ -401,7 +419,7 @@ def run_indexing_entrypoint(
tenant_id: str | None,
connector_credential_pair_id: int,
is_ee: bool = False,
callback: IndexingHeartbeatInterface | None = None,
callback: RunIndexingCallbackInterface | None = None,
) -> None:
try:
if is_ee:

View File

@@ -5,7 +5,7 @@ personas:
# this is for DanswerBot to use when tagged in a non-configured channel
# Careful setting specific IDs, this won't autoincrement the next ID value for postgres
- id: 0
name: "Search"
name: "Knowledge"
description: >
Assistant with access to documents from your Connected Sources.
# Default Prompt objects attached to the persona, see prompts.yaml

View File

@@ -605,7 +605,6 @@ def stream_chat_message_objects(
additional_headers=custom_tool_additional_headers,
),
)
tools: list[Tool] = []
for tool_list in tool_dict.values():
tools.extend(tool_list)

View File

@@ -126,7 +126,6 @@ class User(SQLAlchemyBaseUserTableUUID, Base):
# if specified, controls the assistants that are shown to the user + their order
# if not specified, all assistants are shown
auto_scroll: Mapped[bool] = mapped_column(Boolean, default=True)
chosen_assistants: Mapped[list[int] | None] = mapped_column(
postgresql.JSONB(), nullable=True, default=None
)
@@ -1182,7 +1181,7 @@ class LLMProvider(Base):
default_model_name: Mapped[str] = mapped_column(String)
fast_default_model_name: Mapped[str | None] = mapped_column(String, nullable=True)
# Models to actually display to users
# Models to actually disp;aly to users
# If nulled out, we assume in the application logic we should present all
display_model_names: Mapped[list[str] | None] = mapped_column(
postgresql.ARRAY(String), nullable=True

View File

@@ -10,7 +10,7 @@ from danswer.connectors.cross_connector_utils.miscellaneous_utils import (
get_metadata_keys_to_ignore,
)
from danswer.connectors.models import Document
from danswer.indexing.indexing_heartbeat import IndexingHeartbeatInterface
from danswer.indexing.indexing_heartbeat import Heartbeat
from danswer.indexing.models import DocAwareChunk
from danswer.natural_language_processing.utils import BaseTokenizer
from danswer.utils.logger import setup_logger
@@ -125,7 +125,7 @@ class Chunker:
chunk_token_limit: int = DOC_EMBEDDING_CONTEXT_SIZE,
chunk_overlap: int = CHUNK_OVERLAP,
mini_chunk_size: int = MINI_CHUNK_SIZE,
callback: IndexingHeartbeatInterface | None = None,
heartbeat: Heartbeat | None = None,
) -> None:
from llama_index.text_splitter import SentenceSplitter
@@ -134,7 +134,7 @@ class Chunker:
self.enable_multipass = enable_multipass
self.enable_large_chunks = enable_large_chunks
self.tokenizer = tokenizer
self.callback = callback
self.heartbeat = heartbeat
self.blurb_splitter = SentenceSplitter(
tokenizer=tokenizer.tokenize,
@@ -356,14 +356,9 @@ class Chunker:
def chunk(self, documents: list[Document]) -> list[DocAwareChunk]:
final_chunks: list[DocAwareChunk] = []
for document in documents:
if self.callback:
if self.callback.should_stop():
raise RuntimeError("Chunker.chunk: Stop signal detected")
final_chunks.extend(self._handle_single_document(document))
chunks = self._handle_single_document(document)
final_chunks.extend(chunks)
if self.callback:
self.callback.progress("Chunker.chunk", len(chunks))
if self.heartbeat:
self.heartbeat.heartbeat()
return final_chunks

View File

@@ -2,7 +2,7 @@ from abc import ABC
from abc import abstractmethod
from danswer.db.models import SearchSettings
from danswer.indexing.indexing_heartbeat import IndexingHeartbeatInterface
from danswer.indexing.indexing_heartbeat import Heartbeat
from danswer.indexing.models import ChunkEmbedding
from danswer.indexing.models import DocAwareChunk
from danswer.indexing.models import IndexChunk
@@ -34,7 +34,7 @@ class IndexingEmbedder(ABC):
api_url: str | None,
api_version: str | None,
deployment_name: str | None,
callback: IndexingHeartbeatInterface | None,
heartbeat: Heartbeat | None,
):
self.model_name = model_name
self.normalize = normalize
@@ -60,7 +60,7 @@ class IndexingEmbedder(ABC):
server_host=INDEXING_MODEL_SERVER_HOST,
server_port=INDEXING_MODEL_SERVER_PORT,
retrim_content=True,
callback=callback,
heartbeat=heartbeat,
)
@abstractmethod
@@ -83,7 +83,7 @@ class DefaultIndexingEmbedder(IndexingEmbedder):
api_url: str | None = None,
api_version: str | None = None,
deployment_name: str | None = None,
callback: IndexingHeartbeatInterface | None = None,
heartbeat: Heartbeat | None = None,
):
super().__init__(
model_name,
@@ -95,7 +95,7 @@ class DefaultIndexingEmbedder(IndexingEmbedder):
api_url,
api_version,
deployment_name,
callback,
heartbeat,
)
@log_function_time()
@@ -201,9 +201,7 @@ class DefaultIndexingEmbedder(IndexingEmbedder):
@classmethod
def from_db_search_settings(
cls,
search_settings: SearchSettings,
callback: IndexingHeartbeatInterface | None = None,
cls, search_settings: SearchSettings, heartbeat: Heartbeat | None = None
) -> "DefaultIndexingEmbedder":
return cls(
model_name=search_settings.model_name,
@@ -215,5 +213,5 @@ class DefaultIndexingEmbedder(IndexingEmbedder):
api_url=search_settings.api_url,
api_version=search_settings.api_version,
deployment_name=search_settings.deployment_name,
callback=callback,
heartbeat=heartbeat,
)

View File

@@ -1,15 +1,41 @@
from abc import ABC
from abc import abstractmethod
import abc
from typing import Any
from sqlalchemy import func
from sqlalchemy.orm import Session
from danswer.db.index_attempt import get_index_attempt
from danswer.utils.logger import setup_logger
logger = setup_logger()
class IndexingHeartbeatInterface(ABC):
"""Defines a callback interface to be passed to
to run_indexing_entrypoint."""
class Heartbeat(abc.ABC):
"""Useful for any long-running work that goes through a bunch of items
and needs to occasionally give updates on progress.
e.g. chunking, embedding, updating vespa, etc."""
@abstractmethod
def should_stop(self) -> bool:
"""Signal to stop the looping function in flight."""
@abc.abstractmethod
def heartbeat(self, metadata: Any = None) -> None:
raise NotImplementedError
@abstractmethod
def progress(self, tag: str, amount: int) -> None:
"""Send progress updates to the caller."""
class IndexingHeartbeat(Heartbeat):
def __init__(self, index_attempt_id: int, db_session: Session, freq: int):
self.cnt = 0
self.index_attempt_id = index_attempt_id
self.db_session = db_session
self.freq = freq
def heartbeat(self, metadata: Any = None) -> None:
self.cnt += 1
if self.cnt % self.freq == 0:
index_attempt = get_index_attempt(
db_session=self.db_session, index_attempt_id=self.index_attempt_id
)
if index_attempt:
index_attempt.time_updated = func.now()
self.db_session.commit()
else:
logger.error("Index attempt not found, this should not happen!")

View File

@@ -34,7 +34,7 @@ from danswer.document_index.interfaces import DocumentIndex
from danswer.document_index.interfaces import DocumentMetadata
from danswer.indexing.chunker import Chunker
from danswer.indexing.embedder import IndexingEmbedder
from danswer.indexing.indexing_heartbeat import IndexingHeartbeatInterface
from danswer.indexing.indexing_heartbeat import IndexingHeartbeat
from danswer.indexing.models import DocAwareChunk
from danswer.indexing.models import DocMetadataAwareIndexChunk
from danswer.utils.logger import setup_logger
@@ -414,7 +414,6 @@ def build_indexing_pipeline(
ignore_time_skip: bool = False,
attempt_id: int | None = None,
tenant_id: str | None = None,
callback: IndexingHeartbeatInterface | None = None,
) -> IndexingPipelineProtocol:
"""Builds a pipeline which takes in a list (batch) of docs and indexes them."""
search_settings = get_current_search_settings(db_session)
@@ -441,8 +440,13 @@ def build_indexing_pipeline(
tokenizer=embedder.embedding_model.tokenizer,
enable_multipass=multipass,
enable_large_chunks=enable_large_chunks,
# after every doc, update status in case there are a bunch of really long docs
callback=callback,
# after every doc, update status in case there are a bunch of
# really long docs
heartbeat=IndexingHeartbeat(
index_attempt_id=attempt_id, db_session=db_session, freq=1
)
if attempt_id
else None,
)
return partial(

View File

@@ -16,7 +16,7 @@ from danswer.configs.model_configs import (
)
from danswer.configs.model_configs import DOC_EMBEDDING_CONTEXT_SIZE
from danswer.db.models import SearchSettings
from danswer.indexing.indexing_heartbeat import IndexingHeartbeatInterface
from danswer.indexing.indexing_heartbeat import Heartbeat
from danswer.natural_language_processing.utils import get_tokenizer
from danswer.natural_language_processing.utils import tokenizer_trim_content
from danswer.utils.logger import setup_logger
@@ -99,7 +99,7 @@ class EmbeddingModel:
api_url: str | None,
provider_type: EmbeddingProvider | None,
retrim_content: bool = False,
callback: IndexingHeartbeatInterface | None = None,
heartbeat: Heartbeat | None = None,
api_version: str | None = None,
deployment_name: str | None = None,
) -> None:
@@ -116,7 +116,7 @@ class EmbeddingModel:
self.tokenizer = get_tokenizer(
model_name=model_name, provider_type=provider_type
)
self.callback = callback
self.heartbeat = heartbeat
model_server_url = build_model_server_url(server_host, server_port)
self.embed_server_endpoint = f"{model_server_url}/encoder/bi-encoder-embed"
@@ -160,10 +160,6 @@ class EmbeddingModel:
embeddings: list[Embedding] = []
for idx, text_batch in enumerate(text_batches, start=1):
if self.callback:
if self.callback.should_stop():
raise RuntimeError("_batch_encode_texts detected stop signal")
logger.debug(f"Encoding batch {idx} of {len(text_batches)}")
embed_request = EmbedRequest(
model_name=self.model_name,
@@ -183,8 +179,8 @@ class EmbeddingModel:
response = self._make_model_server_request(embed_request)
embeddings.extend(response.embeddings)
if self.callback:
self.callback.progress("_batch_encode_texts", 1)
if self.heartbeat:
self.heartbeat.heartbeat()
return embeddings
def encode(

View File

@@ -17,7 +17,7 @@ from danswer.db.document import construct_document_select_for_connector_credenti
from danswer.db.models import Document as DbDocument
class RedisConnectorDeletePayload(BaseModel):
class RedisConnectorDeletionFenceData(BaseModel):
num_tasks: int | None
submitted: datetime
@@ -54,18 +54,20 @@ class RedisConnectorDelete:
return False
@property
def payload(self) -> RedisConnectorDeletePayload | None:
def payload(self) -> RedisConnectorDeletionFenceData | None:
# read related data and evaluate/print task progress
fence_bytes = cast(bytes, self.redis.get(self.fence_key))
if fence_bytes is None:
return None
fence_str = fence_bytes.decode("utf-8")
payload = RedisConnectorDeletePayload.model_validate_json(cast(str, fence_str))
payload = RedisConnectorDeletionFenceData.model_validate_json(
cast(str, fence_str)
)
return payload
def set_fence(self, payload: RedisConnectorDeletePayload | None) -> None:
def set_fence(self, payload: RedisConnectorDeletionFenceData | None) -> None:
if not payload:
self.redis.delete(self.fence_key)
return

View File

@@ -45,7 +45,6 @@ class UserPreferences(BaseModel):
visible_assistants: list[int] = []
recent_assistants: list[int] | None = None
default_model: str | None = None
auto_scroll: bool | None = None
class UserInfo(BaseModel):
@@ -80,7 +79,6 @@ class UserInfo(BaseModel):
role=user.role,
preferences=(
UserPreferences(
auto_scroll=user.auto_scroll,
chosen_assistants=user.chosen_assistants,
default_model=user.default_model,
hidden_assistants=user.hidden_assistants,
@@ -130,10 +128,6 @@ class HiddenUpdateRequest(BaseModel):
hidden: bool
class AutoScrollRequest(BaseModel):
auto_scroll: bool | None
class SlackBotCreationRequest(BaseModel):
name: str
enabled: bool

View File

@@ -52,7 +52,6 @@ from danswer.db.users import list_users
from danswer.db.users import validate_user_role_update
from danswer.key_value_store.factory import get_kv_store
from danswer.server.manage.models import AllUsersResponse
from danswer.server.manage.models import AutoScrollRequest
from danswer.server.manage.models import UserByEmail
from danswer.server.manage.models import UserInfo
from danswer.server.manage.models import UserPreferences
@@ -494,14 +493,11 @@ def verify_user_logged_in(
# if auth type is disabled, return a dummy user with preferences from
# the key-value store
if AUTH_TYPE == AuthType.DISABLED:
print("FETCHING NO AUTH USER")
store = get_kv_store()
user = fetch_no_auth_user(store)
print("ll ", user)
return user
return fetch_no_auth_user(store)
raise BasicAuthenticationError(detail="User Not Authenticated")
print("not disabled", user)
if user.oidc_expiry and user.oidc_expiry < datetime.now(timezone.utc):
raise BasicAuthenticationError(
detail="Access denied. User's OIDC token has expired.",
@@ -585,30 +581,6 @@ def update_user_recent_assistants(
db_session.commit()
@router.patch("/auto-scroll")
def update_user_auto_scroll(
request: AutoScrollRequest,
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> None:
if user is None:
if AUTH_TYPE == AuthType.DISABLED:
store = get_kv_store()
no_auth_user = fetch_no_auth_user(store)
no_auth_user.preferences.auto_scroll = request.auto_scroll
set_no_auth_user_preferences(store, no_auth_user.preferences)
return
else:
raise RuntimeError("This should never happen")
db_session.execute(
update(User)
.where(User.id == user.id) # type: ignore
.values(auto_scroll=request.auto_scroll)
)
db_session.commit()
@router.patch("/user/default-model")
def update_user_default_model(
request: ChosenDefaultModelRequest,

View File

@@ -83,7 +83,6 @@ class CreateChatMessageRequest(ChunkContext):
message: str
# Files that we should attach to this message
file_descriptors: list[FileDescriptor]
# If no prompt provided, uses the largest prompt of the chat session
# but really this should be explicitly specified, only in the simplified APIs is this inferred
# Use prompt_id 0 to use the system default prompt which is Answer-Question

View File

@@ -41,10 +41,33 @@ class Notification(BaseModel):
class Settings(BaseModel):
"""General settings"""
chat_page_enabled: bool = True
search_page_enabled: bool = True
default_page: PageType = PageType.SEARCH
maximum_chat_retention_days: int | None = None
gpu_enabled: bool | None = None
product_gating: GatingType = GatingType.NONE
def check_validity(self) -> None:
chat_page_enabled = self.chat_page_enabled
search_page_enabled = self.search_page_enabled
default_page = self.default_page
if chat_page_enabled is False and search_page_enabled is False:
raise ValueError(
"One of `search_page_enabled` and `chat_page_enabled` must be True."
)
if default_page == PageType.CHAT and chat_page_enabled is False:
raise ValueError(
"The default page cannot be 'chat' if the chat page is disabled."
)
if default_page == PageType.SEARCH and search_page_enabled is False:
raise ValueError(
"The default page cannot be 'search' if the search page is disabled."
)
class UserSettings(Settings):
notifications: list[Notification]

View File

@@ -411,8 +411,6 @@ def _validate_curator_status__no_commit(
.all()
)
# if the user is a curator in any of their groups, set their role to CURATOR
# otherwise, set their role to BASIC
if curator_relationships:
user.role = UserRole.CURATOR
elif user.role == UserRole.CURATOR:
@@ -438,15 +436,6 @@ def update_user_curator_relationship(
user = fetch_user_by_id(db_session, set_curator_request.user_id)
if not user:
raise ValueError(f"User with id '{set_curator_request.user_id}' not found")
if user.role == UserRole.ADMIN:
raise ValueError(
f"User '{user.email}' is an admin and therefore has all permissions "
"of a curator. If you'd like this user to only have curator permissions, "
"you must update their role to BASIC then assign them to be CURATOR in the "
"appropriate groups."
)
requested_user_groups = fetch_user_groups_for_user(
db_session=db_session,
user_id=set_curator_request.user_id,

View File

@@ -1,26 +0,0 @@
#!/bin/bash
# Number of recent branches to show
num_branches=5
# Get recent branches
recent_branches=$(git for-each-ref --sort=-committerdate --format='%(refname:short)' --count=$num_branches refs/heads/)
# Loop through recent branches
for branch in $recent_branches; do
echo "Branch: $branch"
echo "Last commit: $(git log -1 --pretty=format:"%cr" $branch)"
# Get the number of commits ahead/behind master
ahead_behind=$(git rev-list --left-right --count master...$branch)
ahead=$(echo $ahead_behind | cut -f2 -d$'\t')
behind=$(echo $ahead_behind | cut -f1 -d$'\t')
echo "Commits ahead of master: $ahead"
echo "Commits behind master: $behind"
# Get the number of lines changed compared to master
lines_changed=$(git diff --shortstat master...$branch)
echo "Changes compared to master: $lines_changed"
echo "----------------------------------------"
done

View File

@@ -1,25 +0,0 @@
#!/bin/bash
# Output CSV file
output_file="branch_commits.csv"
# Write CSV header
echo "Branch,Commit Hash,Author,Date,Subject" > "$output_file"
# Get all local branches except main
branches=$(git for-each-ref --format='%(refname:short)' refs/heads/ | grep -v '^main$')
# Loop through each branch
for branch in $branches; do
# Get commits for the branch
commits=$(git log main..$branch --pretty=format:"%H,%an,%ad,%s" --date=short)
# If there are commits, add them to the CSV
if [ ! -z "$commits" ]; then
while IFS= read -r commit; do
echo "$branch,$commit" >> "$output_file"
done <<< "$commits"
fi
done
echo "CSV file created: $output_file"

View File

@@ -1,16 +1,15 @@
from typing import Any
import pytest
from danswer.indexing.indexing_heartbeat import IndexingHeartbeatInterface
from danswer.indexing.indexing_heartbeat import Heartbeat
class MockHeartbeat(IndexingHeartbeatInterface):
class MockHeartbeat(Heartbeat):
def __init__(self) -> None:
self.call_count = 0
def should_stop(self) -> bool:
return False
def progress(self, tag: str, amount: int) -> None:
def heartbeat(self, metadata: Any = None) -> None:
self.call_count += 1

View File

@@ -74,7 +74,7 @@ def test_chunker_heartbeat(
chunker = Chunker(
tokenizer=embedder.embedding_model.tokenizer,
enable_multipass=False,
callback=mock_heartbeat,
heartbeat=mock_heartbeat,
)
chunks = chunker.chunk([document])

View File

@@ -0,0 +1,80 @@
from unittest.mock import MagicMock
from unittest.mock import patch
import pytest
from sqlalchemy.orm import Session
from danswer.db.index_attempt import IndexAttempt
from danswer.indexing.indexing_heartbeat import IndexingHeartbeat
@pytest.fixture
def mock_db_session() -> MagicMock:
return MagicMock(spec=Session)
@pytest.fixture
def mock_index_attempt() -> MagicMock:
return MagicMock(spec=IndexAttempt)
def test_indexing_heartbeat(
mock_db_session: MagicMock, mock_index_attempt: MagicMock
) -> None:
with patch(
"danswer.indexing.indexing_heartbeat.get_index_attempt"
) as mock_get_index_attempt:
mock_get_index_attempt.return_value = mock_index_attempt
heartbeat = IndexingHeartbeat(
index_attempt_id=1, db_session=mock_db_session, freq=5
)
# Test that heartbeat doesn't update before freq is reached
for _ in range(4):
heartbeat.heartbeat()
mock_db_session.commit.assert_not_called()
# Test that heartbeat updates when freq is reached
heartbeat.heartbeat()
mock_get_index_attempt.assert_called_once_with(
db_session=mock_db_session, index_attempt_id=1
)
assert mock_index_attempt.time_updated is not None
mock_db_session.commit.assert_called_once()
# Reset mock calls
mock_db_session.reset_mock()
mock_get_index_attempt.reset_mock()
# Test that heartbeat updates again after freq more calls
for _ in range(5):
heartbeat.heartbeat()
mock_get_index_attempt.assert_called_once()
mock_db_session.commit.assert_called_once()
def test_indexing_heartbeat_not_found(mock_db_session: MagicMock) -> None:
with patch(
"danswer.indexing.indexing_heartbeat.get_index_attempt"
) as mock_get_index_attempt, patch(
"danswer.indexing.indexing_heartbeat.logger"
) as mock_logger:
mock_get_index_attempt.return_value = None
heartbeat = IndexingHeartbeat(
index_attempt_id=1, db_session=mock_db_session, freq=1
)
heartbeat.heartbeat()
mock_get_index_attempt.assert_called_once_with(
db_session=mock_db_session, index_attempt_id=1
)
mock_logger.error.assert_called_once_with(
"Index attempt not found, this should not happen!"
)
mock_db_session.commit.assert_not_called()

View File

@@ -69,9 +69,6 @@ ENV NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST}
ARG NEXT_PUBLIC_SENTRY_DSN
ENV NEXT_PUBLIC_SENTRY_DSN=${NEXT_PUBLIC_SENTRY_DSN}
ARG NEXT_PUBLIC_GTM_ENABLED
ENV NEXT_PUBLIC_GTM_ENABLED=${NEXT_PUBLIC_GTM_ENABLED}
RUN npx next build
# Step 2. Production image, copy all the files and run next
@@ -137,12 +134,9 @@ ARG NEXT_PUBLIC_POSTHOG_KEY
ARG NEXT_PUBLIC_POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
ENV NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST}
ARG NEXT_PUBLIC_SENTRY_DSN
ENV NEXT_PUBLIC_SENTRY_DSN=${NEXT_PUBLIC_SENTRY_DSN}
ARG NEXT_PUBLIC_GTM_ENABLED
ENV NEXT_PUBLIC_GTM_ENABLED=${NEXT_PUBLIC_GTM_ENABLED}
# Note: Don't expose ports here, Compose will handle that for us if necessary.
# If you want to run this without compose, specify the ports to

103
web/package-lock.json generated
View File

@@ -15,13 +15,11 @@
"@headlessui/react": "^2.2.0",
"@headlessui/tailwindcss": "^0.2.1",
"@phosphor-icons/react": "^2.0.8",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.3",
"@sentry/nextjs": "^8.34.0",
@@ -2663,57 +2661,6 @@
}
}
},
"node_modules/@radix-ui/react-checkbox": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz",
"integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-presence": "1.1.1",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-previous": "1.1.0",
"@radix-ui/react-use-size": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
@@ -3874,56 +3821,6 @@
}
}
},
"node_modules/@radix-ui/react-switch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz",
"integrity": "sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-previous": "1.1.0",
"@radix-ui/react-use-size": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-context": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz",

View File

@@ -17,13 +17,11 @@
"@headlessui/react": "^2.2.0",
"@headlessui/tailwindcss": "^0.2.1",
"@phosphor-icons/react": "^2.0.8",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.3",
"@sentry/nextjs": "^8.34.0",

View File

@@ -142,8 +142,6 @@ export function CustomLLMProviderUpdateForm({
},
body: JSON.stringify({
...values,
// For custom llm providers, all model names are displayed
display_model_names: values.model_names,
custom_config: customConfigProcessing(values.custom_config_list),
}),
});

View File

@@ -62,11 +62,11 @@ export default function Sidebar() {
];
return (
<div className="flex flex-none w-[250px] text-default">
<div className="flex flex-none w-[250px] bg-background text-default">
<div
className={`
fixed
bg-background-sidebar
bg-background-100
h-screen
transition-all
bg-opacity-80

View File

@@ -83,7 +83,7 @@ const EditRow = ({
</div>
</TooltipTrigger>
{!documentSet.is_up_to_date && (
<TooltipContent width="max-w-sm">
<TooltipContent maxWidth="max-w-sm">
<div className="flex break-words break-keep whitespace-pre-wrap items-start">
<InfoIcon className="mr-2 mt-0.5" />
Cannot update while syncing! Wait for the sync to finish, then

View File

@@ -175,6 +175,29 @@ export function SettingsForm() {
{ fieldName, newValue: checked },
];
// If we're disabling a page, check if we need to update the default page
if (
!checked &&
(fieldName === "search_page_enabled" || fieldName === "chat_page_enabled")
) {
const otherPageField =
fieldName === "search_page_enabled"
? "chat_page_enabled"
: "search_page_enabled";
const otherPageEnabled = settings && settings[otherPageField];
if (
otherPageEnabled &&
settings?.default_page ===
(fieldName === "search_page_enabled" ? "search" : "chat")
) {
updates.push({
fieldName: "default_page",
newValue: fieldName === "search_page_enabled" ? "chat" : "search",
});
}
}
updateSettingField(updates);
}
@@ -195,17 +218,42 @@ export function SettingsForm() {
return (
<div>
{popup}
<Title className="mb-4">Workspace Settings</Title>
<Title className="mb-4">Page Visibility</Title>
<Checkbox
label="Auto-scroll"
sublabel="If set, then the chat will auto-scroll to the bottom when a new message is sent."
checked={settings.auto_scroll}
label="Search Page Enabled?"
sublabel="If set, then the 'Search' page will be accessible to all users and will show up as an option on the top navbar. If unset, then this page will not be available."
checked={settings.search_page_enabled}
onChange={(e) =>
handleToggleSettingsField("auto_scroll", e.target.checked)
handleToggleSettingsField("search_page_enabled", e.target.checked)
}
/>
<Checkbox
label="Chat Page Enabled?"
sublabel="If set, then the 'Chat' page will be accessible to all users and will show up as an option on the top navbar. If unset, then this page will not be available."
checked={settings.chat_page_enabled}
onChange={(e) =>
handleToggleSettingsField("chat_page_enabled", e.target.checked)
}
/>
<Selector
label="Default Page"
subtext="The page that users will be redirected to after logging in. Can only be set to a page that is enabled."
options={[
{ value: "search", name: "Search" },
{ value: "chat", name: "Chat" },
]}
selected={settings.default_page}
onSelect={(value) => {
value &&
updateSettingField([
{ fieldName: "default_page", newValue: value },
]);
}}
/>
{isEnterpriseEnabled && (
<>
<Title className="mb-4">Chat Settings</Title>

View File

@@ -5,12 +5,14 @@ export enum GatingType {
}
export interface Settings {
chat_page_enabled: boolean;
search_page_enabled: boolean;
default_page: "search" | "chat";
maximum_chat_retention_days: number | null;
notifications: Notification[];
needs_reindexing: boolean;
gpu_enabled: boolean;
product_gating: GatingType;
auto_scroll: boolean;
}
export enum NotificationType {
@@ -52,7 +54,6 @@ export interface EnterpriseSettings {
custom_popup_header: string | null;
custom_popup_content: string | null;
enable_consent_screen: boolean | null;
auto_scroll: boolean;
}
export interface CombinedSettings {

View File

@@ -16,7 +16,6 @@ import {
RetrievalType,
StreamingError,
ToolCallMetadata,
FinalContextDocs,
} from "./interfaces";
import Prism from "prismjs";
@@ -113,7 +112,6 @@ import {
} from "@/components/ui/card";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import AssistantBanner from "../../components/assistants/AssistantBanner";
import AssistantSelector from "@/components/chat_search/AssistantSelector";
const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2;
@@ -133,9 +131,8 @@ export function ChatPage({
const {
chatSessions,
ccPairs,
tags,
documentSets,
availableSources,
availableDocumentSets,
llmProviders,
folders,
openedFolders,
@@ -151,11 +148,9 @@ export function ChatPage({
// available in server-side components
const settings = useContext(SettingsContext);
const enterpriseSettings = settings?.enterpriseSettings;
const [documentSidebarToggled, setDocumentSidebarToggled] = useState(false);
const [filtersToggled, setFiltersToggled] = useState(false);
const [userSettingsToggled, setUserSettingsToggled] = useState(false);
if (settings?.settings?.chat_page_enabled === false) {
router.push("/search");
}
const { assistants: availableAssistants, finalAssistants } = useAssistants();
@@ -170,6 +165,7 @@ export function ChatPage({
searchParams.get(SEARCH_PARAM_NAMES.SEND_ON_LOAD)
);
const currentPersonaId = searchParams.get(SEARCH_PARAM_NAMES.PERSONA_ID);
const modelVersionFromSearchParams = searchParams.get(
SEARCH_PARAM_NAMES.STRUCTURED_MODEL
);
@@ -271,16 +267,6 @@ export function ChatPage({
const noAssistants = liveAssistant == null || liveAssistant == undefined;
const availableSources = ccPairs.map((ccPair) => ccPair.source);
const [finalAvailableSources, finalAvailableDocumentSets] =
computeAvailableFilters({
selectedPersona: availableAssistants.find(
(assistant) => assistant.id === liveAssistant?.id
),
availableSources: availableSources,
availableDocumentSets: documentSets,
});
// always set the model override for the chat session, when an assistant, llm provider, or user preference exists
useEffect(() => {
const personaDefault = getLLMProviderOverrideForPersona(
@@ -460,9 +446,9 @@ export function ChatPage({
}
if (shouldScrollToBottom) {
if (!hasPerformedInitialScroll && autoScrollEnabled) {
if (!hasPerformedInitialScroll) {
clientScrollToBottom();
} else if (isChatSessionSwitch && autoScrollEnabled) {
} else if (isChatSessionSwitch) {
clientScrollToBottom(true);
}
}
@@ -1043,10 +1029,7 @@ export function ChatPage({
}
setAlternativeGeneratingAssistant(alternativeAssistantOverride);
if (autoScrollEnabled) {
clientScrollToBottom();
}
clientScrollToBottom();
let currChatSessionId: string;
const isNewSession = chatSessionIdRef.current === null;
const searchParamBasedChatSessionName =
@@ -1292,8 +1275,8 @@ export function ChatPage({
if (Object.hasOwn(packet, "answer_piece")) {
answer += (packet as AnswerPiecePacket).answer_piece;
} else if (Object.hasOwn(packet, "final_context_docs")) {
documents = (packet as FinalContextDocs).final_context_docs;
} else if (Object.hasOwn(packet, "top_documents")) {
documents = (packet as DocumentsResponse).top_documents;
retrievalType = RetrievalType.Search;
if (documents && documents.length > 0) {
// point to the latest message (we don't know the messageId yet, which is why
@@ -1391,8 +1374,7 @@ export function ChatPage({
retrievalType,
query: finalMessage?.rephrased_query || query,
documents:
// finalMessage?.context_docs?.top_documents ||
documents,
finalMessage?.context_docs?.top_documents || documents,
citations: finalMessage?.citations || {},
files: finalMessage?.files || aiMessageImages || [],
toolCall: finalMessage?.tool_call || toolCall,
@@ -1610,10 +1592,6 @@ export function ChatPage({
setToggled: removeToggle,
mobile: settings?.isMobile,
});
const autoScrollEnabled =
user?.auto_scroll == null
? settings?.enterpriseSettings?.auto_scroll
: user?.auto_scroll;
useScrollonStream({
chatState: currentSessionChatState,
@@ -1623,7 +1601,6 @@ export function ChatPage({
debounceNumber,
waitForScrollRef,
mobile: settings?.isMobile,
enableAutoScroll: autoScrollEnabled,
});
// Virtualization + Scrolling related effects and functions
@@ -1773,13 +1750,6 @@ export function ChatPage({
liveAssistant
);
});
console.log("retrievalEnabled", retrievalEnabled);
useEffect(() => {
if (!retrievalEnabled) {
setDocumentSidebarToggled(false);
}
}, [retrievalEnabled]);
const [stackTraceModalContent, setStackTraceModalContent] = useState<
string | null
>(null);
@@ -1819,30 +1789,9 @@ export function ChatPage({
setSharedChatSession(chatSession);
};
const [documentSelection, setDocumentSelection] = useState(false);
// const toggleDocumentSelectionAspects = () => {
// setDocumentSelection((documentSelection) => !documentSelection);
// setShowDocSidebar(false);
// };
const toggleDocumentSidebar = () => {
if (!documentSidebarToggled) {
setFiltersToggled(false);
setDocumentSidebarToggled(true);
} else if (!filtersToggled) {
setDocumentSidebarToggled(false);
} else {
setFiltersToggled(false);
}
};
const toggleFilters = () => {
if (!documentSidebarToggled) {
setFiltersToggled(true);
setDocumentSidebarToggled(true);
} else if (filtersToggled) {
setDocumentSidebarToggled(false);
} else {
setFiltersToggled(true);
}
const toggleDocumentSelectionAspects = () => {
setDocumentSelection((documentSelection) => !documentSelection);
setShowDocSidebar(false);
};
interface RegenerationRequest {
@@ -1895,16 +1844,13 @@ export function ChatPage({
/>
)}
{(settingsToggled || userSettingsToggled) && (
{settingsToggled && (
<SetDefaultModelModal
setPopup={setPopup}
setLlmOverride={llmOverrideManager.setGlobalDefault}
defaultModel={user?.preferences.default_model!}
llmProviders={llmProviders}
onClose={() => {
setUserSettingsToggled(false);
setSettingsToggled(false);
}}
onClose={() => setSettingsToggled(false)}
/>
)}
@@ -1995,7 +1941,7 @@ export function ChatPage({
page="chat"
ref={innerSidebarElementRef}
toggleSidebar={toggleSidebar}
toggled={toggledSidebar}
toggled={toggledSidebar && !settings?.isMobile}
backgroundToggled={toggledSidebar || showDocSidebar}
existingChats={chatSessions}
currentChatSession={selectedChatSession}
@@ -2008,53 +1954,6 @@ export function ChatPage({
</div>
</div>
</div>
{!settings?.isMobile && (
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
flex-none
fixed
right-0
z-40
bg-background-100
h-screen
transition-all
bg-opacity-80
duration-300
ease-in-out
bg-transparent
overflow-y-hidden
transition-all
bg-opacity-80
duration-300
ease-in-out
h-full
${documentSidebarToggled ? "w-[300px]" : "w-[0px]"}
`}
>
<DocumentSidebar
filterManager={filterManager}
ccPairs={ccPairs}
tags={tags}
documentSets={documentSets}
ref={innerSidebarElementRef}
toggleSidebar={toggleDocumentSidebar}
showFilters={filtersToggled}
closeSidebar={() => setDocumentSidebarToggled(false)}
selectedMessage={aiMessage}
selectedDocuments={selectedDocuments}
toggleDocumentSelection={toggleDocumentSelection}
clearSelectedDocuments={clearSelectedDocuments}
selectedDocumentTokens={selectedDocumentTokens}
maxTokens={maxTokens}
isLoading={isFetchingChatMessages}
initialWidth={300}
isOpen={documentSidebarToggled}
/>
</div>
)}
<BlurBackground
visible={!untoggled && (showDocSidebar || toggledSidebar)}
@@ -2064,21 +1963,9 @@ export function ChatPage({
ref={masterFlexboxRef}
className="flex h-full w-full overflow-x-hidden"
>
<div className="flex h-full relative mx-2 flex-col w-full">
{/* {liveAssistant && onAssistantChange && (
<div className="flex justify-center mt-4 -mb-4 h w-full overflow-visible">
<AssistantSelector
liveAssistant={liveAssistant}
onAssistantChange={onAssistantChange}
llmOverrideManager={llmOverrideManager}
/>
</div>
)} */}
<div className="flex h-full flex-col w-full">
{liveAssistant && (
<FunctionalHeader
toggleUserSettings={() => setUserSettingsToggled(true)}
liveAssistant={liveAssistant}
onAssistantChange={onAssistantChange}
sidebarToggled={toggledSidebar}
reset={() => setMessage("")}
page="chat"
@@ -2087,10 +1974,8 @@ export function ChatPage({
? setSharingModalVisible
: undefined
}
toggleSidebar={toggleDocumentSidebar}
toggleSidebar={toggleSidebar}
currentChatSession={selectedChatSession}
documentSidebarToggled={documentSidebarToggled}
llmOverrideManager={llmOverrideManager}
/>
)}
@@ -2122,18 +2007,9 @@ export function ChatPage({
{...getRootProps()}
>
<div
className={`w-full h-full flex flex-col default-scrollbar overflow-y-auto overflow-x-hidden relative`}
className={`w-full h-full flex flex-col overflow-y-auto include-scrollbar overflow-x-hidden relative`}
ref={scrollableDivRef}
>
{liveAssistant && onAssistantChange && (
<div className="z-[1000] flex justify-center w-full overflow-visible">
<AssistantSelector
liveAssistant={liveAssistant}
onAssistantChange={onAssistantChange}
llmOverrideManager={llmOverrideManager}
/>
</div>
)}
{/* ChatBanner is a custom banner that displays a admin-specified message at
the top of the chat page. Oly used in the EE version of the app. */}
@@ -2304,11 +2180,6 @@ export function ChatPage({
}
>
<AIMessage
index={i}
documentSelectionToggled={
documentSidebarToggled &&
!filtersToggled
}
continueGenerating={
i == messageHistory.length - 1 &&
currentCanContinue()
@@ -2345,10 +2216,9 @@ export function ChatPage({
}}
isActive={messageHistory.length - 1 == i}
selectedDocuments={selectedDocuments}
toggleDocumentSelection={() => {
// toggleDocumentSelectionAspects();
toggleDocumentSidebar();
}}
toggleDocumentSelection={
toggleDocumentSelectionAspects
}
docs={message.documents}
currentPersona={liveAssistant}
alternativeAssistant={
@@ -2587,7 +2457,6 @@ export function ChatPage({
llmOverrideManager={llmOverrideManager}
files={currentMessageFiles}
setFiles={setCurrentMessageFiles}
toggleFilters={toggleFilters}
handleFileUpload={handleImageUpload}
textAreaRef={textAreaRef}
chatSessionId={chatSessionIdRef.current!}
@@ -2620,20 +2489,6 @@ export function ChatPage({
</div>
</div>
</div>
{!settings?.isMobile && (
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
flex-none
overflow-y-hidden
transition-all
duration-300
ease-in-out
h-full
${documentSidebarToggled ? "w-[300px]" : "w-[0px]"}
`}
></div>
)}
</div>
)}
</Dropzone>
@@ -2653,9 +2508,8 @@ export function ChatPage({
</div>
<FixedLogo backgroundToggled={toggledSidebar || showDocSidebar} />
</div>
{/* Right Sidebar - DocumentSidebar */}
</div>
{/* <DocumentSidebar
<DocumentSidebar
initialWidth={350}
ref={innerSidebarElementRef}
closeSidebar={() => setDocumentSelection(false)}
@@ -2667,7 +2521,7 @@ export function ChatPage({
maxTokens={maxTokens}
isLoading={isFetchingChatMessages}
isOpen={documentSelection}
/> */}
/>
</>
);
}

View File

@@ -30,68 +30,104 @@ export function ChatDocumentDisplay({
tokenLimitReached,
}: DocumentDisplayProps) {
const isInternet = document.is_internet;
// Consider reintroducing null scored docs in the future
if (document.score === null) {
return null;
}
const faviconUrl =
isInternet && document.link
? `https://www.google.com/s2/favicons?domain=${
new URL(document.link).hostname
}&sz=32`
: null;
return (
<div className="opacity-100 will-change-auto">
<div
className={`flex relative flex-col gap-0.5 rounded-xl mx-2 my-1.5 ${
isSelected ? "bg-gray-200" : "hover:bg-background-125"
}`}
>
<div
key={document.semantic_identifier}
className={`p-2 w-[325px] justify-start rounded-md ${
isSelected ? "bg-background-200" : "bg-background-125"
} text-sm mx-3`}
>
<div className="flex relative justify-start overflow-y-visible">
<a
href={document.link}
target="_blank"
rel="noopener noreferrer"
className="flex flex-col px-2 py-1.5"
className={
"rounded-lg flex font-bold flex-shrink truncate" +
(document.link ? "" : "pointer-events-none")
}
rel="noreferrer"
>
<div className="line-clamp-1 flex h-6 items-center gap-2 text-xs">
{faviconUrl ? (
<img
alt="Favicon"
width="32"
height="32"
className="rounded-full bg-gray-200 object-cover"
src={faviconUrl}
/>
) : (
<SourceIcon sourceType={document.source_type} iconSize={18} />
)}
<span>
{document.link
? new URL(document.link).hostname
: document.source_type}
</span>
</div>
<div className="line-clamp-2 text-sm font-semibold">
{isInternet ? (
<InternetSearchIcon url={document.link} />
) : (
<SourceIcon sourceType={document.source_type} iconSize={18} />
)}
<p className="overflow-hidden text-left text-ellipsis mx-2 my-auto text-sm">
{document.semantic_identifier || document.document_id}
</div>
<div className="line-clamp-2 text-sm font-normal leading-snug text-gray-600">
{buildDocumentSummaryDisplay(
document.match_highlights,
document.blurb
)}
</div>
<div className="absolute top-2 right-2">
{!isInternet && (
<DocumentSelector
isSelected={isSelected}
handleSelect={() => handleSelect(document.document_id)}
isDisabled={tokenLimitReached && !isSelected}
/>
)}
</div>
</p>
</a>
{document.score !== null && (
<div className="my-auto">
{isAIPick && (
<div className="w-4 h-4 my-auto mr-1 flex flex-col">
<HoverPopup
mainContent={<FiRadio className="text-gray-500 my-auto" />}
popupContent={
<div className="text-xs text-gray-300 w-36 flex">
<div className="flex mx-auto">
<div className="w-3 h-3 flex flex-col my-auto mr-1">
<FiInfo className="my-auto" />
</div>
<div className="my-auto">The AI liked this doc!</div>
</div>
</div>
}
direction="bottom"
style="dark"
/>
</div>
)}
<div
className={`
text-xs
text-emphasis
bg-hover
rounded
p-0.5
w-fit
my-auto
select-none
my-auto
mr-2`}
>
{Math.abs(document.score).toFixed(2)}
</div>
</div>
)}
{!isInternet && (
<DocumentSelector
isSelected={isSelected}
handleSelect={() => handleSelect(document.document_id)}
isDisabled={tokenLimitReached && !isSelected}
/>
)}
</div>
<div>
<div className="mt-1">
<DocumentMetadataBlock document={document} />
</div>
</div>
<p className="line-clamp-3 pl-1 pt-2 mb-1 text-start break-words">
{buildDocumentSummaryDisplay(document.match_highlights, document.blurb)}
test
</p>
<div className="mb-2">
{/*
// TODO: find a way to include this
{queryEventId && (
<DocumentFeedbackBlock
documentId={document.document_id}
queryId={queryEventId}
setPopup={setPopup}
/>
)} */}
</div>
</div>
);

View File

@@ -6,12 +6,8 @@ import { removeDuplicateDocs } from "@/lib/documentUtils";
import { Message } from "../interfaces";
import { ForwardedRef, forwardRef } from "react";
import { Separator } from "@/components/ui/separator";
import { FilterManager } from "@/lib/hooks";
import { CCPairBasicInfo, DocumentSet, Tag } from "@/lib/types";
import { SourceSelector } from "../shared_chat_search/SearchFilters";
interface DocumentSidebarProps {
filterManager: FilterManager;
closeSidebar: () => void;
selectedMessage: Message | null;
selectedDocuments: DanswerDocument[] | null;
@@ -22,11 +18,6 @@ interface DocumentSidebarProps {
isLoading: boolean;
initialWidth: number;
isOpen: boolean;
toggleSidebar: () => void;
ccPairs: CCPairBasicInfo[];
tags: Tag[];
documentSets: DocumentSet[];
showFilters: boolean;
}
export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
@@ -35,7 +26,6 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
closeSidebar,
selectedMessage,
selectedDocuments,
filterManager,
toggleDocumentSelection,
clearSelectedDocuments,
selectedDocumentTokens,
@@ -43,11 +33,6 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
isLoading,
initialWidth,
isOpen,
toggleSidebar,
ccPairs,
tags,
documentSets,
showFilters,
},
ref: ForwardedRef<HTMLDivElement>
) => {
@@ -59,14 +44,19 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
const currentDocuments = selectedMessage?.documents || null;
const dedupedDocuments = removeDuplicateDocs(currentDocuments || []);
// NOTE: do not allow selection if less than 75 tokens are left
// this is to prevent the case where they are able to select the doc
// but it basically is unused since it's truncated right at the very
// start of the document (since title + metadata + misc overhead) takes up
// space
const tokenLimitReached = selectedDocumentTokens > maxTokens - 75;
const hasSelectedDocuments = selectedDocumentIds.length > 0;
return (
<div
id="danswer-chat-sidebar"
className="w-full border-l border-border"
className={`fixed inset-0 transition-opacity duration-300 z-50 bg-black/80 ${
isOpen ? "opacity-100" : "opacity-0 pointer-events-none"
}`}
onClick={(e) => {
if (e.target === e.currentTarget) {
closeSidebar();
@@ -74,7 +64,7 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
}}
>
<div
className={`ml-auto h-screen rounded-l-lg relative border-l sidebar z-50 absolute right-0 h-screen transition-all duration-300 ${
className={`ml-auto rounded-l-lg relative border-l bg-text-100 sidebar z-50 absolute right-0 h-screen transition-all duration-300 ${
isOpen ? "opacity-100 translate-x-0" : "opacity-0 translate-x-[10%]"
}`}
ref={ref}
@@ -84,95 +74,90 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
>
<div className="pb-6 flex-initial overflow-y-hidden flex flex-col h-screen">
{popup}
<div className="p-4 border-b border-border flex justify-between items-center">
<h2 className="text-xl font-bold text-text-900">
{showFilters ? "Filters" : "Sources"}
</h2>
<button
onClick={toggleSidebar}
className="text-sm text-primary-600 hover:text-primary-800 transition-colors duration-200 ease-in-out"
>
Close
</button>
<div className="pl-3 mx-2 pr-6 mt-3 flex text-text-800 flex-col text-2xl text-emphasis flex font-semibold">
{dedupedDocuments.length} Documents
<p className="text-sm font-semibold flex flex-wrap gap-x-2 text-text-600 mt-1">
Select to add to continuous context
<a
href="https://docs.danswer.dev/introduction"
className="underline cursor-pointer hover:text-strong"
>
Learn more
</a>
</p>
</div>
<div className="overflow-y-auto default-scrollbar flex-grow dark-scrollbar flex relative flex-col">
{showFilters ? (
<SourceSelector
tagsOnLeft={true}
toggleFilters={() => {}}
filtersUntoggled={false}
{...filterManager}
showDocSidebar={false}
availableDocumentSets={documentSets}
existingSources={ccPairs.map((ccPair) => ccPair.source)}
availableTags={tags}
/>
) : (
<>
{dedupedDocuments.length > 0 ? (
dedupedDocuments.map((document, ind) => (
<div
key={document.document_id}
className={`${
ind === dedupedDocuments.length - 1
? "mb-5"
: "border-b border-border-light mb-3"
}`}
>
<ChatDocumentDisplay
document={document}
setPopup={setPopup}
queryEventId={null}
isAIPick={false}
isSelected={selectedDocumentIds.includes(
document.document_id
)}
handleSelect={(documentId) => {
toggleDocumentSelection(
dedupedDocuments.find(
(doc) => doc.document_id === documentId
)!
);
}}
tokenLimitReached={tokenLimitReached}
/>
</div>
))
) : (
<div className="mx-3" />
)}
</>
)}
</div>
<Separator className="mb-0 mt-4 pb-2" />
{currentDocuments ? (
<div className="overflow-y-auto flex-grow dark-scrollbar flex relative flex-col">
{dedupedDocuments.length > 0 ? (
dedupedDocuments.map((document, ind) => (
<div
key={document.document_id}
className={`${
ind === dedupedDocuments.length - 1
? "mb-5"
: "border-b border-border-light mb-3"
}`}
>
<ChatDocumentDisplay
document={document}
setPopup={setPopup}
queryEventId={null}
isAIPick={false}
isSelected={selectedDocumentIds.includes(
document.document_id
)}
handleSelect={(documentId) => {
toggleDocumentSelection(
dedupedDocuments.find(
(document) => document.document_id === documentId
)!
);
}}
tokenLimitReached={tokenLimitReached}
/>
</div>
))
) : (
<div className="mx-3">
<Text>No documents found for the query.</Text>
</div>
)}
</div>
) : (
!isLoading && (
<div className="ml-4 mr-3">
<Text>
When you run ask a question, the retrieved documents will
show up here!
</Text>
</div>
)
)}
</div>
{!showFilters && (
<>
<div className="absolute left-0 bottom-0 w-full bg-gradient-to-b from-white/0 via-white/60 to-white dark:from-black/0 dark:via-black/60 dark:to-black h-[100px]" />
<div className="absolute left-0 bottom-0 w-full bg-gradient-to-b from-neutral-100/0 via-neutral-100/40 backdrop-blur-xs to-neutral-100 h-[100px]" />
<div className="sticky bottom-4 w-full left-0 justify-center flex gap-x-4">
<button
className="bg-[#84e49e] text-xs p-2 rounded text-text-800"
onClick={() => closeSidebar()}
>
Save Changes
</button>
<div
className={`sticky bottom-4 w-full left-0 flex justify-center transition-opacity duration-300 ${
hasSelectedDocuments
? "opacity-100"
: "opacity-0 pointer-events-none"
}`}
>
<button
className="text-sm font-medium py-2 px-4 rounded-full transition-colors bg-black hover:bg-gray-900 text-white"
onClick={() => {
clearSelectedDocuments();
}}
>
{`Remove ${
selectedDocumentIds.length > 0
? selectedDocumentIds.length
: ""
} Source${selectedDocumentIds.length > 1 ? "s" : ""}`}
</button>
</div>
</>
)}
<button
className="bg-error text-xs p-2 rounded text-text-200"
onClick={() => {
clearSelectedDocuments();
closeSidebar();
}}
>
Delete Context
</button>
</div>
</div>
</div>
);

View File

@@ -1,9 +1,13 @@
import React, { useContext, useEffect, useRef, useState } from "react";
import { FiPlusCircle, FiPlus, FiInfo, FiX, FiSearch } from "react-icons/fi";
import { FiPlusCircle, FiPlus, FiInfo, FiX } from "react-icons/fi";
import { ChatInputOption } from "./ChatInputOption";
import { Persona } from "@/app/admin/assistants/interfaces";
import { InputPrompt } from "@/app/admin/prompt-library/interfaces";
import { FilterManager, LlmOverrideManager } from "@/lib/hooks";
import {
FilterManager,
getDisplayNameForModel,
LlmOverrideManager,
} from "@/lib/hooks";
import { SelectedFilterDisplay } from "./SelectedFilterDisplay";
import { useChatContext } from "@/components/context/ChatContext";
import { getFinalLLM } from "@/lib/llm/utils";
@@ -13,10 +17,16 @@ import {
InputBarPreviewImageProvider,
} from "../files/InputBarPreview";
import {
AssistantsIconSkeleton,
CpuIconSkeleton,
FileIcon,
SendIcon,
StopGeneratingIcon,
} from "@/components/icons/icons";
import { IconType } from "react-icons";
import Popup from "../../../components/popup/Popup";
import { LlmTab } from "../modal/configuration/LlmTab";
import { AssistantsTab } from "../modal/configuration/AssistantsTab";
import { DanswerDocument } from "@/lib/search/interfaces";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import {
@@ -30,7 +40,6 @@ import { SettingsContext } from "@/components/settings/SettingsProvider";
import { ChatState } from "../types";
import UnconfiguredProviderText from "@/components/chat_search/UnconfiguredProviderText";
import { useAssistants } from "@/components/context/AssistantsContext";
import AnimatedToggle from "@/components/search/SearchBar";
const MAX_INPUT_HEIGHT = 200;
@@ -59,7 +68,6 @@ export function ChatInputBar({
alternativeAssistant,
chatSessionId,
inputPrompts,
toggleFilters,
}: {
showConfigureAPIKey: () => void;
openModelSettings: () => void;
@@ -82,7 +90,6 @@ export function ChatInputBar({
handleFileUpload: (files: File[]) => void;
textAreaRef: React.RefObject<HTMLTextAreaElement>;
chatSessionId?: string;
toggleFilters?: () => void;
}) {
useEffect(() => {
const textarea = textAreaRef.current;
@@ -363,9 +370,9 @@ export function ChatInputBar({
</div>
)}
{/* <div>
<div>
<SelectedFilterDisplay filterManager={filterManager} />
</div> */}
</div>
<UnconfiguredProviderText showConfigureAPIKey={showConfigureAPIKey} />
@@ -522,6 +529,72 @@ export function ChatInputBar({
suppressContentEditableWarning={true}
/>
<div className="flex items-center space-x-3 mr-12 px-4 pb-2">
<Popup
removePadding
content={(close) => (
<AssistantsTab
llmProviders={llmProviders}
selectedAssistant={selectedAssistant}
onSelect={(assistant) => {
setSelectedAssistant(assistant);
close();
}}
/>
)}
flexPriority="shrink"
position="top"
mobilePosition="top-right"
>
<ChatInputOption
toggle
flexPriority="shrink"
name={
selectedAssistant ? selectedAssistant.name : "Assistants"
}
Icon={AssistantsIconSkeleton as IconType}
/>
</Popup>
<Popup
tab
content={(close, ref) => (
<LlmTab
currentAssistant={alternativeAssistant || selectedAssistant}
openModelSettings={openModelSettings}
currentLlm={
llmOverrideManager.llmOverride.modelName ||
(selectedAssistant
? selectedAssistant.llm_model_version_override ||
llmOverrideManager.globalDefault.modelName ||
llmName
: llmName)
}
close={close}
ref={ref}
llmOverrideManager={llmOverrideManager}
chatSessionId={chatSessionId}
/>
)}
position="top"
>
<ChatInputOption
flexPriority="second"
toggle
name={
settings?.isMobile
? undefined
: getDisplayNameForModel(
llmOverrideManager.llmOverride.modelName ||
(selectedAssistant
? selectedAssistant.llm_model_version_override ||
llmOverrideManager.globalDefault.modelName ||
llmName
: llmName)
)
}
Icon={CpuIconSkeleton}
/>
</Popup>
<ChatInputOption
flexPriority="stiff"
name="File"
@@ -541,17 +614,6 @@ export function ChatInputBar({
input.click();
}}
/>
{toggleFilters && (
<>
<ChatInputOption
flexPriority="stiff"
name="Filters"
Icon={FiSearch}
onClick={toggleFilters}
/>
<AnimatedToggle isOn={false} handleToggle={() => {}} />
</>
)}
</div>
<div className="absolute bottom-2.5 mobile:right-4 desktop:right-10">

View File

@@ -144,7 +144,3 @@ export interface StreamingError {
error: string;
stack_trace: string;
}
export interface FinalContextDocs {
final_context_docs: DanswerDocument[];
}

View File

@@ -18,7 +18,6 @@ import {
RetrievalType,
StreamingError,
ToolCallMetadata,
FinalContextDocs,
} from "./interfaces";
import { Persona } from "../admin/assistants/interfaces";
import { ReadonlyURLSearchParams } from "next/navigation";
@@ -103,7 +102,6 @@ export type PacketType =
| ToolCallMetadata
| BackendMessage
| AnswerPiecePacket
| FinalContextDocs
| DocumentsResponse
| FileChatDisplay
| StreamingError
@@ -149,6 +147,7 @@ export async function* sendMessage({
}): AsyncGenerator<PacketType, void, unknown> {
const documentsAreSelected =
selectedDocumentIds && selectedDocumentIds.length > 0;
const body = JSON.stringify({
alternate_assistant_id: alternateAssistantId,
chat_session_id: chatSessionId,
@@ -642,7 +641,6 @@ export async function useScrollonStream({
endDivRef,
debounceNumber,
mobile,
enableAutoScroll,
}: {
chatState: ChatState;
scrollableDivRef: RefObject<HTMLDivElement>;
@@ -651,7 +649,6 @@ export async function useScrollonStream({
endDivRef: RefObject<HTMLDivElement>;
debounceNumber: number;
mobile?: boolean;
enableAutoScroll?: boolean;
}) {
const mobileDistance = 900; // distance that should "engage" the scroll
const desktopDistance = 500; // distance that should "engage" the scroll
@@ -664,10 +661,6 @@ export async function useScrollonStream({
const previousScroll = useRef<number>(0);
useEffect(() => {
if (!enableAutoScroll) {
return;
}
if (chatState != "input" && scrollableDivRef && scrollableDivRef.current) {
const newHeight: number = scrollableDivRef.current?.scrollTop!;
const heightDifference = newHeight - previousScroll.current;

View File

@@ -1,9 +1,8 @@
import { Citation } from "@/components/search/results/Citation";
import { LoadedDanswerDocument } from "@/lib/search/interfaces";
import React, { memo } from "react";
export const MemoizedLink = memo((props: any) => {
const { node, document, ...rest } = props;
const { node, ...rest } = props;
const value = rest.children;
if (value?.toString().startsWith("*")) {
@@ -11,11 +10,7 @@ export const MemoizedLink = memo((props: any) => {
<div className="flex-none bg-background-800 inline-block rounded-full h-3 w-3 ml-2" />
);
} else if (value?.toString().startsWith("[")) {
return (
<Citation link={rest?.href} document={document as LoadedDanswerDocument}>
{rest.children}
</Citation>
);
return <Citation link={rest?.href}>{rest.children}</Citation>;
} else {
return (
<a

View File

@@ -36,6 +36,8 @@ import "prismjs/themes/prism-tomorrow.css";
import "./custom-code-styles.css";
import { Persona } from "@/app/admin/assistants/interfaces";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { Citation } from "@/components/search/results/Citation";
import { DocumentMetadataBlock } from "@/components/search/DocumentDisplay";
import { LikeFeedback, DislikeFeedback } from "@/components/icons/icons";
import {
@@ -60,10 +62,6 @@ import { MemoizedLink, MemoizedParagraph } from "./MemoizedTextComponents";
import { extractCodeText } from "./codeUtils";
import ToolResult from "../../../components/tools/ToolResult";
import CsvContent from "../../../components/tools/CSVContent";
import FirstSourceCard, {
SeeMoreBlock,
} from "@/components/chat_search/sources/firstsourcecard";
import { getSourceMetadata } from "@/lib/sources";
const TOOLS_WITH_CUSTOM_HANDLING = [
SEARCH_TOOL_NAME,
@@ -164,7 +162,6 @@ export const AIMessage = ({
alternativeAssistant,
docs,
messageId,
documentSelectionToggled,
content,
files,
selectedDocuments,
@@ -181,9 +178,7 @@ export const AIMessage = ({
currentPersona,
otherMessagesCanSwitchTo,
onMessageSelection,
index,
}: {
index?: number;
shared?: boolean;
isActive?: boolean;
continueGenerating?: () => void;
@@ -196,7 +191,6 @@ export const AIMessage = ({
currentPersona: Persona;
messageId: number | null;
content: string | JSX.Element;
documentSelectionToggled?: boolean;
files?: FileDescriptor[];
query?: string;
citedDocuments?: [string, DanswerDocument][] | null;
@@ -282,9 +276,9 @@ export const AIMessage = ({
doc.document_id !== "" &&
index === self.findIndex((d) => d.document_id === doc.document_id)
)
// .filter((doc) => {
// return citedDocumentIds.includes(doc.document_id);
// })
.filter((doc) => {
return citedDocumentIds.includes(doc.document_id);
})
.map((doc: DanswerDocument, ind: number) => {
return {
...doc,
@@ -302,30 +296,7 @@ export const AIMessage = ({
const markdownComponents = useMemo(
() => ({
// a: MemoizedLink,
a: ({ node, ...props }: any) => {
const value = props.children?.toString();
if (value?.startsWith("[") && value?.endsWith("]")) {
const match = value.match(/\[(\d+)\]/);
if (match) {
const index = parseInt(match[1], 10) - 1;
const associatedDoc = docs && docs[index];
const icon = getSourceMetadata(
associatedDoc?.source_type || "web"
).icon({
size: 18,
});
return (
<MemoizedLink {...props} document={{ ...associatedDoc, icon }}>
{props.children}
</MemoizedLink>
);
}
}
return <MemoizedLink {...props}>{props.children}</MemoizedLink>;
},
a: MemoizedLink,
p: MemoizedParagraph,
code: ({ node, className, children, ...props }: any) => {
const codeText = extractCodeText(
@@ -392,7 +363,6 @@ export const AIMessage = ({
!retrievalDisabled && (
<div className="mb-1">
<SearchSummary
index={index || 0}
query={query}
finished={toolCall?.tool_result != undefined}
hasDocs={hasDocs || false}
@@ -452,28 +422,7 @@ export const AIMessage = ({
isRunning={!toolCall.tool_result}
/>
)}
{docs && docs.length > 0 && (
<div className="mt-2 -mx-8 w-full mb-4 flex relative">
<div className="w-full">
<div className="px-8 flex gap-x-2">
{!settings?.isMobile &&
filteredDocs.length > 0 &&
filteredDocs
.slice(0, 2)
.map((doc, ind) => (
<FirstSourceCard doc={doc} key={ind} />
))}
<SeeMoreBlock
documentSelectionToggled={
documentSelectionToggled || false
}
toggleDocumentSelection={toggleDocumentSelection}
uniqueSources={uniqueSources}
/>
</div>
</div>
</div>
)}
{content || files ? (
<>
<FileDisplay files={files || []} />
@@ -489,6 +438,80 @@ export const AIMessage = ({
) : isComplete ? null : (
<></>
)}
{isComplete && docs && docs.length > 0 && (
<div className="mt-2 -mx-8 w-full mb-4 flex relative">
<div className="w-full">
<div className="px-8 flex gap-x-2">
{!settings?.isMobile &&
filteredDocs.length > 0 &&
filteredDocs.slice(0, 2).map((doc, ind) => (
<div
key={doc.document_id}
className={`w-[200px] rounded-lg flex-none transition-all duration-500 hover:bg-background-125 bg-text-100 px-4 pb-2 pt-1 border-b
`}
>
<a
href={doc.link || undefined}
target="_blank"
className="text-sm flex w-full pt-1 gap-x-1.5 overflow-hidden justify-between font-semibold text-text-700"
rel="noreferrer"
>
<Citation link={doc.link} index={ind + 1} />
<p className="shrink truncate ellipsis break-all">
{doc.semantic_identifier ||
doc.document_id}
</p>
<div className="ml-auto flex-none">
{doc.is_internet ? (
<InternetSearchIcon url={doc.link} />
) : (
<SourceIcon
sourceType={doc.source_type}
iconSize={18}
/>
)}
</div>
</a>
<div className="flex overscroll-x-scroll mt-.5">
<DocumentMetadataBlock document={doc} />
</div>
<div className="line-clamp-3 text-xs break-words pt-1">
{doc.blurb}
</div>
</div>
))}
<div
onClick={() => {
if (toggleDocumentSelection) {
toggleDocumentSelection();
}
}}
key={-1}
className="cursor-pointer w-[200px] rounded-lg flex-none transition-all duration-500 hover:bg-background-125 bg-text-100 px-4 py-2 border-b"
>
<div className="text-sm flex justify-between font-semibold text-text-700">
<p className="line-clamp-1">See context</p>
<div className="flex gap-x-1">
{uniqueSources.map((sourceType, ind) => {
return (
<div key={ind} className="flex-none">
<SourceIcon
sourceType={sourceType}
iconSize={18}
/>
</div>
);
})}
</div>
</div>
<div className="line-clamp-3 text-xs break-words pt-1">
See more
</div>
</div>
</div>
</div>
</div>
)}
</div>
{handleFeedback &&
@@ -796,7 +819,6 @@ export const HumanMessage = ({
outline-none
placeholder-gray-400
resize-none
text-text-editing-message
pl-4
overflow-y-auto
pr-12
@@ -855,6 +877,7 @@ export const HumanMessage = ({
py-2
px-3
w-fit
bg-hover
bg-background-strong
text-sm
rounded-lg
@@ -880,13 +903,15 @@ export const HumanMessage = ({
<TooltipProvider delayDuration={1000}>
<Tooltip>
<TooltipTrigger>
<HoverableIcon
icon={<FiEdit2 className="text-gray-600" />}
<button
className="hover:bg-hover p-1.5 rounded"
onClick={() => {
setIsEditing(true);
setIsHovered(false);
}}
/>
>
<FiEdit2 className="!h-4 !w-4" />
</button>
</TooltipTrigger>
<TooltipContent>Edit</TooltipContent>
</Tooltip>

View File

@@ -41,7 +41,6 @@ export function ShowHideDocsButton({
}
export function SearchSummary({
index,
query,
hasDocs,
finished,
@@ -49,7 +48,6 @@ export function SearchSummary({
handleShowRetrieved,
handleSearchQueryEdit,
}: {
index: number;
finished: boolean;
query: string;
hasDocs: boolean;
@@ -100,13 +98,7 @@ export function SearchSummary({
!text-sm !line-clamp-1 !break-all px-0.5`}
ref={searchingForRef}
>
{index !== 1 && (
<>
{finished ? "Searched" : "Searching"} for: <i> {finalQuery}</i>
</>
)}
{index === 1 && <>{finished ? "Ran search" : "Searching"}</>}
{finished ? "Searched" : "Searching"} for: <i> {finalQuery}</i>
</div>
</div>
);
@@ -194,7 +186,7 @@ export function SearchSummary({
<FiEdit2 />
</button>
</TooltipTrigger>
<TooltipContent>Specify Search Term</TooltipContent>
<TooltipContent>Edit Search</TooltipContent>
</Tooltip>
</TooltipProvider>
)}

View File

@@ -9,9 +9,6 @@ import { setUserDefaultModel } from "@/lib/users/UserSettings";
import { useRouter } from "next/navigation";
import { PopupSpec } from "@/components/admin/connectors/Popup";
import { useUser } from "@/components/user/UserProvider";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/admin/connectors/Field";
export function SetDefaultModelModal({
setPopup,
@@ -26,8 +23,7 @@ export function SetDefaultModelModal({
onClose: () => void;
defaultModel: string | null;
}) {
console.log("defaultModel", defaultModel);
const { refreshUser, user, updateUserAutoScroll } = useUser();
const { refreshUser } = useUser();
const containerRef = useRef<HTMLDivElement>(null);
const messageRef = useRef<HTMLDivElement>(null);
@@ -100,8 +96,6 @@ export function SetDefaultModelModal({
const router = useRouter();
const handleChangedefaultModel = async (defaultModel: string | null) => {
try {
console.log("defaultModel", defaultModel);
const response = await setUserDefaultModel(defaultModel);
if (response.ok) {
@@ -128,45 +122,15 @@ export function SetDefaultModelModal({
(llmProvider) => llmProvider.is_default_provider
);
console.log(user?.auto_scroll);
return (
<Modal onOutsideClick={onClose} width="rounded-lg bg-white max-w-xl">
<>
<div className="flex mb-4">
<h2 className="text-2xl text-emphasis font-bold flex my-auto">
User settings
Set Default Model
</h2>
</div>
<div className="flex flex-col gap-y-2">
<div className="flex items-center gap-x-2">
<Switch
checked={user?.auto_scroll === true}
onCheckedChange={(checked) => {
updateUserAutoScroll(checked);
}}
/>
<Label className="text-sm">Enable auto-scroll</Label>
</div>
<div className="flex items-center gap-x-2">
<Switch
checked={user?.auto_scroll === null}
onCheckedChange={(checked) => {
updateUserAutoScroll(checked ? null : false);
}}
/>
<Label className="text-sm">
Use system default for auto-scroll
</Label>
</div>
</div>
<Separator />
<h3 className="text-lg text-emphasis font-bold">
Default model for assistants
</h3>
<Text className="mb-4">
Choose a Large Language Model (LLM) to serve as the default for
assistants that don&apos;t have a default model assigned.

View File

@@ -76,7 +76,7 @@ export function AssistantsTab({
items={assistants.map((a) => a.id.toString())}
strategy={verticalListSortingStrategy}
>
<div className="px-4 pb-2 max-h-[500px] default-scrollbar overflow-y-scroll overflow-x-hidden my-3 grid grid-cols-1 gap-4">
<div className="px-4 pb-2 max-h-[500px] include-scrollbar overflow-y-scroll my-3 grid grid-cols-1 gap-4">
{assistants.map((assistant) => (
<DraggableAssistantCard
key={assistant.id.toString()}

View File

@@ -32,7 +32,6 @@ export default async function Page(props: {
defaultAssistantId,
shouldShowWelcomeModal,
userInputPrompts,
ccPairs,
} = data;
return (
@@ -45,9 +44,6 @@ export default async function Page(props: {
value={{
chatSessions,
availableSources,
ccPairs,
documentSets,
tags,
availableDocumentSets: documentSets,
availableTags: tags,
llmProviders,

View File

@@ -1,622 +0,0 @@
import React, { useState } from "react";
import { DocumentSet, Tag, ValidSources } from "@/lib/types";
import { SourceMetadata } from "@/lib/search/interfaces";
import {
GearIcon,
InfoIcon,
MinusIcon,
PlusCircleIcon,
PlusIcon,
defaultTailwindCSS,
} from "@/components/icons/icons";
import { HoverPopup } from "@/components/HoverPopup";
import {
FiBook,
FiBookmark,
FiFilter,
FiMap,
FiTag,
FiX,
} from "react-icons/fi";
import { DateRangeSelector } from "@/components/search/DateRangeSelector";
import { DateRangePickerValue } from "@/app/ee/admin/performance/DateRangeSelector";
import { listSourceMetadata } from "@/lib/sources";
import { SourceIcon } from "@/components/SourceIcon";
import { TagFilter } from "@/components/search/filtering/TagFilter";
import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverTrigger } from "@/components/ui/popover";
import { PopoverContent } from "@radix-ui/react-popover";
import { CalendarIcon } from "lucide-react";
import { buildDateString, getTimeAgoString } from "@/lib/dateUtils";
import { Separator } from "@/components/ui/separator";
import { FilterDropdown } from "@/components/search/filtering/FilterDropdown";
const SectionTitle = ({ children }: { children: string }) => (
<div className="font-bold text-xs mt-2 flex">{children}</div>
);
export interface SourceSelectorProps {
timeRange: DateRangePickerValue | null;
setTimeRange: React.Dispatch<
React.SetStateAction<DateRangePickerValue | null>
>;
showDocSidebar?: boolean;
selectedSources: SourceMetadata[];
setSelectedSources: React.Dispatch<React.SetStateAction<SourceMetadata[]>>;
selectedDocumentSets: string[];
setSelectedDocumentSets: React.Dispatch<React.SetStateAction<string[]>>;
selectedTags: Tag[];
setSelectedTags: React.Dispatch<React.SetStateAction<Tag[]>>;
availableDocumentSets: DocumentSet[];
existingSources: ValidSources[];
availableTags: Tag[];
toggleFilters: () => void;
filtersUntoggled: boolean;
tagsOnLeft: boolean;
}
export function SourceSelector({
timeRange,
setTimeRange,
selectedSources,
setSelectedSources,
selectedDocumentSets,
setSelectedDocumentSets,
selectedTags,
setSelectedTags,
availableDocumentSets,
existingSources,
availableTags,
showDocSidebar,
toggleFilters,
filtersUntoggled,
tagsOnLeft,
}: SourceSelectorProps) {
const handleSelect = (source: SourceMetadata) => {
setSelectedSources((prev: SourceMetadata[]) => {
if (
prev.map((source) => source.internalName).includes(source.internalName)
) {
return prev.filter((s) => s.internalName !== source.internalName);
} else {
return [...prev, source];
}
});
};
const handleDocumentSetSelect = (documentSetName: string) => {
setSelectedDocumentSets((prev: string[]) => {
if (prev.includes(documentSetName)) {
return prev.filter((s) => s !== documentSetName);
} else {
return [...prev, documentSetName];
}
});
};
let allSourcesSelected = selectedSources.length > 0;
const toggleAllSources = () => {
if (allSourcesSelected) {
setSelectedSources([]);
} else {
const allSources = listSourceMetadata().filter((source) =>
existingSources.includes(source.internalName)
);
setSelectedSources(allSources);
}
};
return (
<div
className={`hidden ${
showDocSidebar ? "4xl:block" : "!block"
} duration-1000 flex ease-out transition-all transform origin-top-right`}
>
<button onClick={() => toggleFilters()} className="flex text-emphasis">
<h2 className="font-bold my-auto">Filters</h2>
<FiFilter className="my-auto ml-2" size="16" />
</button>
{!filtersUntoggled && (
<>
<Separator />
<Popover>
<PopoverTrigger asChild>
<div className="cursor-pointer">
<SectionTitle>Time Range</SectionTitle>
<p className="text-sm text-default mt-2">
{getTimeAgoString(timeRange?.from!) || "Select a time range"}
</p>
</div>
</PopoverTrigger>
<PopoverContent
className="bg-background-search-filter border-border border rounded-md z-[200] p-0"
align="start"
>
<Calendar
mode="range"
selected={
timeRange
? {
from: new Date(timeRange.from),
to: new Date(timeRange.to),
}
: undefined
}
onSelect={(daterange) => {
const initialDate = daterange?.from || new Date();
const endDate = daterange?.to || new Date();
setTimeRange({
from: initialDate,
to: endDate,
selectValue: timeRange?.selectValue || "",
});
}}
className="rounded-md "
/>
</PopoverContent>
</Popover>
{availableTags.length > 0 && (
<>
<div className="mt-4 mb-2">
<SectionTitle>Tags</SectionTitle>
</div>
<TagFilter
showTagsOnLeft={true}
tags={availableTags}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
/>
</>
)}
{existingSources.length > 0 && (
<div className="mt-4">
<div className="flex w-full gap-x-2 items-center">
<div className="font-bold text-xs mt-2 flex items-center gap-x-2">
<p>Sources</p>
<input
type="checkbox"
checked={allSourcesSelected}
onChange={toggleAllSources}
className="my-auto form-checkbox h-3 w-3 text-primary border-background-900 rounded"
/>
</div>
</div>
<div className="px-1">
{listSourceMetadata()
.filter((source) =>
existingSources.includes(source.internalName)
)
.map((source) => (
<div
key={source.internalName}
className={
"flex cursor-pointer w-full items-center " +
"py-1.5 my-1.5 rounded-lg px-2 select-none " +
(selectedSources
.map((source) => source.internalName)
.includes(source.internalName)
? "bg-hover"
: "hover:bg-hover-light")
}
onClick={() => handleSelect(source)}
>
<SourceIcon
sourceType={source.internalName}
iconSize={16}
/>
<span className="ml-2 text-sm text-default">
{source.displayName}
</span>
</div>
))}
</div>
</div>
)}
{availableDocumentSets.length > 0 && (
<>
<div className="mt-4">
<SectionTitle>Knowledge Sets</SectionTitle>
</div>
<div className="px-1">
{availableDocumentSets.map((documentSet) => (
<div key={documentSet.name} className="my-1.5 flex">
<div
key={documentSet.name}
className={
"flex cursor-pointer w-full items-center " +
"py-1.5 rounded-lg px-2 " +
(selectedDocumentSets.includes(documentSet.name)
? "bg-hover"
: "hover:bg-hover-light")
}
onClick={() => handleDocumentSetSelect(documentSet.name)}
>
<HoverPopup
mainContent={
<div className="flex my-auto mr-2">
<InfoIcon className={defaultTailwindCSS} />
</div>
}
popupContent={
<div className="text-sm w-64">
<div className="flex font-medium">Description</div>
<div className="mt-1">
{documentSet.description}
</div>
</div>
}
classNameModifications="-ml-2"
/>
<span className="text-sm">{documentSet.name}</span>
</div>
</div>
))}
</div>
</>
)}
</>
)}
</div>
);
}
export function SelectedBubble({
children,
onClick,
}: {
children: string | JSX.Element;
onClick: () => void;
}) {
return (
<div
className={
"flex cursor-pointer items-center border border-border " +
"py-1 my-1.5 rounded-lg px-2 w-fit hover:bg-hover"
}
onClick={onClick}
>
{children}
<FiX className="ml-2" size={14} />
</div>
);
}
export function HorizontalFilters({
timeRange,
setTimeRange,
selectedSources,
setSelectedSources,
selectedDocumentSets,
setSelectedDocumentSets,
availableDocumentSets,
existingSources,
}: SourceSelectorProps) {
const handleSourceSelect = (source: SourceMetadata) => {
setSelectedSources((prev: SourceMetadata[]) => {
const prevSourceNames = prev.map((source) => source.internalName);
if (prevSourceNames.includes(source.internalName)) {
return prev.filter((s) => s.internalName !== source.internalName);
} else {
return [...prev, source];
}
});
};
const handleDocumentSetSelect = (documentSetName: string) => {
setSelectedDocumentSets((prev: string[]) => {
if (prev.includes(documentSetName)) {
return prev.filter((s) => s !== documentSetName);
} else {
return [...prev, documentSetName];
}
});
};
const allSources = listSourceMetadata();
const availableSources = allSources.filter((source) =>
existingSources.includes(source.internalName)
);
return (
<div>
<div className="flex gap-x-3">
<div className="w-64">
<DateRangeSelector value={timeRange} onValueChange={setTimeRange} />
</div>
<FilterDropdown
options={availableSources.map((source) => {
return {
key: source.displayName,
display: (
<>
<SourceIcon sourceType={source.internalName} iconSize={16} />
<span className="ml-2 text-sm">{source.displayName}</span>
</>
),
};
})}
selected={selectedSources.map((source) => source.displayName)}
handleSelect={(option) =>
handleSourceSelect(
allSources.find((source) => source.displayName === option.key)!
)
}
icon={
<div className="my-auto mr-2 w-[16px] h-[16px]">
<FiMap size={16} />
</div>
}
defaultDisplay="All Sources"
/>
<FilterDropdown
options={availableDocumentSets.map((documentSet) => {
return {
key: documentSet.name,
display: (
<>
<div className="my-auto">
<FiBookmark />
</div>
<span className="ml-2 text-sm">{documentSet.name}</span>
</>
),
};
})}
selected={selectedDocumentSets}
handleSelect={(option) => handleDocumentSetSelect(option.key)}
icon={
<div className="my-auto mr-2 w-[16px] h-[16px]">
<FiBook size={16} />
</div>
}
defaultDisplay="All Document Sets"
/>
</div>
<div className="flex pb-4 mt-2 h-12">
<div className="flex flex-wrap gap-x-2">
{timeRange && timeRange.selectValue && (
<SelectedBubble onClick={() => setTimeRange(null)}>
<div className="text-sm flex">{timeRange.selectValue}</div>
</SelectedBubble>
)}
{existingSources.length > 0 &&
selectedSources.map((source) => (
<SelectedBubble
key={source.internalName}
onClick={() => handleSourceSelect(source)}
>
<>
<SourceIcon sourceType={source.internalName} iconSize={16} />
<span className="ml-2 text-sm">{source.displayName}</span>
</>
</SelectedBubble>
))}
{selectedDocumentSets.length > 0 &&
selectedDocumentSets.map((documentSetName) => (
<SelectedBubble
key={documentSetName}
onClick={() => handleDocumentSetSelect(documentSetName)}
>
<>
<div>
<FiBookmark />
</div>
<span className="ml-2 text-sm">{documentSetName}</span>
</>
</SelectedBubble>
))}
</div>
</div>
</div>
);
}
export function HorizontalSourceSelector({
timeRange,
setTimeRange,
selectedSources,
setSelectedSources,
selectedDocumentSets,
setSelectedDocumentSets,
selectedTags,
setSelectedTags,
availableDocumentSets,
existingSources,
availableTags,
}: SourceSelectorProps) {
const handleSourceSelect = (source: SourceMetadata) => {
setSelectedSources((prev: SourceMetadata[]) => {
if (prev.map((s) => s.internalName).includes(source.internalName)) {
return prev.filter((s) => s.internalName !== source.internalName);
} else {
return [...prev, source];
}
});
};
const handleDocumentSetSelect = (documentSetName: string) => {
setSelectedDocumentSets((prev: string[]) => {
if (prev.includes(documentSetName)) {
return prev.filter((s) => s !== documentSetName);
} else {
return [...prev, documentSetName];
}
});
};
const handleTagSelect = (tag: Tag) => {
setSelectedTags((prev: Tag[]) => {
if (
prev.some(
(t) => t.tag_key === tag.tag_key && t.tag_value === tag.tag_value
)
) {
return prev.filter(
(t) => !(t.tag_key === tag.tag_key && t.tag_value === tag.tag_value)
);
} else {
return [...prev, tag];
}
});
};
const resetSources = () => {
setSelectedSources([]);
};
const resetDocuments = () => {
setSelectedDocumentSets([]);
};
const resetTags = () => {
setSelectedTags([]);
};
return (
<div className="flex flex-nowrap space-x-2">
<Popover>
<PopoverTrigger asChild>
<div
className={`
border
max-w-36
border-border
rounded-lg
max-h-96
overflow-y-scroll
overscroll-contain
px-3
text-sm
py-1.5
select-none
cursor-pointer
w-fit
gap-x-1
hover:bg-hover
flex
items-center
bg-background-search-filter
`}
>
<CalendarIcon className="h-4 w-4" />
{timeRange?.from ? getTimeAgoString(timeRange.from) : "Since"}
</div>
</PopoverTrigger>
<PopoverContent
className="bg-background-search-filter border-border border rounded-md z-[200] p-0"
align="start"
>
<Calendar
mode="range"
selected={
timeRange
? { from: new Date(timeRange.from), to: new Date(timeRange.to) }
: undefined
}
onSelect={(daterange) => {
const initialDate = daterange?.from || new Date();
const endDate = daterange?.to || new Date();
setTimeRange({
from: initialDate,
to: endDate,
selectValue: timeRange?.selectValue || "",
});
}}
className="rounded-md"
/>
</PopoverContent>
</Popover>
{existingSources.length > 0 && (
<FilterDropdown
backgroundColor="bg-background-search-filter"
options={listSourceMetadata()
.filter((source) => existingSources.includes(source.internalName))
.map((source) => ({
key: source.internalName,
display: (
<>
<SourceIcon sourceType={source.internalName} iconSize={16} />
<span className="ml-2 text-sm">{source.displayName}</span>
</>
),
}))}
selected={selectedSources.map((source) => source.internalName)}
handleSelect={(option) =>
handleSourceSelect(
listSourceMetadata().find((s) => s.internalName === option.key)!
)
}
icon={<FiMap size={16} />}
defaultDisplay="Sources"
dropdownColor="bg-background-search-filter-dropdown"
width="w-fit ellipsis truncate"
resetValues={resetSources}
dropdownWidth="w-40"
optionClassName="truncate w-full break-all ellipsis"
/>
)}
{availableDocumentSets.length > 0 && (
<FilterDropdown
backgroundColor="bg-background-search-filter"
options={availableDocumentSets.map((documentSet) => ({
key: documentSet.name,
display: <>{documentSet.name}</>,
}))}
selected={selectedDocumentSets}
handleSelect={(option) => handleDocumentSetSelect(option.key)}
icon={<FiBook size={16} />}
defaultDisplay="Sets"
resetValues={resetDocuments}
width="w-fit max-w-24 text-ellipsis truncate"
dropdownColor="bg-background-search-filter-dropdown"
dropdownWidth="max-w-36 w-fit"
optionClassName="truncate w-full break-all"
/>
)}
{availableTags.length > 0 && (
<FilterDropdown
backgroundColor="bg-background-search-filter"
options={availableTags.map((tag) => ({
key: `${tag.tag_key}=${tag.tag_value}`,
display: (
<span className="text-sm">
{tag.tag_key}
<b>=</b>
{tag.tag_value}
</span>
),
}))}
selected={selectedTags.map(
(tag) => `${tag.tag_key}=${tag.tag_value}`
)}
handleSelect={(option) => {
const [tag_key, tag_value] = option.key.split("=");
const selectedTag = availableTags.find(
(tag) => tag.tag_key === tag_key && tag.tag_value === tag_value
);
if (selectedTag) {
handleTagSelect(selectedTag);
}
}}
icon={<FiTag size={16} />}
defaultDisplay="Tags"
resetValues={resetTags}
dropdownColor="bg-background-search-filter-dropdown"
width="w-fit max-w-24 ellipsis truncate"
dropdownWidth="max-w-80 w-fit"
optionClassName="truncate w-full break-all ellipsis"
/>
)}
</div>
);
}

View File

@@ -21,7 +21,9 @@ export default function FixedLogo({
return (
<>
<Link
href="/chat"
href={
settings && settings.default_page === "chat" ? "/chat" : "/search"
}
className="fixed cursor-pointer flex z-40 left-2.5 top-2"
>
<div className="max-w-[200px] mobile:hidden flex items-center gap-x-1 my-auto">
@@ -47,13 +49,7 @@ export default function FixedLogo({
</div>
</Link>
<div className="mobile:hidden fixed left-2.5 bottom-4">
<FiSidebar
className={`${
backgroundToggled
? "text-text-mobile-sidebar-toggled"
: "text-text-mobile-sidebar-untoggled"
}`}
/>
<FiSidebar className="text-text-mobile-sidebar" />
</div>
</>
);

View File

@@ -1,7 +1,90 @@
"use client";
import React, { ReactNode, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import React, { ReactNode, useContext, useEffect, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { ChatIcon, SearchIcon } from "@/components/icons/icons";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import KeyboardSymbol from "@/lib/browserUtilities";
const ToggleSwitch = () => {
const commandSymbol = KeyboardSymbol();
const pathname = usePathname();
const router = useRouter();
const settings = useContext(SettingsContext);
const [activeTab, setActiveTab] = useState(() => {
return pathname == "/search" ? "search" : "chat";
});
const [isInitialLoad, setIsInitialLoad] = useState(true);
useEffect(() => {
const newTab = pathname === "/search" ? "search" : "chat";
setActiveTab(newTab);
localStorage.setItem("activeTab", newTab);
setIsInitialLoad(false);
}, [pathname]);
const handleTabChange = (tab: string) => {
setActiveTab(tab);
localStorage.setItem("activeTab", tab);
if (settings?.isMobile && window) {
window.location.href = tab;
} else {
router.push(tab === "search" ? "/search" : "/chat");
}
};
return (
<div className="bg-background-toggle mobile:mt-8 flex rounded-full p-1">
<div
className={`absolute mobile:mt-8 top-1 bottom-1 ${
activeTab === "chat" ? "w-[45%]" : "w-[50%]"
} bg-white rounded-full shadow ${
isInitialLoad ? "" : "transition-transform duration-300 ease-in-out"
} ${activeTab === "chat" ? "translate-x-[115%]" : "translate-x-[1%]"}`}
/>
<button
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors duration-300 ease-in-out flex items-center relative z-10 ${
activeTab === "search"
? "text-text-application-toggled"
: "text-text-application-untoggled hover:text-text-application-untoggled-hover"
}`}
onClick={() => handleTabChange("search")}
>
<SearchIcon size={16} className="mr-2" />
<div className="flex items-center">
Search
<div className="ml-2 flex content-center">
<span className="leading-none pb-[1px] my-auto">
{commandSymbol}
</span>
<span className="my-auto">S</span>
</div>
</div>
</button>
<button
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors duration-300 ease-in-out flex items-center relative z-10 ${
activeTab === "chat"
? "text-text-application-toggled"
: "text-text-application-untoggled hover:text-text-application-untoggled-hover"
}`}
onClick={() => handleTabChange("chat")}
>
<ChatIcon size={16} className="mr-2" />
<div className="items-end flex">
Chat
<div className="ml-2 flex content-center">
<span className="leading-none pb-[1px] my-auto">
{commandSymbol}
</span>
<span className="my-auto">D</span>
</div>
</div>
</button>
</div>
);
};
export default function FunctionalWrapper({
initiallyToggled,
@@ -45,6 +128,12 @@ export default function FunctionalWrapper({
window.removeEventListener("keydown", handleKeyDown);
};
}, [router]);
const combinedSettings = useContext(SettingsContext);
const settings = combinedSettings?.settings;
const chatBannerPresent =
combinedSettings?.enterpriseSettings?.custom_header_content;
const twoLines =
combinedSettings?.enterpriseSettings?.two_lines_for_chat_header;
const [toggledSidebar, setToggledSidebar] = useState(initiallyToggled);
@@ -56,7 +145,24 @@ export default function FunctionalWrapper({
return (
<>
{" "}
{(!settings ||
(settings.search_page_enabled && settings.chat_page_enabled)) && (
<div
className={`mobile:hidden z-30 flex fixed ${
chatBannerPresent ? (twoLines ? "top-20" : "top-14") : "top-4"
} left-1/2 transform -translate-x-1/2`}
>
<div
style={{ transition: "width 0.30s ease-out" }}
className={`flex-none overflow-y-hidden bg-background-100 transition-all bg-opacity-80 duration-300 ease-in-out h-full
${toggledSidebar ? "w-[250px] " : "w-[0px]"}`}
/>
<div className="relative">
<ToggleSwitch />
</div>
</div>
)}
<div className="overscroll-y-contain overflow-y-scroll overscroll-contain left-0 top-0 w-full h-svh">
{content(toggledSidebar, toggle)}
</div>

View File

@@ -1,391 +0,0 @@
import { containsObject, objectsAreEquivalent } from "@/lib/contains";
import { useEffect, useRef, useState } from "react";
import debounce from "lodash/debounce";
import { getValidTags } from "@/lib/tags/tagUtils";
import { DocumentSet, Tag, ValidSources } from "@/lib/types";
import { SourceMetadata } from "@/lib/search/interfaces";
import { InfoIcon, defaultTailwindCSS } from "@/components/icons/icons";
import { HoverPopup } from "@/components/HoverPopup";
import { FiFilter, FiTag, FiX } from "react-icons/fi";
import { DateRangePickerValue } from "@/app/ee/admin/performance/DateRangeSelector";
import { listSourceMetadata } from "@/lib/sources";
import { SourceIcon } from "@/components/SourceIcon";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverTrigger,
PopoverContent,
} from "@/components/ui/popover";
import { getTimeAgoString } from "@/lib/dateUtils";
import { Separator } from "@/components/ui/separator";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Divide } from "lucide-react";
const SectionTitle = ({ children }: { children: string }) => (
<CardHeader className="pb-2">
<CardTitle className="text-sm font-semibold">{children}</CardTitle>
</CardHeader>
);
export interface SourceSelectorProps {
timeRange: DateRangePickerValue | null;
setTimeRange: React.Dispatch<
React.SetStateAction<DateRangePickerValue | null>
>;
showDocSidebar?: boolean;
selectedSources: SourceMetadata[];
setSelectedSources: React.Dispatch<React.SetStateAction<SourceMetadata[]>>;
selectedDocumentSets: string[];
setSelectedDocumentSets: React.Dispatch<React.SetStateAction<string[]>>;
selectedTags: Tag[];
setSelectedTags: React.Dispatch<React.SetStateAction<Tag[]>>;
availableDocumentSets: DocumentSet[];
existingSources: ValidSources[];
availableTags: Tag[];
toggleFilters: () => void;
filtersUntoggled: boolean;
tagsOnLeft: boolean;
}
export function SourceSelector({
timeRange,
setTimeRange,
selectedSources,
setSelectedSources,
selectedDocumentSets,
setSelectedDocumentSets,
selectedTags,
setSelectedTags,
availableDocumentSets,
existingSources,
availableTags,
showDocSidebar,
toggleFilters,
filtersUntoggled,
tagsOnLeft,
}: SourceSelectorProps) {
const handleSelect = (source: SourceMetadata) => {
setSelectedSources((prev: SourceMetadata[]) => {
if (
prev.map((source) => source.internalName).includes(source.internalName)
) {
return prev.filter((s) => s.internalName !== source.internalName);
} else {
return [...prev, source];
}
});
};
const handleDocumentSetSelect = (documentSetName: string) => {
setSelectedDocumentSets((prev: string[]) => {
if (prev.includes(documentSetName)) {
return prev.filter((s) => s !== documentSetName);
} else {
return [...prev, documentSetName];
}
});
};
let allSourcesSelected = selectedSources.length > 0;
const toggleAllSources = () => {
if (allSourcesSelected) {
setSelectedSources([]);
} else {
const allSources = listSourceMetadata().filter((source) =>
existingSources.includes(source.internalName)
);
setSelectedSources(allSources);
}
};
return (
<div>
{!filtersUntoggled && (
<CardContent className="space-y-2">
<div>
<SectionTitle>Time Range</SectionTitle>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start text-left font-normal"
>
<span>
{getTimeAgoString(timeRange?.from!) ||
"Select a time range"}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="range"
selected={
timeRange
? {
from: new Date(timeRange.from),
to: new Date(timeRange.to),
}
: undefined
}
onSelect={(daterange) => {
const initialDate = daterange?.from || new Date();
const endDate = daterange?.to || new Date();
setTimeRange({
from: initialDate,
to: endDate,
selectValue: timeRange?.selectValue || "",
});
}}
className="rounded-md"
/>
</PopoverContent>
</Popover>
</div>
{availableTags.length > 0 && (
<div>
<SectionTitle>Tags</SectionTitle>
<TagFilter
showTagsOnLeft={true}
tags={availableTags}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
/>
</div>
)}
{existingSources.length > 0 && (
<div>
<SectionTitle>Sources</SectionTitle>
<div className="space-y-0">
<div className="flex items-center space-x-2 cursor-pointer hover:bg-background-200 rounded-md p-2">
<Checkbox
id="select-all-sources"
checked={allSourcesSelected}
onCheckedChange={toggleAllSources}
/>
<label
htmlFor="select-all-sources"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Select All
</label>
</div>
{listSourceMetadata()
.filter((source) =>
existingSources.includes(source.internalName)
)
.map((source) => (
<div
key={source.internalName}
className="flex items-center space-x-2 cursor-pointer hover:bg-background-200 rounded-md p-2"
onClick={() => handleSelect(source)}
>
<Checkbox
checked={selectedSources
.map((s) => s.internalName)
.includes(source.internalName)}
onCheckedChange={() => handleSelect(source)}
/>
<SourceIcon
sourceType={source.internalName}
iconSize={16}
/>
<span className="text-sm">{source.displayName}</span>
</div>
))}
</div>
</div>
)}
{availableDocumentSets.length > 0 && (
<div>
<SectionTitle>Knowledge Sets</SectionTitle>
<div className="space-y-2">
{availableDocumentSets.map((documentSet) => (
<div
key={documentSet.name}
className="flex items-center space-x-2 cursor-pointer hover:bg-accent rounded-md p-2"
onClick={() => handleDocumentSetSelect(documentSet.name)}
>
<Checkbox
checked={selectedDocumentSets.includes(documentSet.name)}
onCheckedChange={() =>
handleDocumentSetSelect(documentSet.name)
}
/>
<HoverPopup
mainContent={
<InfoIcon className={`${defaultTailwindCSS} h-4 w-4`} />
}
popupContent={
<div className="text-sm w-64">
<div className="font-medium">Description</div>
<div className="mt-1">{documentSet.description}</div>
</div>
}
/>
<span className="text-sm">{documentSet.name}</span>
</div>
))}
</div>
</div>
)}
</CardContent>
)}
</div>
);
}
export function TagFilter({
tags,
selectedTags,
setSelectedTags,
showTagsOnLeft = false,
}: {
tags: Tag[];
selectedTags: Tag[];
setSelectedTags: React.Dispatch<React.SetStateAction<Tag[]>>;
showTagsOnLeft?: boolean;
}) {
const [filterValue, setFilterValue] = useState("");
const [tagOptionsAreVisible, setTagOptionsAreVisible] = useState(false);
const [filteredTags, setFilteredTags] = useState<Tag[]>(tags);
const inputRef = useRef<HTMLInputElement>(null);
const popupRef = useRef<HTMLDivElement>(null);
const onSelectTag = (tag: Tag) => {
setSelectedTags((prev) => {
if (containsObject(prev, tag)) {
return prev.filter((t) => !objectsAreEquivalent(t, tag));
} else {
return [...prev, tag];
}
});
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
popupRef.current &&
!popupRef.current.contains(event.target as Node) &&
inputRef.current &&
!inputRef.current.contains(event.target as Node)
) {
setTagOptionsAreVisible(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
const debouncedFetchTags = useRef(
debounce(async (value: string) => {
if (value) {
const fetchedTags = await getValidTags(value);
setFilteredTags(fetchedTags);
} else {
setFilteredTags(tags);
}
}, 50)
).current;
useEffect(() => {
debouncedFetchTags(filterValue);
return () => {
debouncedFetchTags.cancel();
};
}, [filterValue, tags, debouncedFetchTags]);
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setFilterValue(event.target.value);
};
return (
<div className="space-y-2">
<Input
ref={inputRef}
placeholder="Find a tag"
value={filterValue}
onChange={handleFilterChange}
onFocus={() => setTagOptionsAreVisible(true)}
/>
{selectedTags.length > 0 && (
<div className="space-y-2">
<div className="flex flex-wrap gap-2">
{selectedTags.map((tag) => (
<Badge
key={tag.tag_key + tag.tag_value}
variant="secondary"
className="cursor-pointer"
onClick={() => onSelectTag(tag)}
>
{tag.tag_key}={tag.tag_value}
<FiX className="ml-1 h-3 w-3" />
</Badge>
))}
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setSelectedTags([])}
className="text-xs"
>
Clear all
</Button>
</div>
)}
{tagOptionsAreVisible && (
<Popover>
<PopoverContent
className="w-72"
align={showTagsOnLeft ? "start" : "end"}
>
<div ref={popupRef}>
<div className="flex items-center border-b pb-2 mb-2">
<FiTag className="mr-2" />
<span className="font-medium text-sm">Tags</span>
</div>
<div className="max-h-96 overflow-y-auto space-y-1">
{filteredTags.length > 0 ? (
filteredTags.map((tag) => (
<div
key={tag.tag_key + tag.tag_value}
onClick={() => onSelectTag(tag)}
className={`
text-sm
cursor-pointer
p-2
rounded-md
${
selectedTags.includes(tag)
? "bg-accent"
: "hover:bg-accent"
}
`}
>
{tag.tag_key}={tag.tag_value}
</div>
))
) : (
<div className="text-sm p-2">No matching tags found</div>
)}
</div>
</div>
</PopoverContent>
</Popover>
)}
</div>
);
}

View File

@@ -55,7 +55,6 @@ export function WhitelabelingForm() {
<div>
<Formik
initialValues={{
auto_scroll: enterpriseSettings?.auto_scroll || false,
application_name: enterpriseSettings?.application_name || null,
use_custom_logo: enterpriseSettings?.use_custom_logo || false,
use_custom_logotype: enterpriseSettings?.use_custom_logotype || false,
@@ -72,7 +71,6 @@ export function WhitelabelingForm() {
enterpriseSettings?.enable_consent_screen || false,
}}
validationSchema={Yup.object().shape({
auto_scroll: Yup.boolean().nullable(),
application_name: Yup.string().nullable(),
use_custom_logo: Yup.boolean().required(),
use_custom_logotype: Yup.boolean().required(),

View File

@@ -260,29 +260,28 @@
}
}
.default-scrollbar::-webkit-scrollbar {
.include-scrollbar::-webkit-scrollbar {
width: 6px;
}
.default-scrollbar::-webkit-scrollbar-track {
.include-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
}
.default-scrollbar::-webkit-scrollbar-thumb {
.include-scrollbar::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.default-scrollbar::-webkit-scrollbar-thumb:hover {
.include-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
.default-scrollbar {
.include-scrollbar {
scrollbar-width: thin;
scrollbar-color: #888 transparent;
overflow: overlay;
overflow-y: scroll;
overflow-x: hidden;
}
.inputscroll::-webkit-scrollbar-track {

View File

@@ -1,5 +1,15 @@
import { fetchSettingsSS } from "@/components/settings/lib";
import { redirect } from "next/navigation";
export default async function Page() {
redirect("/chat");
const settings = await fetchSettingsSS();
if (!settings) {
redirect("/search");
}
if (settings.settings.default_page === "search") {
redirect("/search");
} else {
redirect("/chat");
}
}

View File

@@ -0,0 +1,24 @@
"use client";
import { SearchSection } from "@/components/search/SearchSection";
import FunctionalWrapper from "../chat/shared_chat_search/FunctionalWrapper";
export default function WrappedSearch({
searchTypeDefault,
initiallyToggled,
}: {
searchTypeDefault: string;
initiallyToggled: boolean;
}) {
return (
<FunctionalWrapper
initiallyToggled={initiallyToggled}
content={(toggledSidebar, toggle) => (
<SearchSection
toggle={toggle}
toggledSidebar={toggledSidebar}
defaultSearchType={searchTypeDefault}
/>
)}
/>
);
}

213
web/src/app/search/page.tsx Normal file
View File

@@ -0,0 +1,213 @@
import {
AuthTypeMetadata,
getAuthTypeMetadataSS,
getCurrentUserSS,
} from "@/lib/userSS";
import { redirect } from "next/navigation";
import { HealthCheckBanner } from "@/components/health/healthcheck";
import { fetchSS } from "@/lib/utilsSS";
import { CCPairBasicInfo, DocumentSet, Tag, User } from "@/lib/types";
import { cookies } from "next/headers";
import { SearchType } from "@/lib/search/interfaces";
import { Persona } from "../admin/assistants/interfaces";
import { unstable_noStore as noStore } from "next/cache";
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
import { personaComparator } from "../admin/assistants/lib";
import { FullEmbeddingModelResponse } from "@/components/embedding/interfaces";
import { ChatPopup } from "../chat/ChatPopup";
import {
FetchAssistantsResponse,
fetchAssistantsSS,
} from "@/lib/assistants/fetchAssistantsSS";
import { ChatSession } from "../chat/interfaces";
import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants";
import {
AGENTIC_SEARCH_TYPE_COOKIE_NAME,
NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN,
DISABLE_LLM_DOC_RELEVANCE,
} from "@/lib/constants";
import WrappedSearch from "./WrappedSearch";
import { SearchProvider } from "@/components/context/SearchContext";
import { fetchLLMProvidersSS } from "@/lib/llm/fetchLLMs";
import { LLMProviderDescriptor } from "../admin/configuration/llm/interfaces";
import { headers } from "next/headers";
import {
hasCompletedWelcomeFlowSS,
WelcomeModal,
} from "@/components/initialSetup/welcome/WelcomeModalWrapper";
export default async function Home(props: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const searchParams = await props.searchParams;
// Disable caching so we always get the up to date connector / document set / persona info
// importantly, this prevents users from adding a connector, going back to the main page,
// and then getting hit with a "No Connectors" popup
noStore();
const requestCookies = await cookies();
const tasks = [
getAuthTypeMetadataSS(),
getCurrentUserSS(),
fetchSS("/manage/indexing-status"),
fetchSS("/manage/document-set"),
fetchAssistantsSS(),
fetchSS("/query/valid-tags"),
fetchSS("/query/user-searches"),
fetchLLMProvidersSS(),
];
// catch cases where the backend is completely unreachable here
// without try / catch, will just raise an exception and the page
// will not render
let results: (
| User
| Response
| AuthTypeMetadata
| FullEmbeddingModelResponse
| FetchAssistantsResponse
| LLMProviderDescriptor[]
| null
)[] = [null, null, null, null, null, null, null, null];
try {
results = await Promise.all(tasks);
} catch (e) {
console.log(`Some fetch failed for the main search page - ${e}`);
}
const authTypeMetadata = results[0] as AuthTypeMetadata | null;
const user = results[1] as User | null;
const ccPairsResponse = results[2] as Response | null;
const documentSetsResponse = results[3] as Response | null;
const [initialAssistantsList, assistantsFetchError] =
results[4] as FetchAssistantsResponse;
const tagsResponse = results[5] as Response | null;
const queryResponse = results[6] as Response | null;
const llmProviders = (results[7] || []) as LLMProviderDescriptor[];
const authDisabled = authTypeMetadata?.authType === "disabled";
if (!authDisabled && !user) {
const headersList = await headers();
const fullUrl = headersList.get("x-url") || "/search";
const searchParamsString = new URLSearchParams(
searchParams as unknown as Record<string, string>
).toString();
const redirectUrl = searchParamsString
? `${fullUrl}?${searchParamsString}`
: fullUrl;
return redirect(`/auth/login?next=${encodeURIComponent(redirectUrl)}`);
}
if (user && !user.is_verified && authTypeMetadata?.requiresVerification) {
return redirect("/auth/waiting-on-verification");
}
let ccPairs: CCPairBasicInfo[] = [];
if (ccPairsResponse?.ok) {
ccPairs = await ccPairsResponse.json();
} else {
console.log(`Failed to fetch connectors - ${ccPairsResponse?.status}`);
}
let documentSets: DocumentSet[] = [];
if (documentSetsResponse?.ok) {
documentSets = await documentSetsResponse.json();
} else {
console.log(
`Failed to fetch document sets - ${documentSetsResponse?.status}`
);
}
let querySessions: ChatSession[] = [];
if (queryResponse?.ok) {
querySessions = (await queryResponse.json()).sessions;
} else {
console.log(`Failed to fetch chat sessions - ${queryResponse?.text()}`);
}
let assistants: Persona[] = initialAssistantsList;
if (assistantsFetchError) {
console.log(`Failed to fetch assistants - ${assistantsFetchError}`);
} else {
// remove those marked as hidden by an admin
assistants = assistants.filter((assistant) => assistant.is_visible);
// hide personas with no retrieval
assistants = assistants.filter((assistant) => assistant.num_chunks !== 0);
// sort them in priority order
assistants.sort(personaComparator);
}
let tags: Tag[] = [];
if (tagsResponse?.ok) {
tags = (await tagsResponse.json()).tags;
} else {
console.log(`Failed to fetch tags - ${tagsResponse?.status}`);
}
// needs to be done in a non-client side component due to nextjs
const storedSearchType = requestCookies.get("searchType")?.value as
| string
| undefined;
const searchTypeDefault: SearchType =
storedSearchType !== undefined &&
SearchType.hasOwnProperty(storedSearchType)
? (storedSearchType as SearchType)
: SearchType.SEMANTIC; // default to semantic
const hasAnyConnectors = ccPairs.length > 0;
const shouldShowWelcomeModal =
!llmProviders.length &&
!hasCompletedWelcomeFlowSS(requestCookies) &&
!hasAnyConnectors &&
(!user || user.role === "admin");
const shouldDisplayNoSourcesModal =
(!user || user.role === "admin") &&
ccPairs.length === 0 &&
!shouldShowWelcomeModal;
const sidebarToggled = requestCookies.get(SIDEBAR_TOGGLED_COOKIE_NAME);
const agenticSearchToggle = requestCookies.get(
AGENTIC_SEARCH_TYPE_COOKIE_NAME
);
const toggleSidebar = sidebarToggled
? sidebarToggled.value.toLocaleLowerCase() == "true" || false
: NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN;
const agenticSearchEnabled = agenticSearchToggle
? agenticSearchToggle.value.toLocaleLowerCase() == "true" || false
: false;
return (
<>
<HealthCheckBanner />
<InstantSSRAutoRefresh />
{shouldShowWelcomeModal && (
<WelcomeModal user={user} requestCookies={requestCookies} />
)}
{/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit.
Only used in the EE version of the app. */}
<ChatPopup />
<SearchProvider
value={{
querySessions,
ccPairs,
documentSets,
assistants,
tags,
agenticSearchEnabled,
disabledAgentic: DISABLE_LLM_DOC_RELEVANCE,
initiallyToggled: toggleSidebar,
shouldShowWelcomeModal,
shouldDisplayNoSources: shouldDisplayNoSourcesModal,
}}
>
<WrappedSearch
initiallyToggled={toggleSidebar}
searchTypeDefault={searchTypeDefault}
/>
</SearchProvider>
</>
);
}

View File

@@ -9,7 +9,7 @@ import { checkUserIsNoAuthUser, logout } from "@/lib/user";
import { Popover } from "./popover/Popover";
import { LOGOUT_DISABLED } from "@/lib/constants";
import { SettingsContext } from "./settings/SettingsProvider";
import { BellIcon, LightSettingsIcon, UserIcon } from "./icons/icons";
import { BellIcon, LightSettingsIcon } from "./icons/icons";
import { pageType } from "@/app/chat/sessionSidebar/types";
import { NavigationItem, Notification } from "@/app/admin/settings/interfaces";
import DynamicFaIcon, { preloadIcons } from "./icons/DynamicFaIcon";
@@ -56,13 +56,7 @@ const DropdownOption: React.FC<DropdownOptionProps> = ({
}
};
export function UserDropdown({
page,
toggleUserSettings,
}: {
page?: pageType;
toggleUserSettings?: () => void;
}) {
export function UserDropdown({ page }: { page?: pageType }) {
const { user } = useUser();
const [userInfoVisible, setUserInfoVisible] = useState(false);
const userInfoRef = useRef<HTMLDivElement>(null);
@@ -101,9 +95,7 @@ export function UserDropdown({
}
// Construct the current URL
const currentUrl = `${pathname}${
searchParams.toString() ? `?${searchParams.toString()}` : ""
}`;
const currentUrl = `${pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
// Encode the current URL to use as a redirect parameter
const encodedRedirect = encodeURIComponent(currentUrl);
@@ -138,7 +130,7 @@ export function UserDropdown({
<div
className="
my-auto
bg-userdropdown-background
bg-background-strong
ring-2
ring-transparent
group-hover:ring-background-300/50
@@ -246,24 +238,13 @@ export function UserDropdown({
)
)}
{toggleUserSettings && (
<DropdownOption
onClick={toggleUserSettings}
icon={<UserIcon className="h-5 w-5 my-auto mr-2" />}
label="User Settings"
/>
)}
<DropdownOption
onClick={() => {
setUserInfoVisible(true);
setShowNotifications(true);
}}
icon={<BellIcon className="h-5 w-5 my-auto mr-2" />}
label={`Notifications ${
notifications && notifications.length > 0
? `(${notifications.length})`
: ""
}`}
label={`Notifications ${notifications && notifications.length > 0 ? `(${notifications.length})` : ""}`}
/>
{showLogout &&

View File

@@ -40,7 +40,14 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
<nav className="space-y-2">
<div className="w-full justify-center mb-4 flex">
<div className="w-52">
<Link className="flex flex-col" href="/chat">
<Link
className="flex flex-col"
href={
settings && settings.default_page === "chat"
? "/chat"
: "/search"
}
>
<div className="max-w-[200px] w-full flex gap-x-1 my-auto">
<div className="flex-none mb-auto">
<Logo />
@@ -66,7 +73,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
</div>
</div>
<div className="flex w-full justify-center">
<Link href="/chat">
<Link href={settings.default_page == "chat" ? "/chat" : "/search"}>
<button className="text-sm flex items-center block w-52 py-2.5 flex px-2 text-left text-text-back-button bg-background-back-button hover:bg-opacity-80 cursor-pointer rounded">
<BackIcon className="my-auto" size={18} />
<p className="ml-1 break-words line-clamp-2 ellipsis leading-none">

View File

@@ -41,7 +41,7 @@ export default function AssistantBanner({
<Tooltip>
<TooltipTrigger asChild>
<div
className="flex w-36 mx-3 py-1.5 scale-[1.] rounded-full border border-border-recent-assistants justify-center items-center gap-x-2 py-1 px-3 hover:bg-background-recent-assistants-hover transition-colors cursor-pointer"
className="flex w-36 mx-3 py-1.5 scale-[1.] rounded-full border border-background-150 justify-center items-center gap-x-2 py-1 px-3 hover:bg-background-125 transition-colors cursor-pointer"
onClick={() => onAssistantChange(assistant)}
>
<AssistantIcon
@@ -49,7 +49,7 @@ export default function AssistantBanner({
size="xs"
assistant={assistant}
/>
<span className="font-semibold text-text-recent-assistants text-xs truncate max-w-[120px]">
<span className="font-semibold text-text-800 text-xs truncate max-w-[120px]">
{assistant.name}
</span>
</div>

View File

@@ -7,9 +7,6 @@ import { useSortable } from "@dnd-kit/sortable";
import React, { useState } from "react";
import { FiBookmark } from "react-icons/fi";
import { MdDragIndicator } from "react-icons/md";
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
import { SourceIcon } from "@/components/SourceIcon";
import { Badge } from "../ui/badge";
export const AssistantCard = ({
assistant,
@@ -23,79 +20,58 @@ export const AssistantCard = ({
llmName: string;
}) => {
const [hovering, setHovering] = useState(false);
const renderBadgeContent = (tool: { name: string }) => {
switch (tool.name) {
case "SearchTool":
return (
<>
<SourceIcon sourceType="web" iconSize={8} />
<span>Search</span>
</>
);
case "ImageGenerationTool":
return (
<>
<SourceIcon sourceType="web" iconSize={8} />
<span>Image Gen</span>
</>
);
default:
return tool.name;
}
};
return (
<div
onClick={() => onSelect(assistant)}
className={`
flex flex-col overflow-hidden w-full rounded-xl px-3 py-4
p-4
cursor-pointer
${isSelected ? "bg-background-125" : "hover:bg-background-100"}
border
${isSelected ? "bg-hover" : "hover:bg-hover-light"}
shadow-md
rounded-lg
border-border
grow
flex items-center
overflow-hidden
`}
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
>
<div className="flex items-center gap-4">
<AssistantIcon size="xs" assistant={assistant} />
<div className="overflow-hidden text-ellipsis break-words flex-grow">
<div className="flex items-center justify-start gap-2">
<span className="line-clamp-1 text-sm text-black font-semibold leading-tight">
{assistant.name}
</span>
{assistant.llm_model_version_override && (
<Badge size="xs" variant="destructive">
{getDisplayNameForModel(
assistant.llm_model_version_override || llmName
)}
</Badge>
)}
{assistant.tools.map((tool, index) => (
<Badge key={index} size="xs" variant="secondary" className="ml-1">
<div className="flex items-center gap-1">
{renderBadgeContent(tool)}
</div>
</Badge>
))}
<div className="w-full">
<div className="flex items-center mb-2">
<AssistantIcon assistant={assistant} />
<div className="ml-2 ellipsis truncate font-bold text-sm text-emphasis">
{assistant.name}
</div>
<span className="line-clamp-2 text-xs text-text-700">
{assistant.description}
</span>
</div>
<div className="text-xs text-wrap text-subtle mb-2 mt-2 line-clamp-3 py-1">
{assistant.description}
</div>
<div className="mt-2 flex flex-col gap-y-1">
{assistant.document_sets.length > 0 && (
<div className="text-xs text-subtle flex flex-wrap gap-2">
<p className="my-auto font-medium">Document Sets:</p>
{assistant.document_sets.map((set) => (
<Bubble key={set.id} isSelected={false}>
<div className="flex flex-row gap-1">
<FiBookmark className="mr-1 my-auto" />
{set.name}
</div>
</Bubble>
))}
</div>
)}
<div className="text-xs text-subtle">
<span className="font-semibold">Default model:</span>{" "}
{getDisplayNameForModel(
assistant.llm_model_version_override || llmName
)}
</div>
<AssistantTools hovered={hovering} assistant={assistant} />
</div>
</div>
{assistant.document_sets.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{assistant.document_sets.map((set) => (
<Bubble key={set.id} isSelected={false}>
<div className="flex items-center gap-1 text-xs">
<FiBookmark className="text-text-500" />
{set.name}
</div>
</Bubble>
))}
</div>
)}
</div>
);
};

View File

@@ -1,311 +0,0 @@
import React, { useEffect, useState, useRef, useCallback } from "react";
import { useAssistants } from "@/components/context/AssistantsContext";
import { useChatContext } from "@/components/context/ChatContext";
import { useUser } from "@/components/user/UserProvider";
import { Persona } from "@/app/admin/assistants/interfaces";
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
import { FiChevronDown } from "react-icons/fi";
import { getFinalLLM, destructureValue } from "@/lib/llm/utils";
import { updateModelOverrideForChatSession } from "@/app/chat/lib";
import { debounce } from "lodash";
import { LlmList } from "@/components/llm/LLMList";
import { checkPersonaRequiresImageGeneration } from "@/app/admin/assistants/lib";
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent,
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { DraggableAssistantCard } from "@/components/assistants/AssistantCards";
import { updateUserAssistantList } from "@/lib/assistants/updateAssistantPreferences";
import Text from "@/components/ui/text";
import { GearIcon } from "@/components/icons/icons";
import { LlmOverrideManager } from "@/lib/hooks";
import { Tab } from "@headlessui/react";
import { AssistantIcon } from "../assistants/AssistantIcon";
const AssistantSelector = ({
liveAssistant,
onAssistantChange,
chatSessionId,
llmOverrideManager,
}: {
liveAssistant: Persona;
onAssistantChange: (assistant: Persona) => void;
chatSessionId?: string;
llmOverrideManager?: LlmOverrideManager;
}) => {
const { finalAssistants, refreshAssistants } = useAssistants();
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const { llmProviders } = useChatContext();
const { refreshUser, user } = useUser();
const [assistants, setAssistants] = useState<Persona[]>(finalAssistants);
const [selectedTab, setSelectedTab] = useState(0);
const [isTemperatureExpanded, setIsTemperatureExpanded] = useState(false);
const [localTemperature, setLocalTemperature] = useState<number>(
llmOverrideManager?.temperature || 0
);
useEffect(() => {
setAssistants(finalAssistants);
}, [finalAssistants]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const handleDragEnd = async (event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = assistants.findIndex(
(item) => item.id.toString() === active.id
);
const newIndex = assistants.findIndex(
(item) => item.id.toString() === over.id
);
const updatedAssistants = arrayMove(assistants, oldIndex, newIndex);
setAssistants(updatedAssistants);
await updateUserAssistantList(updatedAssistants.map((a) => a.id));
// await refreshUser();
// await refreshAssistants();
}
};
const debouncedSetTemperature = useCallback(
(value: number) => {
const debouncedFunction = debounce((value: number) => {
llmOverrideManager?.setTemperature(value);
}, 300);
return debouncedFunction(value);
},
[llmOverrideManager]
);
const handleTemperatureChange = (value: number) => {
setLocalTemperature(value);
debouncedSetTemperature(value);
};
// Get the user's default model
const userDefaultModel = user?.preferences.default_model;
// Get the assistant's default model if it exists
const assistantDefaultModel = liveAssistant.llm_model_version_override;
// Determine the current LLM model to use
const currentLlm =
llmOverrideManager?.llmOverride?.modelName ??
assistantDefaultModel ??
userDefaultModel ??
llmProviders[0].model_names[0];
const requiresImageGeneration =
checkPersonaRequiresImageGeneration(liveAssistant);
return (
<div className=" relative" ref={dropdownRef}>
<div className="flex justify-center">
<div
onClick={() => {
setIsOpen(!isOpen);
setSelectedTab(0);
}}
className="flex items-center gap-x-2 justify-between px-6 py-3 text-sm font-medium text-white bg-black rounded-full shadow-lg hover:shadow-xl transition-all duration-300 cursor-pointer"
>
<div className="flex gap-x-2 items-center">
<AssistantIcon assistant={liveAssistant} size="xs" />
<span className="font-bold">{liveAssistant.name}</span>
</div>
<div className="flex items-center">
<span className="mr-2 text-xs">{currentLlm}</span>
<FiChevronDown
className={`w-5 h-5 text-white transition-transform duration-300 transform ${
isOpen ? "rotate-180" : ""
}`}
aria-hidden="true"
/>
</div>
</div>
</div>
{isOpen && (
<div className="absolute z-10 w-96 mt-2 origin-top-center left-1/2 transform -translate-x-1/2 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<Tab.Group selectedIndex={selectedTab} onChange={setSelectedTab}>
<Tab.List className="flex p-1 space-x-1 bg-gray-100 rounded-t-md">
<Tab
className={({ selected }) =>
`w-full py-2.5 text-sm leading-5 font-medium rounded-md
${
selected
? "bg-white text-gray-700 shadow"
: "text-gray-500 hover:bg-white/[0.12] hover:text-gray-700"
}`
}
>
Assistant
</Tab>
<Tab
className={({ selected }) =>
`w-full py-2.5 text-sm leading-5 font-medium rounded-md
${
selected
? "bg-white text-gray-700 shadow"
: "text-gray-500 hover:bg-white/[0.12] hover:text-gray-700"
}`
}
>
Model
</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel className="p-3">
<div className="mb-4">
<h3 className="text-center text-lg font-semibold text-gray-800">
Choose an Assistant
</h3>
</div>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={assistants.map((a) => a.id.toString())}
strategy={verticalListSortingStrategy}
>
<div className="space-y-2 max-h-96 overflow-x-none overflow-y-auto">
{assistants.map((assistant) => (
<DraggableAssistantCard
key={assistant.id.toString()}
assistant={assistant}
isSelected={liveAssistant.id === assistant.id}
onSelect={(assistant) => {
onAssistantChange(assistant);
setIsOpen(false);
}}
llmName={
assistant.llm_model_version_override ??
userDefaultModel ??
currentLlm
}
/>
))}
</div>
</SortableContext>
</DndContext>
</Tab.Panel>
<Tab.Panel className="p-3">
<div className="mb-4">
<h3 className="text-center text-lg font-semibold text-gray-800 ">
Choose a Model
</h3>
</div>
<LlmList
currentAssistant={liveAssistant}
requiresImageGeneration={requiresImageGeneration}
llmProviders={llmProviders}
currentLlm={currentLlm}
userDefault={userDefaultModel}
includeUserDefault={true}
onSelect={(value: string | null) => {
if (value == null) return;
const { modelName, name, provider } =
destructureValue(value);
llmOverrideManager?.setLlmOverride({
name,
provider,
modelName,
});
if (chatSessionId) {
updateModelOverrideForChatSession(chatSessionId, value);
}
setIsOpen(false);
}}
/>
<div className="mt-4">
<button
className="flex items-center text-sm font-medium transition-colors duration-200"
onClick={() =>
setIsTemperatureExpanded(!isTemperatureExpanded)
}
>
<span className="mr-2 text-xs text-primary">
{isTemperatureExpanded ? "▼" : "►"}
</span>
<span>Temperature</span>
</button>
{isTemperatureExpanded && (
<>
<Text className="mt-2 mb-8">
Adjust the temperature of the LLM. Higher temperatures
will make the LLM generate more creative and diverse
responses, while lower temperature will make the LLM
generate more conservative and focused responses.
</Text>
<div className="relative w-full">
<input
type="range"
onChange={(e) =>
handleTemperatureChange(parseFloat(e.target.value))
}
className="w-full p-2 border border-border rounded-md"
min="0"
max="2"
step="0.01"
value={localTemperature}
/>
<div
className="absolute text-sm"
style={{
left: `${(localTemperature || 0) * 50}%`,
transform: `translateX(-${Math.min(
Math.max((localTemperature || 0) * 50, 10),
90
)}%)`,
top: "-1.5rem",
}}
>
{localTemperature}
</div>
</div>
</>
)}
</div>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</div>
)}
</div>
);
};
export default AssistantSelector;

View File

@@ -11,8 +11,7 @@ import { pageType } from "@/app/chat/sessionSidebar/types";
import { useRouter } from "next/navigation";
import { ChatBanner } from "@/app/chat/ChatBanner";
import LogoType from "../header/LogoType";
import { Persona } from "@/app/admin/assistants/interfaces";
import { LlmOverrideManager } from "@/lib/hooks";
import { useUser } from "../user/UserProvider";
export default function FunctionalHeader({
page,
@@ -21,23 +20,13 @@ export default function FunctionalHeader({
toggleSidebar = () => null,
reset = () => null,
sidebarToggled,
liveAssistant,
onAssistantChange,
llmOverrideManager,
documentSidebarToggled,
toggleUserSettings,
}: {
reset?: () => void;
page: pageType;
sidebarToggled?: boolean;
documentSidebarToggled?: boolean;
currentChatSession?: ChatSession | null | undefined;
setSharingModalVisible?: (value: SetStateAction<boolean>) => void;
toggleSidebar?: () => void;
liveAssistant?: Persona;
onAssistantChange?: (assistant: Persona) => void;
llmOverrideManager?: LlmOverrideManager;
toggleUserSettings?: () => void;
}) {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@@ -82,7 +71,6 @@ export default function FunctionalHeader({
toggleSidebar={toggleSidebar}
handleNewChat={handleNewChat}
/>
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
@@ -97,7 +85,6 @@ export default function FunctionalHeader({
${sidebarToggled ? "w-[250px]" : "w-[0px]"}
`}
/>
<div className="w-full mobile:-mx-20 desktop:px-4">
<ChatBanner />
</div>
@@ -121,7 +108,7 @@ export default function FunctionalHeader({
)}
<div className="mobile:hidden flex my-auto">
<UserDropdown page={page} toggleUserSettings={toggleUserSettings} />
<UserDropdown />
</div>
<Link
className="desktop:hidden my-auto"
@@ -137,22 +124,12 @@ export default function FunctionalHeader({
<NewChatIcon size={20} />
</div>
</Link>
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
mobile:hidden
flex-none
mx-auto
overflow-y-hidden
transition-all
duration-300
ease-in-out
h-full
${documentSidebarToggled ? "w-[300px]" : "w-[0px]"}
`}
/>
</div>
</div>
{page != "assistants" && (
<div className="h-20 left-0 absolute top-0 z-10 w-full bg-gradient-to-b via-50% z-[-1] from-background via-background to-background/10 flex" />
)}
</div>
);
}

View File

@@ -1,79 +0,0 @@
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
import { SourceIcon } from "@/components/SourceIcon";
import { DanswerDocument } from "@/lib/search/interfaces";
export default function FirstSourceCard({ doc }: { doc: DanswerDocument }) {
return (
<a
key={doc.document_id}
href={doc.link || undefined}
target="_blank"
rel="noopener"
className="flex flex-col gap-0.5 rounded-sm px-3 py-2.5 hover:bg-background-125 bg-background-100 w-[200px]"
>
<div className="line-clamp-1 font-semibold text-ellipsis text-text-900 flex h-6 items-center gap-2 text-sm">
{doc.is_internet ? (
<InternetSearchIcon url={doc.link} />
) : (
<SourceIcon sourceType={doc.source_type} iconSize={18} />
)}
<p>
{(doc.semantic_identifier || doc.document_id).slice(0, 12).trim()}
{(doc.semantic_identifier || doc.document_id).length > 12 && (
<span className="text-text-500">...</span>
)}
</p>
{/* {doc.source_type} */}
</div>
<div className="line-clamp-2 text-sm font-semibold"></div>
<div className="line-clamp-2 text-sm font-normal leading-snug text-text-700">
{doc.blurb}
</div>
</a>
);
}
interface SeeMoreBlockProps {
documentSelectionToggled: boolean;
toggleDocumentSelection?: () => void;
uniqueSources: DanswerDocument["source_type"][];
}
export function SeeMoreBlock({
documentSelectionToggled,
toggleDocumentSelection,
uniqueSources,
}: SeeMoreBlockProps) {
return (
<div
onClick={toggleDocumentSelection}
className={`
${documentSelectionToggled ? "border-border-100 border" : ""}
cursor-pointer w-[150px] rounded-sm flex-none transition-all duration-500 hover:bg-background-125 bg-text-100 px-3 py-2.5
`}
>
<div className="line-clamp-1 font-semibold text-ellipsis text-text-900 flex h-6 items-center justify-between text-sm">
<p className="mr-0 flex-shrink-0">
{documentSelectionToggled ? "Hide sources" : "See context"}
</p>
<div className="flex -space-x-3 flex-shrink-0 overflow-hidden">
{uniqueSources.map((sourceType, ind) => (
<div
key={ind}
className="inline-block bg-background-100 rounded-full p-0.5"
style={{ zIndex: uniqueSources.length - ind }}
>
<div className="bg-background-100 rounded-full">
<SourceIcon sourceType={sourceType} iconSize={20} />
</div>
</div>
))}
</div>
</div>
<div className="line-clamp-2 text-sm font-semibold"></div>
<div className="line-clamp-2 text-sm font-normal leading-snug text-text-700">
See more
</div>
</div>
);
}

View File

@@ -1,13 +1,7 @@
"use client";
import React, { createContext, useContext, useState } from "react";
import {
CCPairBasicInfo,
DocumentSet,
Tag,
User,
ValidSources,
} from "@/lib/types";
import { DocumentSet, Tag, User, ValidSources } from "@/lib/types";
import { ChatSession } from "@/app/chat/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
@@ -18,9 +12,6 @@ import { personaComparator } from "@/app/admin/assistants/lib";
interface ChatContextProps {
chatSessions: ChatSession[];
availableSources: ValidSources[];
ccPairs: CCPairBasicInfo[];
tags: Tag[];
documentSets: DocumentSet[];
availableDocumentSets: DocumentSet[];
availableTags: Tag[];
llmProviders: LLMProviderDescriptor[];

View File

@@ -8,9 +8,9 @@ import { ChatSession } from "@/app/chat/interfaces";
interface SearchContextProps {
querySessions: ChatSession[];
ccPairs: CCPairBasicInfo[];
tags: Tag[];
documentSets: DocumentSet[];
assistants: Persona[];
tags: Tag[];
agenticSearchEnabled: boolean;
disabledAgentic: boolean;
initiallyToggled: boolean;

View File

@@ -51,14 +51,7 @@ export default function LogoType({
onClick={() => toggleSidebar()}
className="pt-[2px] flex gap-x-2 items-center ml-4 desktop:invisible mb-auto"
>
<FiSidebar
size={20}
className={`${
toggled
? "text-text-mobile-sidebar-toggled"
: "text-text-mobile-sidebar-untoggled"
}`}
/>
<FiSidebar size={20} className="text-text-mobile-sidebar" />
{!showArrow && (
<Logo className="desktop:hidden -my-2" height={24} width={24} />
)}

View File

@@ -81,7 +81,6 @@ import voyageIcon from "../../../public/Voyage.png";
import googleIcon from "../../../public/Google.webp";
import xenforoIcon from "../../../public/Xenforo.svg";
import { FaRobot } from "react-icons/fa";
import { isConstructSignatureDeclaration } from "typescript";
export interface IconProps {
size?: number;
@@ -2693,28 +2692,3 @@ export const DownloadCSVIcon = ({
</svg>
);
};
export const UserIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return (
<svg
style={{ width: `${size}px`, height: `${size}px` }}
className={`w-[${size}px] h-[${size}px] ` + className}
xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
viewBox="0 0 24 24"
>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M19.618 21.25c0-3.602-4.016-6.53-7.618-6.53c-3.602 0-7.618 2.928-7.618 6.53M12 11.456a4.353 4.353 0 1 0 0-8.706a4.353 4.353 0 0 0 0 8.706"
/>
</svg>
);
};

View File

@@ -1,15 +1,10 @@
import React from "react";
import { getDisplayNameForModel } from "@/lib/hooks";
import {
checkLLMSupportsImageInput,
destructureValue,
structureValue,
} from "@/lib/llm/utils";
import { checkLLMSupportsImageInput, structureValue } from "@/lib/llm/utils";
import {
getProviderIcon,
LLMProviderDescriptor,
} from "@/app/admin/configuration/llm/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
interface LlmListProps {
llmProviders: LLMProviderDescriptor[];
@@ -19,19 +14,15 @@ interface LlmListProps {
scrollable?: boolean;
hideProviderIcon?: boolean;
requiresImageGeneration?: boolean;
includeUserDefault?: boolean;
currentAssistant?: Persona;
}
export const LlmList: React.FC<LlmListProps> = ({
currentAssistant,
llmProviders,
currentLlm,
onSelect,
userDefault,
scrollable,
requiresImageGeneration,
includeUserDefault = false,
}) => {
const llmOptionsByProvider: {
[provider: string]: {
@@ -72,11 +63,24 @@ export const LlmList: React.FC<LlmListProps> = ({
return (
<div
className={`${
scrollable
? "max-h-[200px] default-scrollbar overflow-x-hidden"
: "max-h-[300px]"
} bg-background-175 flex flex-col gap-y-1 overflow-y-scroll`}
scrollable ? "max-h-[200px] include-scrollbar" : "max-h-[300px]"
} bg-background-175 flex flex-col gap-y-1 overflow-y-scroll`}
>
{userDefault && (
<button
type="button"
key={-1}
className={`w-full py-1.5 px-2 text-sm ${
currentLlm == null
? "bg-background-200"
: "bg-background hover:bg-background-100"
} text-left rounded`}
onClick={() => onSelect(null)}
>
User Default (currently {getDisplayNameForModel(userDefault)})
</button>
)}
{llmOptions.map(({ name, icon, value }, index) => {
if (!requiresImageGeneration || checkLLMSupportsImageInput(name)) {
return (
@@ -92,25 +96,6 @@ export const LlmList: React.FC<LlmListProps> = ({
>
{icon({ size: 16 })}
{getDisplayNameForModel(name)}
{(() => {
if (
currentAssistant?.llm_model_version_override === name &&
userDefault &&
name === destructureValue(userDefault).modelName
) {
return " (assistant + user default)";
} else if (
currentAssistant?.llm_model_version_override === name
) {
return " (assistant)";
} else if (
userDefault &&
name === destructureValue(userDefault).modelName
) {
return " (user default)";
}
return "";
})()}
</button>
);
}

View File

@@ -18,7 +18,7 @@ export default function ExceptionTraceModal({
title="Full Exception Trace"
onOutsideClick={onOutsideClick}
>
<div className="overflow-y-auto default-scrollbar overflow-x-hidden pr-3 h-full mb-6">
<div className="overflow-y-auto include-scrollbar pr-3 h-full mb-6">
<div className="mb-6">
{!copyClicked ? (
<div

View File

@@ -1,10 +1,8 @@
"use client";
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
import React from "react";
import {
DanswerDocument,
DocumentRelevance,
LoadedDanswerDocument,
SearchDanswerDocument,
} from "@/lib/search/interfaces";
import { DocumentFeedbackBlock } from "./DocumentFeedbackBlock";
@@ -13,7 +11,7 @@ import { PopupSpec } from "../admin/connectors/Popup";
import { DocumentUpdatedAtBadge } from "./DocumentUpdatedAtBadge";
import { SourceIcon } from "../SourceIcon";
import { MetadataBadge } from "../MetadataBadge";
import { BookIcon, GlobeIcon, LightBulbIcon } from "../icons/icons";
import { BookIcon, LightBulbIcon } from "../icons/icons";
import { FaStar } from "react-icons/fa";
import { FiTag } from "react-icons/fi";
@@ -25,7 +23,7 @@ export const buildDocumentSummaryDisplay = (
matchHighlights: string[],
blurb: string
) => {
if (!matchHighlights || matchHighlights.length === 0) {
if (matchHighlights.length === 0) {
return blurb;
}
@@ -253,11 +251,7 @@ export const DocumentDisplay = ({
>
<CustomTooltip showTick line content="Toggle content">
<LightBulbIcon
className={`${
settings?.isMobile && alternativeToggled
? "text-green-600"
: "text-blue-600"
} my-auto ml-2 h-4 w-4 cursor-pointer`}
className={`${settings?.isMobile && alternativeToggled ? "text-green-600" : "text-blue-600"} my-auto ml-2 h-4 w-4 cursor-pointer`}
/>
</CustomTooltip>
</button>
@@ -314,9 +308,7 @@ export const AgenticDocumentDisplay = ({
}}
>
<div
className={`collapsible ${
!hide && "collapsible-closed overflow-y-auto border-transparent"
}`}
className={`collapsible ${!hide && "collapsible-closed overflow-y-auto border-transparent"}`}
>
<div className="flex relative">
<a
@@ -388,33 +380,3 @@ export const AgenticDocumentDisplay = ({
</div>
);
};
export function CompactDocumentCard({
document,
}: {
document: LoadedDanswerDocument;
}) {
return (
<div className="max-w-[300px] pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0 ">
<h3 className="text-sm font-semibold flex items-center gap-x-1 text-text-900 pt-0 mt-0 truncate w-full">
<GlobeIcon className="w-4 h-4" />
{(document.semantic_identifier || document.document_id).slice(0, 40)}
{(document.semantic_identifier || document.document_id).length > 40 &&
"..."}
</h3>
{document.blurb && (
<p className="text-xs text-gray-600 line-clamp-2">{document.blurb}</p>
)}
<div className="flex items-center justify-between w-full ">
<span className="text-xs text-gray-500">
{new Date(document.updated_at || "").toLocaleDateString()}
</span>
{document.is_internet ? (
<InternetSearchIcon url={document.link} />
) : (
<SourceIcon sourceType={document.source_type} iconSize={18} />
)}
</div>
</div>
);
}

View File

@@ -74,9 +74,8 @@ export default function SearchAnswer({
searchAnswerExpanded ? "min-h-[16rem]" : "h-[16rem]"
} ${
!searchAnswerExpanded && searchAnswerOverflowing && "overflow-y-hidden"
} p-4 border-2 border-search-answer-border text-text-search-answer bg-background-search-answer rounded-lg relative`}
} p-4 border-2 border-search-answer-border rounded-lg relative`}
>
asdfasdf
<div>
<div className="flex gap-x-2">
<h2 className="text-emphasis font-bold my-auto mb-1">AI Answer</h2>
@@ -153,6 +152,7 @@ export default function SearchAnswer({
{!searchAnswerExpanded && searchAnswerOverflowing && (
<div className="absolute bottom-0 left-0 w-full h-[100px] bg-gradient-to-b from-background/5 via-background/60 to-background/90"></div>
)}
{!searchAnswerExpanded && searchAnswerOverflowing && (
<div className="w-full h-12 absolute items-center content-center flex left-0 px-4 bottom-0">
<button

View File

@@ -17,12 +17,6 @@ interface FullSearchBarProps {
showingSidebar: boolean;
}
import {
TooltipProvider,
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useRef } from "react";
import { SendIcon } from "../icons/icons";
import { Separator } from "@/components/ui/separator";
@@ -34,65 +28,61 @@ import { CCPairBasicInfo, DocumentSet, Tag } from "@/lib/types";
export const AnimatedToggle = ({
isOn,
handleToggle,
direction = "top",
}: {
isOn: boolean;
handleToggle: () => void;
direction?: "bottom" | "top";
}) => {
const commandSymbol = KeyboardSymbol();
const containerRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<CustomTooltip
light
large
content={
<div className="bg-white my-auto p-6 rounded-lg w-full">
<h2 className="text-xl text-text-800 font-bold mb-2">
Agentic Search
</h2>
<p className="text-text-700 text-sm mb-4">
Our most powerful search, have an AI agent guide you to pinpoint
exactly what you&apos;re looking for.
</p>
<Separator />
<h2 className="text-xl text-text-800 font-bold mb-2">Fast Search</h2>
<p className="text-text-700 text-sm mb-4">
Get quality results immediately, best suited for instant access to
your documents.
</p>
<p className="mt-2 flex text-xs">Shortcut: ({commandSymbol}/)</p>
</div>
}
>
<div
ref={containerRef}
className="my-auto ml-auto flex justify-end items-center cursor-pointer"
onClick={handleToggle}
>
<div ref={contentRef} className="flex items-center">
{/* Toggle switch */}
<div
ref={containerRef}
className="my-auto ml-auto flex justify-end items-center cursor-pointer"
onClick={handleToggle}
className={`
w-10 h-6 flex items-center rounded-full p-1 transition-all duration-300 ease-in-out
${isOn ? "bg-toggled-background" : "bg-untoggled-background"}
`}
>
<div ref={contentRef} className="flex items-center">
<div
className={`
w-10 h-6 flex items-center rounded-full p-1 transition-all duration-300 ease-in-out
${isOn ? "bg-toggled-background" : "bg-untoggled-background"}
`}
>
<div
className={`
bg-white w-4 h-4 rounded-full shadow-md transform transition-all duration-300 ease-in-out
${isOn ? "translate-x-4" : ""}
`}
></div>
</div>
<p className="ml-2 text-sm">Pro</p>
</div>
<div
className={`
bg-white w-4 h-4 rounded-full shadow-md transform transition-all duration-300 ease-in-out
${isOn ? "translate-x-4" : ""}
`}
></div>
</div>
</TooltipTrigger>
<TooltipContent side={direction} backgroundColor="bg-background-200">
<div className="bg-white my-auto p-6 rounded-lg max-w-sm">
<h2 className="text-xl text-text-800 font-bold mb-2">
Agentic Search
</h2>
<p className="text-text-700 text-sm mb-4">
Our most powerful search, have an AI agent guide you to pinpoint
exactly what you&apos;re looking for.
</p>
<Separator />
<h2 className="text-xl text-text-800 font-bold mb-2">
Fast Search
</h2>
<p className="text-text-700 text-sm mb-4">
Get quality results immediately, best suited for instant access to
your documents.
</p>
<p className="mt-2 flex text-xs">Shortcut: ({commandSymbol}/)</p>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<p className="ml-2 text-sm">Agentic</p>
</div>
</div>
</CustomTooltip>
);
};

View File

@@ -0,0 +1,856 @@
"use client";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { FullSearchBar } from "./SearchBar";
import { SearchResultsDisplay } from "./SearchResultsDisplay";
import { SourceSelector } from "./filtering/Filters";
import {
Quote,
SearchResponse,
FlowType,
SearchType,
SearchDefaultOverrides,
SearchRequestOverrides,
ValidQuestionResponse,
Relevance,
SearchDanswerDocument,
SourceMetadata,
} from "@/lib/search/interfaces";
import { searchRequestStreamed } from "@/lib/search/streamingQa";
import { CancellationToken, cancellable } from "@/lib/search/cancellable";
import { useFilters, useObjectState } from "@/lib/hooks";
import { Persona } from "@/app/admin/assistants/interfaces";
import { computeAvailableFilters } from "@/lib/filters";
import { useRouter, useSearchParams } from "next/navigation";
import { SettingsContext } from "../settings/SettingsProvider";
import { HistorySidebar } from "@/app/chat/sessionSidebar/HistorySidebar";
import { ChatSession, SearchSession } from "@/app/chat/interfaces";
import FunctionalHeader from "../chat_search/Header";
import { useSidebarVisibility } from "../chat_search/hooks";
import { SIDEBAR_TOGGLED_COOKIE_NAME } from "../resizable/constants";
import { AGENTIC_SEARCH_TYPE_COOKIE_NAME } from "@/lib/constants";
import Cookies from "js-cookie";
import FixedLogo from "@/app/chat/shared_chat_search/FixedLogo";
import { usePopup } from "../admin/connectors/Popup";
import { FeedbackType } from "@/app/chat/types";
import { FeedbackModal } from "@/app/chat/modal/FeedbackModal";
import { deleteChatSession, handleChatFeedback } from "@/app/chat/lib";
import SearchAnswer from "./SearchAnswer";
import { DeleteEntityModal } from "../modals/DeleteEntityModal";
import { ApiKeyModal } from "../llm/ApiKeyModal";
import { useSearchContext } from "../context/SearchContext";
import { useUser } from "../user/UserProvider";
import UnconfiguredProviderText from "../chat_search/UnconfiguredProviderText";
import { DateRangePickerValue } from "@/app/ee/admin/performance/DateRangeSelector";
import { Tag } from "@/lib/types";
import { isEqual } from "lodash";
export type searchState =
| "input"
| "searching"
| "reading"
| "analyzing"
| "summarizing"
| "generating"
| "citing";
const SEARCH_DEFAULT_OVERRIDES_START: SearchDefaultOverrides = {
forceDisplayQA: false,
offset: 0,
};
interface SearchSectionProps {
toggle: () => void;
defaultSearchType: SearchType;
toggledSidebar: boolean;
}
export const SearchSection = ({
toggle,
toggledSidebar,
defaultSearchType,
}: SearchSectionProps) => {
const {
querySessions,
ccPairs,
documentSets,
assistants,
tags,
shouldShowWelcomeModal,
agenticSearchEnabled,
disabledAgentic,
shouldDisplayNoSources,
} = useSearchContext();
const [query, setQuery] = useState<string>("");
const [comments, setComments] = useState<any>(null);
const [contentEnriched, setContentEnriched] = useState(false);
const [searchResponse, setSearchResponse] = useState<SearchResponse>({
suggestedSearchType: null,
suggestedFlowType: null,
answer: null,
quotes: null,
documents: null,
selectedDocIndices: null,
error: null,
messageId: null,
});
const [showApiKeyModal, setShowApiKeyModal] = useState(true);
const [agentic, setAgentic] = useState(agenticSearchEnabled);
const toggleAgentic = useCallback(() => {
Cookies.set(
AGENTIC_SEARCH_TYPE_COOKIE_NAME,
String(!agentic).toLocaleLowerCase()
);
setAgentic((agentic) => !agentic);
}, [agentic]);
const toggleSidebar = useCallback(() => {
Cookies.set(
SIDEBAR_TOGGLED_COOKIE_NAME,
String(!toggledSidebar).toLocaleLowerCase()
),
{
path: "/",
};
toggle();
}, [toggledSidebar, toggle]);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.metaKey || event.ctrlKey) {
switch (event.key.toLowerCase()) {
case "/":
toggleAgentic();
break;
}
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [toggleAgentic]);
const [isFetching, setIsFetching] = useState(false);
// Search Type
const selectedSearchType = defaultSearchType;
// If knowledge assistant exists, use it. Otherwise, use first available assistant for search.
const selectedPersona = assistants.find((assistant) => assistant.id === 0)
? 0
: assistants[0]?.id;
// Used for search state display
const [analyzeStartTime, setAnalyzeStartTime] = useState<number>(0);
// Filters
const filterManager = useFilters();
const availableSources = ccPairs.map((ccPair) => ccPair.source);
const [finalAvailableSources, finalAvailableDocumentSets] =
computeAvailableFilters({
selectedPersona: assistants.find(
(assistant) => assistant.id === selectedPersona
),
availableSources: availableSources,
availableDocumentSets: documentSets,
});
const searchParams = useSearchParams();
const existingSearchessionId = searchParams.get("searchId");
useEffect(() => {
if (existingSearchessionId == null) {
return;
}
function extractFirstMessageByType(
chatSession: SearchSession,
messageType: "user" | "assistant"
): string | null {
const userMessage = chatSession?.messages.find(
(msg) => msg.message_type === messageType
);
return userMessage ? userMessage.message : null;
}
async function initialSessionFetch() {
const response = await fetch(
`/api/query/search-session/${existingSearchessionId}`
);
const searchSession = (await response.json()) as SearchSession;
const userMessage = extractFirstMessageByType(searchSession, "user");
const assistantMessage = extractFirstMessageByType(
searchSession,
"assistant"
);
if (userMessage) {
setQuery(userMessage);
const danswerDocs: SearchResponse = {
documents: searchSession.documents,
suggestedSearchType: null,
answer: assistantMessage || "Search response not found",
quotes: null,
selectedDocIndices: null,
error: null,
messageId: searchSession.messages[0].message_id,
suggestedFlowType: null,
additional_relevance: undefined,
};
setIsFetching(false);
setFirstSearch(false);
setSearchResponse(danswerDocs);
setContentEnriched(true);
}
}
initialSessionFetch();
}, [existingSearchessionId]);
// Overrides for default behavior that only last a single query
const [defaultOverrides, setDefaultOverrides] =
useState<SearchDefaultOverrides>(SEARCH_DEFAULT_OVERRIDES_START);
const newSearchState = (
currentSearchState: searchState,
newSearchState: searchState
) => {
if (currentSearchState != "input") {
return newSearchState;
}
return "input";
};
// Helpers
const initialSearchResponse: SearchResponse = {
answer: null,
quotes: null,
documents: null,
suggestedSearchType: null,
suggestedFlowType: null,
selectedDocIndices: null,
error: null,
messageId: null,
additional_relevance: undefined,
};
// Streaming updates
const updateCurrentAnswer = (answer: string) => {
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
answer,
}));
if (analyzeStartTime) {
const elapsedTime = Date.now() - analyzeStartTime;
const nextInterval = Math.ceil(elapsedTime / 1500) * 1500;
setTimeout(() => {
setSearchState((searchState) =>
newSearchState(searchState, "generating")
);
}, nextInterval - elapsedTime);
}
};
const updateQuotes = (quotes: Quote[]) => {
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
quotes,
}));
setSearchState((searchState) => "citing");
};
const updateDocs = (documents: SearchDanswerDocument[]) => {
if (agentic) {
setTimeout(() => {
setSearchState((searchState) => newSearchState(searchState, "reading"));
}, 1500);
setTimeout(() => {
setAnalyzeStartTime(Date.now());
setSearchState((searchState) => {
const newState = newSearchState(searchState, "analyzing");
if (newState === "analyzing") {
setAnalyzeStartTime(Date.now());
}
return newState;
});
}, 4500);
}
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
documents,
}));
if (disabledAgentic) {
setIsFetching(false);
setSearchState((searchState) => "citing");
}
if (documents.length == 0) {
setSearchState("input");
}
};
const updateSuggestedSearchType = (suggestedSearchType: SearchType) =>
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
suggestedSearchType,
}));
const updateSuggestedFlowType = (suggestedFlowType: FlowType) =>
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
suggestedFlowType,
}));
const updateSelectedDocIndices = (docIndices: number[]) =>
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
selectedDocIndices: docIndices,
}));
const updateError = (error: FlowType) => {
resetInput(true);
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
error,
}));
};
const updateMessageAndThreadId = (
messageId: number,
chat_session_id: string
) => {
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
messageId,
}));
router.refresh();
setIsFetching(false);
setSearchState((searchState) => "input");
};
const updateDocumentRelevance = (relevance: Relevance) => {
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
additional_relevance: relevance,
}));
setContentEnriched(true);
setIsFetching(false);
if (disabledAgentic) {
setSearchState("input");
} else {
setSearchState("analyzing");
}
};
const updateComments = (comments: any) => {
setComments(comments);
};
const finishedSearching = () => {
if (disabledAgentic) {
setSearchState("input");
}
};
const { user } = useUser();
const [searchAnswerExpanded, setSearchAnswerExpanded] = useState(false);
const resetInput = (finalized?: boolean) => {
setSweep(false);
setFirstSearch(false);
setComments(null);
setSearchState(finalized ? "input" : "searching");
setSearchAnswerExpanded(false);
};
interface SearchDetails {
query: string;
sources: SourceMetadata[];
agentic: boolean;
documentSets: string[];
timeRange: DateRangePickerValue | null;
tags: Tag[];
persona: Persona;
}
const [previousSearch, setPreviousSearch] = useState<null | SearchDetails>(
null
);
const [agenticResults, setAgenticResults] = useState<boolean | null>(null);
const currentSearch = (overrideMessage?: string): SearchDetails => {
return {
query: overrideMessage || query,
sources: filterManager.selectedSources,
agentic: agentic!,
documentSets: filterManager.selectedDocumentSets,
timeRange: filterManager.timeRange,
tags: filterManager.selectedTags,
persona: assistants.find(
(assistant) => assistant.id === selectedPersona
) as Persona,
};
};
const isSearchChanged = () => {
return !isEqual(currentSearch(), previousSearch);
};
let lastSearchCancellationToken = useRef<CancellationToken | null>(null);
const onSearch = async ({
searchType,
agentic,
offset,
overrideMessage,
}: SearchRequestOverrides = {}) => {
if ((overrideMessage || query) == "") {
return;
}
setAgenticResults(agentic!);
resetInput();
setContentEnriched(false);
if (lastSearchCancellationToken.current) {
lastSearchCancellationToken.current.cancel();
}
lastSearchCancellationToken.current = new CancellationToken();
setIsFetching(true);
setSearchResponse(initialSearchResponse);
setPreviousSearch(currentSearch(overrideMessage));
const searchFnArgs = {
query: overrideMessage || query,
sources: filterManager.selectedSources,
agentic: agentic,
documentSets: filterManager.selectedDocumentSets,
timeRange: filterManager.timeRange,
tags: filterManager.selectedTags,
persona: assistants.find(
(assistant) => assistant.id === selectedPersona
) as Persona,
updateCurrentAnswer: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateCurrentAnswer,
}),
updateQuotes: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateQuotes,
}),
updateDocs: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateDocs,
}),
updateSuggestedSearchType: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateSuggestedSearchType,
}),
updateSuggestedFlowType: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateSuggestedFlowType,
}),
updateSelectedDocIndices: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateSelectedDocIndices,
}),
updateError: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateError,
}),
updateMessageAndThreadId: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateMessageAndThreadId,
}),
updateDocStatus: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateMessageAndThreadId,
}),
updateDocumentRelevance: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateDocumentRelevance,
}),
updateComments: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateComments,
}),
finishedSearching: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: finishedSearching,
}),
selectedSearchType: searchType ?? selectedSearchType,
offset: offset ?? defaultOverrides.offset,
};
await Promise.all([searchRequestStreamed(searchFnArgs)]);
};
// handle redirect if search page is disabled
// NOTE: this must be done here, in a client component since
// settings are passed in via Context and therefore aren't
// available in server-side components
const router = useRouter();
const settings = useContext(SettingsContext);
if (settings?.settings?.search_page_enabled === false) {
router.push("/chat");
}
const sidebarElementRef = useRef<HTMLDivElement>(null);
const innerSidebarElementRef = useRef<HTMLDivElement>(null);
const [showDocSidebar, setShowDocSidebar] = useState(false);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.metaKey || event.ctrlKey) {
switch (event.key.toLowerCase()) {
case "e":
event.preventDefault();
toggleSidebar();
break;
}
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [router, toggleSidebar]);
useEffect(() => {
if (settings?.isMobile) {
router.push("/chat");
}
}, [settings?.isMobile, router]);
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
if (e.propertyName === "opacity" && !firstSearch) {
const target = e.target as HTMLDivElement;
target.style.display = "none";
}
};
const [sweep, setSweep] = useState(false);
const performSweep = () => {
setSweep((sweep) => !sweep);
};
const [firstSearch, setFirstSearch] = useState(true);
const [searchState, setSearchState] = useState<searchState>("input");
const [deletingChatSession, setDeletingChatSession] =
useState<ChatSession | null>();
const showDeleteModal = (chatSession: ChatSession) => {
setDeletingChatSession(chatSession);
};
// Used to maintain a "time out" for history sidebar so our existing refs can have time to process change
const [untoggled, setUntoggled] = useState(false);
const explicitlyUntoggle = () => {
setShowDocSidebar(false);
setUntoggled(true);
setTimeout(() => {
setUntoggled(false);
}, 200);
};
useSidebarVisibility({
toggledSidebar,
sidebarElementRef,
showDocSidebar,
setShowDocSidebar,
mobile: settings?.isMobile,
});
const { answer, quotes, documents, error, messageId } = searchResponse;
const dedupedQuotes: Quote[] = [];
const seen = new Set<string>();
if (quotes) {
quotes.forEach((quote) => {
if (!seen.has(quote.document_id)) {
dedupedQuotes.push(quote);
seen.add(quote.document_id);
}
});
}
const [currentFeedback, setCurrentFeedback] = useState<
[FeedbackType, number] | null
>(null);
const onFeedback = async (
messageId: number,
feedbackType: FeedbackType,
feedbackDetails: string,
predefinedFeedback: string | undefined
) => {
const response = await handleChatFeedback(
messageId,
feedbackType,
feedbackDetails,
predefinedFeedback
);
if (response.ok) {
setPopup({
message: "Thanks for your feedback!",
type: "success",
});
} else {
const responseJson = await response.json();
const errorMsg = responseJson.detail || responseJson.message;
setPopup({
message: `Failed to submit feedback - ${errorMsg}`,
type: "error",
});
}
};
const chatBannerPresent = settings?.enterpriseSettings?.custom_header_content;
const { popup, setPopup } = usePopup();
const shouldUseAgenticDisplay =
agenticResults &&
(searchResponse.documents || []).some(
(document) =>
searchResponse.additional_relevance &&
searchResponse.additional_relevance[document.document_id] !== undefined
);
return (
<>
<div className="flex relative pr-[8px] h-full text-default">
{popup}
{!shouldDisplayNoSources &&
showApiKeyModal &&
!shouldShowWelcomeModal && (
<ApiKeyModal
setPopup={setPopup}
hide={() => setShowApiKeyModal(false)}
/>
)}
{deletingChatSession && (
<DeleteEntityModal
entityType="search"
entityName={deletingChatSession.name}
onClose={() => setDeletingChatSession(null)}
onSubmit={async () => {
const response = await deleteChatSession(deletingChatSession.id);
if (response.ok) {
setDeletingChatSession(null);
// go back to the main page
router.push("/search");
} else {
const responseJson = await response.json();
setPopup({ message: responseJson.detail, type: "error" });
}
router.refresh();
}}
/>
)}
{currentFeedback && (
<FeedbackModal
feedbackType={currentFeedback[0]}
onClose={() => setCurrentFeedback(null)}
onSubmit={({ message, predefinedFeedback }) => {
onFeedback(
currentFeedback[1],
currentFeedback[0],
message,
predefinedFeedback
);
setCurrentFeedback(null);
}}
/>
)}
<div
ref={sidebarElementRef}
className={`
flex-none
fixed
left-0
z-30
bg-background-100
h-screen
transition-all
bg-opacity-80
duration-300
ease-in-out
${
!untoggled && (showDocSidebar || toggledSidebar)
? "opacity-100 w-[250px] translate-x-0"
: "opacity-0 w-[200px] pointer-events-none -translate-x-10"
}
`}
>
<div className="w-full relative">
<HistorySidebar
showDeleteModal={showDeleteModal}
explicitlyUntoggle={explicitlyUntoggle}
reset={() => setQuery("")}
page="search"
ref={innerSidebarElementRef}
toggleSidebar={toggleSidebar}
toggled={toggledSidebar}
existingChats={querySessions}
/>
</div>
</div>
<div className="absolute include-scrollbar h-screen overflow-y-auto left-0 w-full top-0">
<FunctionalHeader
sidebarToggled={toggledSidebar}
reset={() => setQuery("")}
toggleSidebar={toggleSidebar}
page="search"
/>
<div className="w-full flex">
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
flex-none
overflow-y-hidden
bg-background-100
h-full
transition-all
bg-opacity-80
duration-300
ease-in-out
${toggledSidebar ? "w-[250px]" : "w-[0px]"}
`}
/>
{
<div
className={`desktop:px-24 w-full ${
chatBannerPresent && "mt-10"
} pt-10 relative max-w-[2000px] xl:max-w-[1430px] mx-auto`}
>
<div className="absolute z-10 mobile:px-4 mobile:max-w-searchbar-max mobile:w-[90%] top-12 desktop:left-4 hidden 2xl:block mobile:left-1/2 mobile:transform mobile:-translate-x-1/2 desktop:w-52 3xl:w-64">
{!settings?.isMobile &&
(ccPairs.length > 0 || documentSets.length > 0) && (
<SourceSelector
{...filterManager}
showDocSidebar={toggledSidebar}
availableDocumentSets={finalAvailableDocumentSets}
existingSources={finalAvailableSources}
availableTags={tags}
/>
)}
</div>
<div className="absolute left-0 hidden 2xl:block w-52 3xl:w-64"></div>
<div className="max-w-searchbar-max w-[90%] mx-auto">
{settings?.isMobile && (
<div className="mt-6">
{!(agenticResults && isFetching) || disabledAgentic ? (
<SearchResultsDisplay
searchState={searchState}
disabledAgentic={disabledAgentic}
contentEnriched={contentEnriched}
comments={comments}
sweep={sweep}
agenticResults={agenticResults && !disabledAgentic}
performSweep={performSweep}
searchResponse={searchResponse}
isFetching={isFetching}
defaultOverrides={defaultOverrides}
/>
) : (
<></>
)}
</div>
)}
<div
className={`mobile:fixed mobile:left-1/2 mobile:transform mobile:-translate-x-1/2 mobile:max-w-search-bar-max mobile:w-[90%] mobile:z-100 mobile:bottom-12`}
>
<div
className={`transition-all duration-500 ease-in-out overflow-hidden
${
firstSearch
? "opacity-100 max-h-[500px]"
: "opacity-0 max-h-0"
}`}
onTransitionEnd={handleTransitionEnd}
>
<div className="mt-48 mb-8 flex justify-center items-center">
<div className="w-message-xs 2xl:w-message-sm 3xl:w-message">
<div className="flex">
<div className="text-3xl font-bold font-strong text-strong mx-auto">
Unlock Knowledge
</div>
</div>
</div>
</div>
</div>
<UnconfiguredProviderText
noSources={shouldDisplayNoSources}
showConfigureAPIKey={() => setShowApiKeyModal(true)}
/>
<FullSearchBar
disabled={!isSearchChanged()}
toggleAgentic={
disabledAgentic ? undefined : toggleAgentic
}
showingSidebar={toggledSidebar}
agentic={agentic}
query={query}
setQuery={setQuery}
onSearch={async (agentic?: boolean) => {
setDefaultOverrides(SEARCH_DEFAULT_OVERRIDES_START);
await onSearch({ agentic, offset: 0 });
}}
finalAvailableDocumentSets={finalAvailableDocumentSets}
finalAvailableSources={finalAvailableSources}
filterManager={filterManager}
documentSets={documentSets}
ccPairs={ccPairs}
tags={tags}
/>
</div>
{!firstSearch && (
<SearchAnswer
isFetching={isFetching}
dedupedQuotes={dedupedQuotes}
searchResponse={searchResponse}
setSearchAnswerExpanded={setSearchAnswerExpanded}
searchAnswerExpanded={searchAnswerExpanded}
setCurrentFeedback={setCurrentFeedback}
searchState={searchState}
/>
)}
{!settings?.isMobile && (
<div className="mt-6">
{!(agenticResults && isFetching) || disabledAgentic ? (
<SearchResultsDisplay
searchState={searchState}
disabledAgentic={disabledAgentic}
contentEnriched={contentEnriched}
comments={comments}
sweep={sweep}
agenticResults={
shouldUseAgenticDisplay && !disabledAgentic
}
performSweep={performSweep}
searchResponse={searchResponse}
isFetching={isFetching}
defaultOverrides={defaultOverrides}
/>
) : (
<></>
)}
</div>
)}
</div>
</div>
}
</div>
</div>
</div>
<FixedLogo backgroundToggled={toggledSidebar || showDocSidebar} />
</>
);
};

View File

@@ -29,7 +29,6 @@ import { Popover, PopoverTrigger } from "@/components/ui/popover";
import { PopoverContent } from "@radix-ui/react-popover";
import { CalendarIcon } from "lucide-react";
import { buildDateString, getTimeAgoString } from "@/lib/dateUtils";
import { Separator } from "@/components/ui/separator";
const SectionTitle = ({ children }: { children: string }) => (
<div className="font-bold text-xs mt-2 flex">{children}</div>
@@ -50,9 +49,6 @@ export interface SourceSelectorProps {
availableDocumentSets: DocumentSet[];
existingSources: ValidSources[];
availableTags: Tag[];
toggleFilters?: () => void;
filtersUntoggled?: boolean;
tagsOnLeft?: boolean;
}
export function SourceSelector({
@@ -68,9 +64,6 @@ export function SourceSelector({
existingSources,
availableTags,
showDocSidebar,
toggleFilters,
filtersUntoggled,
tagsOnLeft,
}: SourceSelectorProps) {
const handleSelect = (source: SourceMetadata) => {
setSelectedSources((prev: SourceMetadata[]) => {
@@ -113,154 +106,136 @@ export function SourceSelector({
showDocSidebar ? "4xl:block" : "!block"
} duration-1000 flex ease-out transition-all transform origin-top-right`}
>
<button
onClick={() => toggleFilters && toggleFilters()}
className="flex text-emphasis"
>
<div className="mb-4 pb-2 flex border-b border-border text-emphasis">
<h2 className="font-bold my-auto">Filters</h2>
<FiFilter className="my-auto ml-2" size="16" />
</button>
{!filtersUntoggled && (
</div>
<Popover>
<PopoverTrigger asChild>
<div className="cursor-pointer">
<SectionTitle>Time Range</SectionTitle>
<p className="text-sm text-default mt-2">
{getTimeAgoString(timeRange?.from!) || "Select a time range"}
</p>
</div>
</PopoverTrigger>
<PopoverContent
className="bg-background border-border border rounded-md z-[200] p-0"
align="start"
>
<Calendar
mode="range"
selected={
timeRange
? { from: new Date(timeRange.from), to: new Date(timeRange.to) }
: undefined
}
onSelect={(daterange) => {
const initialDate = daterange?.from || new Date();
const endDate = daterange?.to || new Date();
setTimeRange({
from: initialDate,
to: endDate,
selectValue: timeRange?.selectValue || "",
});
}}
className="rounded-md "
/>
</PopoverContent>
</Popover>
{availableTags.length > 0 && (
<>
<Separator />
<Popover>
<PopoverTrigger asChild>
<div className="cursor-pointer">
<SectionTitle>Time Range</SectionTitle>
<p className="text-sm text-default mt-2">
{getTimeAgoString(timeRange?.from!) || "Select a time range"}
</p>
</div>
</PopoverTrigger>
<PopoverContent
className="bg-background-search-filter border-border border rounded-md z-[200] p-0"
align="start"
>
<Calendar
mode="range"
selected={
timeRange
? {
from: new Date(timeRange.from),
to: new Date(timeRange.to),
}
: undefined
}
onSelect={(daterange) => {
const initialDate = daterange?.from || new Date();
const endDate = daterange?.to || new Date();
setTimeRange({
from: initialDate,
to: endDate,
selectValue: timeRange?.selectValue || "",
});
}}
className="rounded-md "
/>
</PopoverContent>
</Popover>
<div className="mt-4 mb-2">
<SectionTitle>Tags</SectionTitle>
</div>
<TagFilter
tags={availableTags}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
/>
</>
)}
{availableTags.length > 0 && (
<>
<div className="mt-4 mb-2">
<SectionTitle>Tags</SectionTitle>
</div>
<TagFilter
showTagsOnLeft={true}
tags={availableTags}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
{existingSources.length > 0 && (
<div className="mt-4">
<div className="flex w-full gap-x-2 items-center">
<div className="font-bold text-xs mt-2 flex items-center gap-x-2">
<p>Sources</p>
<input
type="checkbox"
checked={allSourcesSelected}
onChange={toggleAllSources}
className="my-auto form-checkbox h-3 w-3 text-primary border-background-900 rounded"
/>
</>
)}
</div>
</div>
<div className="px-1">
{listSourceMetadata()
.filter((source) => existingSources.includes(source.internalName))
.map((source) => (
<div
key={source.internalName}
className={
"flex cursor-pointer w-full items-center " +
"py-1.5 my-1.5 rounded-lg px-2 select-none " +
(selectedSources
.map((source) => source.internalName)
.includes(source.internalName)
? "bg-hover"
: "hover:bg-hover-light")
}
onClick={() => handleSelect(source)}
>
<SourceIcon sourceType={source.internalName} iconSize={16} />
<span className="ml-2 text-sm text-default">
{source.displayName}
</span>
</div>
))}
</div>
</div>
)}
{existingSources.length > 0 && (
<div className="mt-4">
<div className="flex w-full gap-x-2 items-center">
<div className="font-bold text-xs mt-2 flex items-center gap-x-2">
<p>Sources</p>
<input
type="checkbox"
checked={allSourcesSelected}
onChange={toggleAllSources}
className="my-auto form-checkbox h-3 w-3 text-primary border-background-900 rounded"
{availableDocumentSets.length > 0 && (
<>
<div className="mt-4">
<SectionTitle>Knowledge Sets</SectionTitle>
</div>
<div className="px-1">
{availableDocumentSets.map((documentSet) => (
<div key={documentSet.name} className="my-1.5 flex">
<div
key={documentSet.name}
className={
"flex cursor-pointer w-full items-center " +
"py-1.5 rounded-lg px-2 " +
(selectedDocumentSets.includes(documentSet.name)
? "bg-hover"
: "hover:bg-hover-light")
}
onClick={() => handleDocumentSetSelect(documentSet.name)}
>
<HoverPopup
mainContent={
<div className="flex my-auto mr-2">
<InfoIcon className={defaultTailwindCSS} />
</div>
}
popupContent={
<div className="text-sm w-64">
<div className="flex font-medium">Description</div>
<div className="mt-1">{documentSet.description}</div>
</div>
}
classNameModifications="-ml-2"
/>
<span className="text-sm">{documentSet.name}</span>
</div>
</div>
<div className="px-1">
{listSourceMetadata()
.filter((source) =>
existingSources.includes(source.internalName)
)
.map((source) => (
<div
key={source.internalName}
className={
"flex cursor-pointer w-full items-center " +
"py-1.5 my-1.5 rounded-lg px-2 select-none " +
(selectedSources
.map((source) => source.internalName)
.includes(source.internalName)
? "bg-hover"
: "hover:bg-hover-light")
}
onClick={() => handleSelect(source)}
>
<SourceIcon
sourceType={source.internalName}
iconSize={16}
/>
<span className="ml-2 text-sm text-default">
{source.displayName}
</span>
</div>
))}
</div>
</div>
)}
{availableDocumentSets.length > 0 && (
<>
<div className="mt-4">
<SectionTitle>Knowledge Sets</SectionTitle>
</div>
<div className="px-1">
{availableDocumentSets.map((documentSet) => (
<div key={documentSet.name} className="my-1.5 flex">
<div
key={documentSet.name}
className={
"flex cursor-pointer w-full items-center " +
"py-1.5 rounded-lg px-2 " +
(selectedDocumentSets.includes(documentSet.name)
? "bg-hover"
: "hover:bg-hover-light")
}
onClick={() => handleDocumentSetSelect(documentSet.name)}
>
<HoverPopup
mainContent={
<div className="flex my-auto mr-2">
<InfoIcon className={defaultTailwindCSS} />
</div>
}
popupContent={
<div className="text-sm w-64">
<div className="flex font-medium">Description</div>
<div className="mt-1">
{documentSet.description}
</div>
</div>
}
classNameModifications="-ml-2"
/>
<span className="text-sm">{documentSet.name}</span>
</div>
</div>
))}
</div>
</>
)}
))}
</div>
</>
)}
</div>
@@ -491,6 +466,7 @@ export function HorizontalSourceSelector({
max-w-36
border-border
rounded-lg
bg-background
max-h-96
overflow-y-scroll
overscroll-contain
@@ -502,6 +478,7 @@ export function HorizontalSourceSelector({
w-fit
gap-x-1
hover:bg-hover
bg-hover-light
flex
items-center
bg-background-search-filter
@@ -513,26 +490,22 @@ export function HorizontalSourceSelector({
</div>
</PopoverTrigger>
<PopoverContent
className="bg-background-search-filter border-border border rounded-md z-[200] p-0"
className="bg-background border-border border rounded-md z-[200] p-0"
align="start"
>
<Calendar
mode="range"
selected={
timeRange
? { from: new Date(timeRange.from), to: new Date(timeRange.to) }
: undefined
}
onSelect={(daterange) => {
const initialDate = daterange?.from || new Date();
const endDate = daterange?.to || new Date();
mode="single"
selected={timeRange ? new Date(timeRange.from) : undefined}
onSelect={(date) => {
const selectedDate = date || new Date();
const today = new Date();
setTimeRange({
from: initialDate,
to: endDate,
from: selectedDate > today ? today : selectedDate,
to: today,
selectValue: timeRange?.selectValue || "",
});
}}
className="rounded-md"
className="rounded-md "
/>
</PopoverContent>
</Popover>

View File

@@ -9,12 +9,10 @@ export function TagFilter({
tags,
selectedTags,
setSelectedTags,
showTagsOnLeft = false,
}: {
tags: Tag[];
selectedTags: Tag[];
setSelectedTags: React.Dispatch<React.SetStateAction<Tag[]>>;
showTagsOnLeft?: boolean;
}) {
const [filterValue, setFilterValue] = useState("");
const [tagOptionsAreVisible, setTagOptionsAreVisible] = useState(false);
@@ -74,7 +72,7 @@ export function TagFilter({
};
return (
<div className="relative ">
<div className="relative">
<input
ref={inputRef}
className="w-full border border-border py-0.5 px-2 rounded text-sm h-8"
@@ -90,22 +88,7 @@ export function TagFilter({
<div
key={tag.tag_key + tag.tag_value}
onClick={() => onSelectTag(tag)}
className={`
max-w-full
break-all
line-clamp-1
text-ellipsis
flex
text-sm
border
border-border
py-0.5
px-2
rounded
cursor-pointer
bg-background-search-filter
hover:bg-background-search-filter-dropdown
`}
className="max-w-full break-all line-clamp-1 text-ellipsis flex text-sm border border-border py-0.5 px-2 rounded cursor-pointer bg-background hover:bg-hover"
>
{tag.tag_key}
<b>=</b>
@@ -123,16 +106,10 @@ export function TagFilter({
</div>
)}
{tagOptionsAreVisible && (
<div
className={` absolute z-[100] ${
showTagsOnLeft
? "left-0 top-0 translate-y-[2rem]"
: "right-0 translate-x-[105%] top-0"
} z-40`}
>
<div className="absolute top-0 right-0 transform translate-x-[105%] z-40">
<div
ref={popupRef}
className="p-2 border border-border rounded shadow-lg w-72 bg-background-search-filter"
className="p-2 border border-border rounded shadow-lg w-72 bg-background"
>
<div className="flex border-b border-border font-medium pb-1 text-xs mb-2">
<FiTag className="mr-1 my-auto" />
@@ -155,11 +132,7 @@ export function TagFilter({
cursor-pointer
bg-background
hover:bg-hover
${
selectedTags.includes(tag)
? "bg-background-search-filter-dropdown"
: ""
}
${selectedTags.includes(tag) ? "bg-hover" : ""}
`}
>
{tag.tag_key}

View File

@@ -1,73 +1,55 @@
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
import { ReactNode } from "react";
import { CompactDocumentCard } from "../DocumentDisplay";
import { LoadedDanswerDocument } from "@/lib/search/interfaces";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
// NOTE: This is the preivous version of the citations which works just fine
export function Citation({
children,
link,
document,
index,
}: {
link?: string;
children?: JSX.Element | string | null | ReactNode;
index?: number;
document: LoadedDanswerDocument;
}) {
const innerText = children
? children?.toString().split("[")[1].split("]")[0]
: index;
// const CitationTrigger = () => {
// return (
// );
// };
if (link) {
if (link != "") {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div
onMouseDown={() => window.open(link, "_blank")}
className="inline-flex items-center ml-1 cursor-pointer transition-all duration-200 ease-in-out"
<CustomTooltip
citation
content={<div className="inline-block p-0 m-0 truncate">{link}</div>}
>
<a
onMouseDown={() => (link ? window.open(link, "_blank") : undefined)}
className="cursor-pointer inline ml-1 align-middle"
>
<span className="group relative -top-1 text-sm text-gray-500 dark:text-gray-400 selection:bg-indigo-300 selection:text-black dark:selection:bg-indigo-900 dark:selection:text-white">
<span
className="inline-flex bg-background-200 group-hover:bg-background-300 items-center justify-center h-3.5 min-w-3.5 px-1 text-center text-xs rounded-full border-1 border-gray-400 ring-1 ring-gray-400 divide-gray-300 dark:divide-gray-700 dark:ring-gray-700 dark:border-gray-700 transition duration-150"
data-number="3"
>
<span className="relative no-underline -top-0.5 px-1.5 py-0.5 text-xs font-medium text-gray-700 bg-gray-100 rounded-full border border-gray-300 hover:bg-gray-200 hover:text-gray-900 shadow-sm no-underline">
{innerText}
</span>
</div>
</TooltipTrigger>
<TooltipContent width="w-f" className="bg-background">
<CompactDocumentCard document={document} />
</TooltipContent>
</Tooltip>
</TooltipProvider>
{innerText}
</span>
</span>
</a>
</CustomTooltip>
);
} else {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div
onMouseDown={() => window.open(link, "_blank")}
className="inline-flex items-center ml-1 cursor-pointer transition-all duration-200 ease-in-out"
<CustomTooltip content={<div>This doc doesn&apos;t have a link!</div>}>
<div className="inline-block cursor-help leading-none inline ml-1 align-middle">
<span className="group relative -top-1 text-gray-500 dark:text-gray-400 selection:bg-indigo-300 selection:text-black dark:selection:bg-indigo-900 dark:selection:text-white">
<span
className="inline-flex bg-background-200 group-hover:bg-background-300 items-center justify-center h-3.5 min-w-3.5 flex-none px-1 text-center text-xs rounded-full border-1 border-gray-400 ring-1 ring-gray-400 divide-gray-300 dark:divide-gray-700 dark:ring-gray-700 dark:border-gray-700 transition duration-150"
data-number="3"
>
<span className="relative no-underline -top-0.5 px-1.5 py-0.5 text-xs font-medium text-gray-700 bg-gray-100 rounded-full border border-gray-300 hover:bg-gray-200 hover:text-gray-900 shadow-sm no-underline">
{innerText}
</span>
</div>
</TooltipTrigger>
<TooltipContent width="max-w-lg" backgroundColor="bg-background-200">
<CompactDocumentCard document={document} />
</TooltipContent>
</Tooltip>
</TooltipProvider>
{innerText}
</span>
</span>
</div>
</CustomTooltip>
);
}
}

View File

@@ -43,9 +43,11 @@ export async function fetchSettingsSS(): Promise<CombinedSettings | null> {
if (!results[0].ok) {
if (results[0].status === 403 || results[0].status === 401) {
settings = {
auto_scroll: true,
product_gating: GatingType.NONE,
gpu_enabled: false,
chat_page_enabled: true,
search_page_enabled: true,
default_page: "search",
maximum_chat_retention_days: null,
notifications: [],
needs_reindexing: false,

View File

@@ -131,7 +131,7 @@ export const CustomTooltip = ({
transform -translate-x-1/2 text-sm
${
light
? "text-text-800 bg-background-200"
? "text-gray-800 bg-background-200"
: "text-white bg-background-800"
}
rounded-lg shadow-lg`}

View File

@@ -48,13 +48,12 @@ function Badge({
...props
}: BadgeProps & {
icon?: React.ElementType;
size?: "sm" | "md" | "xs";
size?: "sm" | "md";
circle?: boolean;
}) {
const sizeClasses = {
sm: "px-2.5 py-0.5 text-xs",
md: "px-3 py-1 text-sm",
xs: "px-1.5 py-0.25 text-[.5rem]", // Made xs smaller
};
return (
@@ -63,20 +62,10 @@ function Badge({
{...props}
>
{Icon && (
<Icon
className={cn(
"mr-1",
size === "sm" ? "h-3 w-3" : size === "xs" ? "h-2 w-2" : "h-4 w-4"
)}
/>
<Icon className={cn("mr-1", size === "sm" ? "h-3 w-3" : "h-4 w-4")} />
)}
{circle && (
<div
className={cn(
"mr-2 rounded-full bg-current opacity-80",
size === "xs" ? "h-2 w-2" : "h-2.5 w-2.5"
)}
/>
<div className="h-2.5 w-2.5 mr-2 rounded-full bg-current opacity-80" />
)}
{props.children}
</div>

View File

@@ -34,45 +34,23 @@ function Calendar({
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"text-calendar-text-muted rounded-md w-9 font-normal text-[0.8rem] dark:text-calendar-text-muted-dark",
"text-neutral-500 rounded-md w-9 font-normal text-[0.8rem] dark:text-neutral-400",
row: "flex w-full mt-2",
cell: cn(
"h-9 w-9 text-center text-sm p-0 relative",
"[&:has([aria-selected].day-range-end)]:rounded-r-md",
"[&:has([aria-selected].day-outside)]:bg-calendar-bg-outside-selected",
"[&:has([aria-selected])]:bg-calendar-bg-selected",
"first:[&:has([aria-selected])]:rounded-l-md",
"last:[&:has([aria-selected])]:rounded-r-md",
"focus-within:relative focus-within:z-20",
"dark:[&:has([aria-selected].day-outside)]:bg-calendar-bg-outside-selected-dark",
"dark:[&:has([aria-selected])]:bg-calendar-bg-selected-dark"
),
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-neutral-100/50 [&:has([aria-selected])]:bg-neutral-100 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20 dark:[&:has([aria-selected].day-outside)]:bg-neutral-800/50 dark:[&:has([aria-selected])]:bg-neutral-800",
day: cn(
buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
),
day_selected: cn(
"bg-calendar-selected text-calendar-text-selected rounded-full",
"hover:bg-calendar-selected hover:text-calendar-text-selected",
"focus:bg-calendar-selected focus:text-calendar-text-selected"
),
day_range_start: cn(
"bg-calendar-range-start text-calendar-text-selected rounded-l-full",
"hover:bg-calendar-range-start hover:text-calendar-text-selected",
"focus:bg-calendar-range-start focus:text-calendar-text-selected"
),
day_range_end: cn(
"bg-calendar-range-end text-calendar-text-selected rounded-r-full",
"hover:bg-calendar-range-end hover:text-calendar-text-selected",
"focus:bg-calendar-range-end focus:text-calendar-text-selected"
),
day_range_end: "day-range-end !text-neutral-200",
day_selected:
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900 hover:text-neutral-50 focus:bg-neutral-900 focus:text-neutral-50 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50 dark:hover:text-neutral-900 dark:focus:bg-neutral-50 dark:focus:text-neutral-900",
day_today:
"bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50",
day_outside:
"day-outside text-neutral-500 opacity-50 aria-selected:bg-neutral-100/50 aria-selected:text-neutral-500 aria-selected:opacity-30 dark:text-neutral-400 dark:aria-selected:bg-neutral-800/50 dark:aria-selected:text-neutral-400",
day_disabled: "text-neutral-500 opacity-50 dark:text-neutral-400",
day_range_middle:
"aria-selected:bg-calendar-bg-selected aria-selected:text-calendar-text-selected dark:aria-selected:bg-calendar-bg-selected-dark dark:aria-selected:text-calendar-text-selected-dark",
"aria-selected:bg-neutral-100 aria-selected:text-neutral-900 dark:aria-selected:bg-neutral-800 dark:aria-selected:text-neutral-50",
day_hidden: "invisible",
...classNames,
}}

View File

@@ -1,30 +0,0 @@
"use client";
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-neutral-200 border-neutral-900 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=checked]:text-neutral-50 dark:border-neutral-800 dark:border-neutral-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=checked]:text-neutral-900",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };

View File

@@ -1,29 +0,0 @@
"use client";
import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=unchecked]:bg-neutral-200 dark:focus-visible:ring-neutral-300 dark:focus-visible:ring-offset-neutral-950 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=unchecked]:bg-neutral-800",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 dark:bg-neutral-950"
)}
/>
</SwitchPrimitives.Root>
));
Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch };

View File

@@ -13,19 +13,17 @@ const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & {
width?: string;
maxWidth?: string;
backgroundColor?: string;
}
>(({ className, sideOffset = 4, width, backgroundColor, ...props }, ref) => (
>(({ className, sideOffset = 4, maxWidth, backgroundColor, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
`z-50 overflow-hidden rounded-md border border-neutral-200 text-white ${
backgroundColor || "bg-background-900"
}
${width || "max-w-sm"}
px-2 py-1.5 text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50 `,
} px-2 py-1.5 text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50 max-w-sm`,
className
)}
{...props}

View File

@@ -12,7 +12,6 @@ interface UserContextType {
isCurator: boolean;
refreshUser: () => Promise<void>;
isCloudSuperuser: boolean;
updateUserAutoScroll: (autoScroll: boolean | null) => Promise<void>;
}
const UserContext = createContext<UserContextType | undefined>(undefined);
@@ -28,7 +27,6 @@ export function UserProvider({
const [isLoadingUser, setIsLoadingUser] = useState(false);
const posthog = usePostHog();
console.log("upToDateUser", upToDateUser);
useEffect(() => {
if (!posthog) return;
@@ -57,31 +55,6 @@ export function UserProvider({
setIsLoadingUser(false);
}
};
const updateUserAutoScroll = async (autoScroll: boolean | null) => {
try {
const response = await fetch("/api/auto-scroll", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ auto_scroll: autoScroll }),
});
setUpToDateUser((prevUser) => {
if (prevUser) {
return { ...prevUser, auto_scroll: autoScroll };
}
return prevUser;
});
if (!response.ok) {
throw new Error("Failed to update auto-scroll setting");
}
} catch (error) {
console.error("Error updating auto-scroll setting:", error);
throw error;
}
// await updateUserSettings({ auto_scroll: autoScroll });
};
const refreshUser = async () => {
await fetchUser();
@@ -93,7 +66,6 @@ export function UserProvider({
user: upToDateUser,
isLoadingUser,
refreshUser,
updateUserAutoScroll,
isAdmin: upToDateUser?.role === UserRole.ADMIN,
isCurator: upToDateUser?.role === UserRole.CURATOR,
isCloudSuperuser: upToDateUser?.is_cloud_superuser ?? false,

View File

@@ -62,9 +62,6 @@ export interface DanswerDocument {
is_internet: boolean;
validationState?: null | "good" | "bad";
}
export interface LoadedDanswerDocument extends DanswerDocument {
icon: React.FC<{ size?: number; className?: string }>;
}
export interface SearchDanswerDocument extends DanswerDocument {
is_relevant: boolean;

View File

@@ -61,7 +61,6 @@ export interface User {
oidc_expiry?: Date;
is_cloud_superuser?: boolean;
organization_name: string | null;
auto_scroll: boolean | null;
}
export interface MinimalUserSnapshot {

View File

@@ -157,7 +157,6 @@ export const getCurrentUserSS = async (): Promise<User | null> => {
return null;
}
const user = await response.json();
console.log("user", user);
return user;
} catch (e) {
console.log(`Error fetching user: ${e}`);

View File

@@ -1,399 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx,mdx}",
// tremor
"./node_modules/@tremor/**/*.{js,ts,jsx,tsx}",
],
theme: {
transparent: "transparent",
current: "currentColor",
extend: {
transitionProperty: {
spacing: "margin, padding",
},
keyframes: {
"subtle-pulse": {
"0%, 100%": { opacity: 0.9 },
"50%": { opacity: 0.5 },
},
pulse: {
"0%, 100%": { opacity: 0.9 },
"50%": { opacity: 0.4 },
},
},
animation: {
"fade-in-up": "fadeInUp 0.5s ease-out",
"subtle-pulse": "subtle-pulse 2s ease-in-out infinite",
pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
},
gradientColorStops: {
"neutral-10": "#e5e5e5 5%",
},
screens: {
"2xl": "1420px",
"3xl": "1700px",
"4xl": "2000px",
mobile: { max: "767px" },
desktop: "768px",
},
fontFamily: {
sans: ["var(--font-inter)"],
},
width: {
"message-xs": "450px",
"message-sm": "550px",
"message-default": "740px",
"searchbar-xs": "560px",
"searchbar-sm": "660px",
searchbar: "850px",
"document-sidebar": "800px",
"document-sidebar-large": "1000px",
"searchbar-max": "60px",
},
maxWidth: {
"document-sidebar": "1000px",
"message-max": "850px",
"content-max": "725px",
"searchbar-max": "800px",
},
colors: {
// code styling
"code-bg": "var(--black)",
"code-text": "var(--code-text)",
"token-comment": "var(--token-comment)",
"token-punctuation": "var(--token-punctuation)",
"token-property": "var(--token-property)",
"token-selector": "var(--token-selector)",
"token-atrule": "var(--token-atrule)",
"token-function": "var(--token-function)",
"token-regex": "var(--token-regex)",
"token-attr-name": "var(--token-attr-name)",
"non-selectable": "var(--non-selectable)",
// background
background: "#EDEDF1",
"background-100": "var(--background-100)",
"background-125": "var(--background-125)",
"background-150": "var(--background-150)",
"background-200": "var(--background-200)",
"background-300": "var(--background-300)",
"background-400": "var(--background-400)",
"background-500": "var(--background-500)",
"background-600": "var(--background-600)",
"background-700": "var(--background-700)",
"background-800": "var(--background-800)",
"background-900": "var(--background-900)",
"background-toggle": "var(--background-100)",
"toggled-background": "var(--background-500)",
"untoggled-background": "var(--background-300)",
"background-inverted": "var(--background-inverted)",
"background-emphasis": "var(--background-emphasis)",
"background-strong": "var(--background-800)",
"background-search": "var(--white)",
"text-history-sidebar-button": "var(--text-900)",
"userdropdown-background": "var(--background-300)",
"text-sidebar-toggled-header": "var(--text-200)",
"text-sidebar-header": "var(--text-900)",
"text-sidebar-dark": "var(--text-200)",
"search-answer-border": "var(--background-300)",
"background-search-filter": "var(--background-100)",
"background-search-filter-dropdown": "var(--background-100)",
"text-editing-message": "var(--text-800)",
"background-chat-hover": "#1D4ED8",
"background-chat-selected": "#1D4ED8",
// colors for sidebar in chat, search, and manage settings
"background-sidebar": "#0021A5",
"background-settings-sidebar": "var(--background-100)",
"background-chatbar": "#FFFFFF",
"text-sidebar": "#FFFFFF",
// Settings
"text-sidebar-subtle": "#A9D4FF",
"icon-settings-sidebar": "var(--text-300)",
"text-settings-sidebar": "var(--text-300)",
"text-settings-sidebar-strong": "var(--text-200)",
"background-settings-hover": "#0026CC",
"background-back-button": "#A9D4FF",
"text-back-button": "var(--text-800)",
"background-search-answer": "var(--background-100)",
"text-search-answer": "var(--text-100)",
"text-recent-assistants": "var(--text-800)",
"border-recent-assistants": "var(--background-300)",
"text-mobile-sidebar-toggled": "var(--text-200)",
"text-mobile-sidebar-untoggled": "var(--text-900)",
"background-starter-message": "var(--background-100)",
"background-starter-message-hover": "var(--background-150)",
// Background for chat messages (user bubbles)
user: "#0021A5",
// Colors for the search toggle buttons
"background-agentic-toggled": "var(--light-success)",
"background-agentic-untoggled": "var(--undo)",
"text-agentic-toggled": "var(--text-800)",
"text-agentic-untoggled": "var(--white)",
"text-chatbar-subtle": "var(--text-500)",
"text-chatbar": "var(--text-800)",
// Color for the star indicator on high quality search results.
"star-indicator": "var(--background-100)",
// Backgrounds for submit buttons on search and chat
"submit-background": "#0021A5",
"disabled-submit-background": "var(--background-400)",
"background-recent-assistants-hover": "var(--background-100)",
"sidebar-toggle": "var(--text-200)",
input: "var(--white)",
"text-50": "var(--text-50)",
"text-100": "var(--text-100)",
"text-200": "var(--text-200)",
"text-300": "var(--text-300)",
"text-400": "var(--text-400)",
"text-500": "var(--text-500)",
"text-600": "var(--text-600)",
"text-700": "var(--text-700)",
"text-800": "var(--text-800)",
"text-900": "var(--text-900)",
"text-950": "var(--text-950)",
"user-text": "#FFFFFF",
description: "var(--text-400)",
subtle: "var(--text-500)",
default: "var(--text-600)",
emphasis: "var(--text-700)",
strong: "var(--text-900)",
// borders
border: "var(--border)",
"border-light": "var(--border-light)",
"border-medium": "var(--border-medium)",
"border-strong": "var(--border-strong)",
"border-dark": "var(--border-dark)",
"non-selectable-border": "#f5c2c7",
inverted: "var(--white)",
link: "var(--link)",
"link-hover": "var(--link-hover)",
// one offs
error: "var(--error)",
success: "var(--success)",
alert: "var(--alert)",
accent: "var(--accent)",
// hover
"hover-light": "var(--background-100)",
"hover-lightish": "var(--background-125)",
hover: "var(--background-200)",
"hover-emphasis": "var(--background-700)",
"accent-hover": "var(--accent-hover)",
// keyword highlighting
highlight: {
text: "var(--highlight-text)",
},
// scrollbar
scrollbar: {
track: "var(--scrollbar-track)",
thumb: "var(--scrollbar-thumb)",
"thumb-hover": "var(--scrollbar-thumb-hover)",
dark: {
thumb: "var(--scrollbar-dark-thumb)",
"thumb-hover": "var(--scrollbar-dark-thumb-hover)",
},
},
// for display documents
document: "var(--document-color)",
// light mode
tremor: {
brand: {
faint: "var(--tremor-brand-faint)",
muted: "var(--tremor-brand-muted)",
subtle: "var(--tremor-brand-subtle)",
DEFAULT: "#3b82f6", // blue-500
emphasis: "var(--tremor-brand-emphasis)",
inverted: "var(--tremor-brand-inverted)",
},
background: {
muted: "var(--tremor-background-muted)",
subtle: "var(--tremor-background-subtle)",
DEFAULT: "#ffffff", // white
emphasis: "var(--tremor-background-emphasis)",
},
border: {
DEFAULT: "#e5e7eb", // gray-200
},
ring: {
DEFAULT: "#e5e7eb", // gray-200
},
content: {
subtle: "var(--tremor-content-subtle)",
DEFAULT: "var(--tremor-content-default)",
emphasis: "var(--tremor-content-emphasis)",
strong: "var(--tremor-content-strong)",
inverted: "var(--tremor-content-inverted)",
},
},
// dark mode
"dark-tremor": {
brand: {
faint: "var(--dark-tremor-brand-faint)",
muted: "var(--dark-tremor-brand-muted)",
subtle: "var(--dark-tremor-brand-subtle)",
DEFAULT: "#3b82f6", // blue-500
emphasis: "var(--dark-tremor-brand-emphasis)",
inverted: "var(--dark-tremor-brand-inverted)",
},
background: {
muted: "var(--dark-tremor-background-muted)",
subtle: "var(--dark-tremor-background-subtle)",
DEFAULT: "var(--dark-tremor-background-default)",
emphasis: "var(--dark-tremor-background-emphasis)",
},
border: {
DEFAULT: "#1f2937", // gray-800
},
ring: {
DEFAULT: "#1f2937", // gray-800
},
content: {
subtle: "var(--dark-tremor-content-subtle)",
DEFAULT: "var(--dark-tremor-content-default)",
emphasis: "var(--dark-tremor-content-emphasis)",
strong: "var(--dark-tremor-content-strong)",
inverted: "var(--dark-tremor-content-inverted)",
},
},
calendar: {
// Light mode
"bg-selected": "#3B82F6",
"bg-outside-selected": "rgba(59, 130, 246, 0.2)",
"text-muted": "#6B7280",
"text-selected": "#FFFFFF",
"range-start": "#000000",
"range-middle": "#EFF6FF",
"range-end": "#000000",
"text-in-range": "#1E40AF",
// Dark mode
"bg-selected-dark": "#2563EB",
"bg-outside-selected-dark": "rgba(37, 99, 235, 0.2)",
"text-muted-dark": "#9CA3AF",
"text-selected-dark": "#F3F4F6",
"range-start-dark": "#1E40AF",
"range-middle-dark": "#1E3A8A",
"range-end-dark": "#1E40AF",
"text-in-range-dark": "#BFDBFE",
// Hover effects
"hover-bg": "#60A5FA",
"hover-bg-dark": "#3B82F6",
"hover-text": "#1E3A8A",
"hover-text-dark": "#DBEAFE",
// Today's date
"today-bg": "#FDE68A",
"today-bg-dark": "#92400E",
"today-text": "#92400E",
"today-text-dark": "#FDE68A",
},
},
boxShadow: {
// light
"tremor-input": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
"tremor-card":
"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
"tremor-dropdown":
"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
// dark
"dark-tremor-input": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
"dark-tremor-card":
"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
"dark-tremor-dropdown":
"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
},
borderRadius: {
"tremor-small": "0.375rem",
"tremor-default": "0.5rem",
"tremor-full": "9999px",
},
fontSize: {
"code-sm": "small",
"tremor-label": ["0.75rem"],
"tremor-default": ["0.875rem", { lineHeight: "1.25rem" }],
"tremor-title": ["1.125rem", { lineHeight: "1.75rem" }],
"tremor-metric": ["1.875rem", { lineHeight: "2.25rem" }],
},
fontWeight: {
description: "375",
"token-bold": "bold",
},
fontStyle: {
"token-italic": "italic",
},
},
},
safelist: [
{
pattern:
/^(bg-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
variants: ["hover", "ui-selected"],
},
{
pattern:
/^(text-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
variants: ["hover", "ui-selected"],
},
{
pattern:
/^(border-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
variants: ["hover", "ui-selected"],
},
{
pattern:
/^(ring-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
},
{
pattern:
/^(stroke-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
},
{
pattern:
/^(fill-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/,
},
],
plugins: [
require("@tailwindcss/typography"),
require("@headlessui/tailwindcss"),
],
};

View File

@@ -108,8 +108,6 @@ module.exports = {
"background-search-filter": "var(--background-100)",
"background-search-filter-dropdown": "var(--background-100)",
"user-bubble": "var(--user-bubble)",
// colors for sidebar in chat, search, and manage settings
"background-sidebar": "var(--background-100)",
"background-chatbar": "var(--background-100)",
@@ -143,14 +141,6 @@ module.exports = {
// Background for chat messages (user bubbles)
user: "var(--user-bubble)",
"userdropdown-background": "var(--background-100)",
"text-mobile-sidebar-toggled": "var(--text-800)",
"text-mobile-sidebar-untoggled": "var(--text-500)",
"text-editing-message": "var(--text-800)",
"background-sidebar": "var(--background-100)",
"background-search-filter": "var(--background-100)",
"background-search-filter-dropdown": "var(--background-hover)",
"background-toggle": "var(--background-100)",
// Colors for the search toggle buttons
@@ -317,7 +307,6 @@ module.exports = {
"tremor-full": "9999px",
},
fontSize: {
"2xs": "0.625rem",
"code-sm": "small",
"tremor-label": ["0.75rem"],
"tremor-default": ["0.875rem", { lineHeight: "1.25rem" }],
@@ -331,39 +320,6 @@ module.exports = {
fontStyle: {
"token-italic": "italic",
},
calendar: {
// Light mode
"bg-selected": "#4B5563",
"bg-outside-selected": "rgba(75, 85, 99, 0.2)",
"text-muted": "#6B7280",
"text-selected": "#FFFFFF",
"range-start": "#000000",
"range-middle": "#F3F4F6",
"range-end": "#000000",
"text-in-range": "#1F2937",
// Dark mode
"bg-selected-dark": "#6B7280",
"bg-outside-selected-dark": "rgba(107, 114, 128, 0.2)",
"text-muted-dark": "#9CA3AF",
"text-selected-dark": "#F3F4F6",
"range-start-dark": "#374151",
"range-middle-dark": "#4B5563",
"range-end-dark": "#374151",
"text-in-range-dark": "#E5E7EB",
// Hover effects
"hover-bg": "#9CA3AF",
"hover-bg-dark": "#6B7280",
"hover-text": "#374151",
"hover-text-dark": "#E5E7EB",
// Today's date
"today-bg": "#D1D5DB",
"today-bg-dark": "#4B5563",
"today-text": "#374151",
"today-text-dark": "#D1D5DB",
},
},
},
safelist: [